mirror of
https://github.com/GAM-team/GAM.git
synced 2026-06-24 16:11:36 +00:00
Compare commits
12 Commits
20231202.1
...
20231221.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae46ae8738 | ||
|
|
06a4c7a8c9 | ||
|
|
f89f730957 | ||
|
|
80fc40a9c7 | ||
|
|
2bb0088ade | ||
|
|
d113b3ec8e | ||
|
|
97e13b92be | ||
|
|
dc832b8c7f | ||
|
|
56c33fec87 | ||
|
|
48862997b0 | ||
|
|
59dd01f1e8 | ||
|
|
d639e8e728 |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -114,7 +114,7 @@ jobs:
|
||||
path: |
|
||||
bin.tar.xz
|
||||
src/cpython
|
||||
key: gam-${{ matrix.jid }}-202311118
|
||||
key: gam-${{ matrix.jid }}-20231212
|
||||
|
||||
- name: Untar Cache archive
|
||||
if: matrix.goal == 'build' && steps.cache-python-ssl.outputs.cache-hit == 'true'
|
||||
|
||||
@@ -55,13 +55,18 @@ gam create alias bob[@yourdomain.com] user robert[@yourdomain.com]
|
||||
The existing alias is deleted and a new alias is created.
|
||||
```
|
||||
gam update alias|aliases <EmailAddressEntity> user|group|target <UniqueID>|<EmailAddress>
|
||||
[notargetverify]
|
||||
[notargetverify] [waitafterdelete <Integer>]
|
||||
```
|
||||
`<EmailAddressEntity>` are the aliases, `<EmailAddress>` is the target.
|
||||
|
||||
By default, GAM makes additional API calls to verify that the target email address exists before updating the alias;
|
||||
if you know that the target exists, you can suppress the verification with `notargetverify.
|
||||
|
||||
GAM updates an alias to point to a new target by deleting the alias and then recreates the alias pointing to the new target.
|
||||
Unfortunately, if these commands are executed back-to-back; Google generates the `Update Failed: Duplicate` error.
|
||||
Now, GAM waits 2 seconds between the delete and the insert which seems to eliminate the problem. If the problem persists,
|
||||
use the option `waitafterdelete <Integer>` to increase the wait time to a maximum of 10 seconds.
|
||||
|
||||
## Delete an alias regardless of the target
|
||||
```
|
||||
gam delete alias|aliases [user|group|target] <EmailAddressEntity>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,104 @@ Add the `-s` option to the end of the above commands to suppress creating the `g
|
||||
|
||||
See [Downloads](https://github.com/taers232c/GAMADV-XTD3/wiki/Downloads) for Windows or other options, including manual installation
|
||||
|
||||
### 6.66.16
|
||||
|
||||
Added option `convertcrnl` to `gam update chromepolicy` to properly handle carriage returns (\r) and line feeds (\n)
|
||||
in value strings entered on the command line in the `<Field> <Value>` form.
|
||||
```
|
||||
gam update chromepolicy convertcrnl chrome.devices.DisabledDeviceReturnInstructions
|
||||
deviceDisabledMessage "Please return device to:\nSchool\n123 Main Street\nAnytown US" ou /Path/to/OU
|
||||
```
|
||||
|
||||
### 6.66.15
|
||||
|
||||
Added option `copysubfilesownedby any|me|others` to `gam <UserTypeEntity> copy drivefile` that allows
|
||||
specification of which source folder sub files to copy based on file ownership; the default is `any`.
|
||||
This only applies when files are being copied from a 'My Drive'.
|
||||
|
||||
### 6.66.14
|
||||
|
||||
Updated `gam <UserTypeEntity> modify messages` to recognize the following error:
|
||||
```
|
||||
ERROR: 400: invalid - Invalid label: SENT
|
||||
```
|
||||
|
||||
Updated `gam update alias <EmailAddressEntity> user|group|target <EmailAddress>`
|
||||
to avoid the following problem.
|
||||
```
|
||||
$ gam update alias testalias@domain.com user testuser
|
||||
User Alias: testalias@domain.com, Deleted
|
||||
User Alias: testalias@domain.com, User: testuser@domain.com, Update Failed: Duplicate, Email Address: testalias@domain.com
|
||||
```
|
||||
GAM updates an alias to point to a new target by deleting the alias and then recreating the alias pointing to the new target.
|
||||
Unfortunately, if these commands are executed back-to-back; Google generates the `Update Failed: Duplicate` error.
|
||||
Now, GAM waits 2 seconds between the delete and the insert which seems to eliminate the problem. If the problem persists,
|
||||
the option `waitafterdelete <Integer>` can be used to increase the wait time to a maximum of 10 seconds.
|
||||
|
||||
### 6.66.13
|
||||
|
||||
Updated functionality of option `preservefiletimes` in `gam <UserTypeEntity> update drivefile <DriveFileEntity>`.
|
||||
|
||||
* Current
|
||||
* `preservefiletimes localfile <FileName>` - `modifiedTime` of `<DriveFileEntity>` is set to that of `localfile <FileName>`
|
||||
* `preservefiletimes` - No effect
|
||||
* Updated
|
||||
* `preservefiletimes localfile <FileName>` - `modifiedTime` of `<DriveFileEntity>` is set to that of `localfile <FileName>`
|
||||
* `preservefiletimes` - `modifiedTime` of `<DriveFileEntity>` retains its current value
|
||||
|
||||
### 6.66.12
|
||||
|
||||
Upgraded to Python 3.12.1 where possible.
|
||||
|
||||
Updated all drive commands to handle the following error:
|
||||
```
|
||||
ERROR: 401: Active session is invalid. Error code: 4 - authError
|
||||
```
|
||||
This is due to the Drive SDK API being disabled in the user's OU.
|
||||
* See: https://support.google.com/a/answer/6105699
|
||||
|
||||
### 6.66.11
|
||||
|
||||
Fixed/improved handling of shortcuts in `gam <UserTypeEntity> transfer drive`.
|
||||
|
||||
### 6.66.10
|
||||
|
||||
Updated `gam create datatransfer` to handle the following error:
|
||||
```
|
||||
ERROR: 401: Active session is invalid. Error code: 4 - authError
|
||||
```
|
||||
|
||||
### 6.66.09
|
||||
|
||||
Fixed bug in `gam <UserTypeEntity> print filelist ... allfields` that caused a trap
|
||||
when `gam.cfg` contained `drive_v3_native_names = False`.
|
||||
|
||||
### 6.66.08
|
||||
|
||||
Added additional columns `isBase` and `baseId` to `gam <UserTypeEntity> print fileparenttree`
|
||||
to simplify processing the output in a script.
|
||||
|
||||
### 6.66.07
|
||||
|
||||
Fixed bug in `gam <UserTypeEntity> print diskusage` that caused a trap.
|
||||
|
||||
### 6.66.06
|
||||
|
||||
Added a command the print the parent tree of file/folder.
|
||||
```
|
||||
gam <UserTypeEntity> print fileparenttree <DriveFileEntity> [todrive <ToDriveAttribute>*]
|
||||
[stripcrsfromname]
|
||||
```
|
||||
* See: https://github.com/taers232c/GAMADV-XTD3/wiki/Users-Drive-Files-Display#display-file-parent-tree
|
||||
|
||||
### 6.66.05
|
||||
|
||||
Added column `space.name` to `gam <UserTypeEntity> print chatmembers`.
|
||||
|
||||
### 6.66.04
|
||||
|
||||
Updated Chat info|show|print commands to display all time fields in local time if specified in `gam.cfg`.
|
||||
|
||||
### 6.66.03
|
||||
|
||||
Fixed bug in `gam <UserTypeEntity> print filelist select <DriveFileEntity>` where `stripcrsfromname` was not being
|
||||
|
||||
@@ -334,7 +334,7 @@ writes the credentials into the file oauth2.txt.
|
||||
admin@server:/Users/admin/bin/gamadv-xtd3$ rm -f /Users/admin/GAMConfig/oauth2.txt
|
||||
admin@server:/Users/admin/bin/gamadv-xtd3$ ./gam version
|
||||
WARNING: Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, Item: oauth2_txt, Value: /Users/admin/GAMConfig/oauth2.txt, Not Found
|
||||
GAMADV-XTD3 6.66.03 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
|
||||
GAMADV-XTD3 6.66.16 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
|
||||
Ross Scroggs <ross.scroggs@gmail.com>
|
||||
Python 3.10.8 64-bit final
|
||||
MacOS High Sierra 10.13.6 x86_64
|
||||
@@ -1002,7 +1002,7 @@ writes the credentials into the file oauth2.txt.
|
||||
C:\GAMADV-XTD3>del C:\GAMConfig\oauth2.txt
|
||||
C:\GAMADV-XTD3>gam version
|
||||
WARNING: Config File: C:\GAMConfig\gam.cfg, Section: DEFAULT, Item: oauth2_txt, Value: C:\GAMConfig\oauth2.txt, Not Found
|
||||
GAMADV-XTD3 6.66.03 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
|
||||
GAMADV-XTD3 6.66.16 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
|
||||
Ross Scroggs <ross.scroggs@gmail.com>
|
||||
Python 3.12.0 64-bit final
|
||||
Windows-10-10.0.17134 AMD64
|
||||
|
||||
@@ -585,13 +585,6 @@ gam <UserTypeEntity> delete events <UserCalendarEntity> [doit] [<EventNotificati
|
||||
```
|
||||
No events are deleted unless you specify the `doit` option; omit `doit` to verify that you properly selected the events to delete.
|
||||
|
||||
## Move calendar events to another calendar
|
||||
Generally you won't move all events from one calendar to another; typically, you'll move events created by the event creator
|
||||
using `matchfield creatoremail <RegularExpression>` in conjunction with other `<EventSelectProperty>` and `<EventMatchProperty>` options.
|
||||
```
|
||||
gam <UserTypeEntity> move events <UserCalendarEntity> [<EventEntity>] destination|to <CalendarItem> [<EventNotificationAttribute>]
|
||||
```
|
||||
|
||||
## Empty calendar trash
|
||||
A user signed in to Google Calendar can empty the calendar trash but there is no direct API support for this operation.
|
||||
To empty the calendar trash a temporary calendar is created, the deleted events are moved to the temporary calendar and then the temporary calendar is deleted.
|
||||
|
||||
@@ -68,6 +68,7 @@ gam <UserTypeEntity> copy drivefile <DriveFileEntity>
|
||||
[mergewithparent [<Boolean>]] [recursive [depth <Number>]]
|
||||
[copysubfiles [<Boolean>]] [filenamematchpattern <RegularExpression>]
|
||||
[filemimetype [not] <MimeTypeList>]
|
||||
[copysubfilesownedby any|me|others]
|
||||
[copysubfolders [<Boolean>]] [foldernamematchpattern <RegularExpression>]
|
||||
[copysubshortcuts [<Boolean>]] [shortcutnamematchpattern <RegularExpression>]
|
||||
<DriveFileCopyAttribute>*
|
||||
@@ -148,6 +149,11 @@ You can specify `<RegularExpression>` patterns that limit the items copied based
|
||||
* `foldernamematchpattern <RegularExpression>` - Only folders whose name matches `<RegularExpression>` are copied
|
||||
* `shortcutnamematchpattern <RegularExpression>` - Only shortcuts whose name matches `<RegularExpression>` are copied
|
||||
|
||||
### By default, when copying sub files, all files, regardless of ownership, are copied.
|
||||
* `copysubfilesownedby any` - All files, regardless of ownership, are copied.
|
||||
* `copysubfilesownedby me` - Only files owned by `<UserTypeEntity>` are copied.
|
||||
* `copysubfilesownedby others` - Only files not owned by `<UserTypeEntity>` are copied.
|
||||
|
||||
### Specify a new name for the file/folder
|
||||
* `newfilename <DriveFileName>` - The copied file/folder will be named `<DriveFileName>`
|
||||
* If `stripnameprefix <String>` is specified, `<String>` will be stripped from the front of `<DriveFileName>`
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
- [Display file share counts](#display-file-share-counts)
|
||||
- [Display file tree](#display-file-tree)
|
||||
- [File selection starting point for Display file tree](#file-selection-starting-point-for-display-file-tree)
|
||||
- [Display file parent tree](#display-file-parent-tree)
|
||||
- [Display file list](#display-file-list)
|
||||
- [File selection by name and entity shortcuts for Display file list](#file-selection-by-name-and-entity-shortcuts-for-display-file-list)
|
||||
- [File selection starting point for Display file list](#file-selection-starting-point-for-display-file-list)
|
||||
@@ -33,6 +34,7 @@
|
||||
|
||||
## API documentation
|
||||
* https://developers.google.com/drive/api/v3/reference/files
|
||||
* https://support.google.com/a/answer/6105699
|
||||
|
||||
## Definitions
|
||||
* [`<DriveFileEntity>`](Drive-File-Selection)
|
||||
@@ -79,6 +81,7 @@
|
||||
canaddfolderfromanotherdrive|
|
||||
canaddmydriveparent|
|
||||
canchangecopyrequireswriterpermission|
|
||||
canchangecopyrequireswriterpermissionrestriction|
|
||||
canchangedomainusersonlyrestriction|
|
||||
canchangedrivebackground|
|
||||
canchangedrivemembersonlyrestriction|
|
||||
@@ -96,11 +99,14 @@
|
||||
canmanagemembers|
|
||||
canmodifycontent|
|
||||
canmodifycontentrestriction|
|
||||
canmodifyeditorcontentrestriction|
|
||||
canmodifylabels|
|
||||
canmodifyownercontentrestriction|
|
||||
canmovechildrenoutofdrive|
|
||||
canmovechildrenoutofteamdrive|
|
||||
canmovechildrenwithindrive|
|
||||
canmovechildrenwithinteamdrive|
|
||||
canmoveitemintodrive|
|
||||
canmoveitemintoteamdrive|
|
||||
canmoveitemoutofdrive|
|
||||
canmoveitemoutofteamdrive|
|
||||
@@ -112,6 +118,7 @@
|
||||
canreadrevisions|
|
||||
canreadteamdrive|
|
||||
canremovechildren|
|
||||
canremovecontentrestriction|
|
||||
canremovemydriveparent|
|
||||
canrename|
|
||||
canrenamedrive|
|
||||
@@ -946,6 +953,40 @@ Show file tree starting at the folder named "Middle Folder" and 2 levels deeper
|
||||
```
|
||||
gam user testuser show filetree select drivefilename "Middle Folder" depth 2
|
||||
```
|
||||
## Display file parent tree
|
||||
Print the parent tree of file/folder.
|
||||
```
|
||||
gam <UserTypeEntity> print fileparenttree <DriveFileEntity> [todrive <ToDriveAttribute>*]
|
||||
[stripcrsfromname]
|
||||
```
|
||||
### Examples
|
||||
```
|
||||
# My Drive file
|
||||
$ gam user user@domain.com print fileparenttree 1tDGtnaBXc1qx_9NjBSZOUUNZ7FoRc2u6
|
||||
User: user@domain.com, Print 1 File Parent Tree
|
||||
Owner,id,name,parentId,depth,isRoot
|
||||
user@domain.com,1tDGtnaBXc1qx_9NjBSZOUUNZ7FoRc2u6,Bottom Folder,1HvAJtmQ2KZrKJhzY8aeZVScHhZ3HBJLp,4,False
|
||||
user@domain.com,1HvAJtmQ2KZrKJhzY8aeZVScHhZ3HBJLp,Middle Folder,1CVqOJJLNQtxX4QEPdpDfbkjiq1oUsxne,3,False
|
||||
user@domain.com,1CVqOJJLNQtxX4QEPdpDfbkjiq1oUsxne,TopCopy,0AHYenC8f12ALUk9PVA,2,False
|
||||
user@domain.com,0AHYenC8f12ALUk9PVA,My Drive,,1,True
|
||||
|
||||
# Shared Drive file
|
||||
$ gam user user@domain.com print fileparenttree 1kAHa7Q801KXRF1DfoofNlW05UWDzddhVP_u_L2xGfFQ
|
||||
User: user@domain.com, Print 1 File Parent Tree
|
||||
Owner,id,name,parentId,depth,isRoot
|
||||
user@domain.com,1kAHa7Q801KXRF1DfoofNlW05UWDzddhVP_u_L2xGfFQ,Middle Doc,1DShPJ6iG1TnNsgiBn-Oy1OVE2BahYlPr,4,False
|
||||
user@domain.com,1DShPJ6iG1TnNsgiBn-Oy1OVE2BahYlPr,Middle Folder,1s3g64uWfuQrpXRPf82B-bWCB5VuyrOmQ,3,False
|
||||
user@domain.com,1s3g64uWfuQrpXRPf82B-bWCB5VuyrOmQ,Top Folder,0AL5LiIe4dqxZUk9PVA,2,False
|
||||
user@domain.com,0AL5LiIe4dqxZUk9PVA,TS Shared Drive 1,,1,True
|
||||
|
||||
# Shared with Me file
|
||||
$ gam user user@domain.com print fileparenttree 1S2D97pyG1vAil4hgNnGGLD2ldCwTOzXUM9D7XbeUv0s
|
||||
User: user@domain.com, Print 1 File Parent Tree
|
||||
Owner,id,name,parentId,depth,isRoot
|
||||
user@domain.com,1S2D97pyG1vAil4hgNnGGLD2ldCwTOzXUM9D7XbeUv0s,GooGoo,0B0NlVEBUkz-hfjVudlF4VHlYYWlmOEdCUUxDaHdLdXhJTF84YWQwbmpRWmZ3Qm0wZnpHSGs,2,False
|
||||
user@domain.com,0B0NlVEBUkz-hfjVudlF4VHlYYWlmOEdCUUxDaHdLdXhJTF84YWQwbmpRWmZ3Qm0wZnpHSGs,FooBar,,1,False
|
||||
```
|
||||
|
||||
## Display file list
|
||||
Display a list of file/folder details in CSV format.
|
||||
```
|
||||
@@ -1041,6 +1082,12 @@ Use the following option to select a subset of files based on their permissions.
|
||||
* `<PermissionMatch>* [<PermissionMatchAction>]` - Use permission matching to select files
|
||||
|
||||
## File selection starting point for Display file list
|
||||
You can limit the selection for files on a specific Shared drive.
|
||||
Any query will be applied to the Shared drive.
|
||||
```
|
||||
select <SharedDriveEntity>
|
||||
```
|
||||
|
||||
You can specify a specific folder from which to select files.
|
||||
```
|
||||
select <DriveFileEntity> [selectsubquery <QueryDriveFile>]
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
* https://developers.google.com/drive/api/v3/ref-single-parent
|
||||
* https://developers.google.com/drive/api/v3/shared-drives-diffs
|
||||
* https://developers.google.com/drive/api/v3/shortcuts
|
||||
* https://support.google.com/a/answer/6105699
|
||||
* https://support.google.com/a/answer/7374057
|
||||
* https://developers.google.com/docs/api/reference/rest
|
||||
|
||||
@@ -492,7 +493,8 @@ From the Google Drive API documentation.
|
||||
By default, Google assigns the current time to the attribute `modifiedTime`; you can assign your own value
|
||||
with `modifiedtime <Time>`.
|
||||
|
||||
The option `preservefiletimes`, when used with `localfile <FileName>`, will set the `modifiedTime` attribute from the local file.
|
||||
* `preservefiletimes localfile <FileName>` - `modifiedTime` of `<DriveFileEntity>` is set to that of `localfile <FileName>`
|
||||
* `preservefiletimes` - `modifiedTime` of `<DriveFileEntity>` retains its current value
|
||||
|
||||
These are the naming rules when updating from a local file:
|
||||
* `update drivefile drivefilename "GoogleFile.csv" localfile "NewLocalFile.csv"` - Google Drive file "GoogleFile.csv" is renamed "NewLocalFile.csv"
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
- [Display a selected set of messages](#display-a-selected-set-of-messages)
|
||||
- [Choose information to display](#choose-information-to-display)
|
||||
- [Display message content](#display-message-content)
|
||||
- [Display message count](#display-message-count)
|
||||
- [Display message counts](#display-message-counts)
|
||||
- [Display label counts](#display-label-counts)
|
||||
- [Print only options](#print-only-options)
|
||||
- [Show only options](#show-only-options)
|
||||
@@ -204,6 +204,9 @@ You can also replace ` ` with `-` but it doesn't seem to be required.
|
||||
|
||||
* `query "label:Foo -Bar-"` - Select messages with label `Foo (Bar)`
|
||||
|
||||
You can have GAM do the substitutions for you with the `matchlabel <LabelName>` option.
|
||||
* `matchlabel "Foo (Bar)"` is converted to `query "label:Foo -Bar-"`
|
||||
|
||||
## Draft messages
|
||||
Add a draft message to a user's mailbox.
|
||||
```
|
||||
@@ -539,7 +542,7 @@ The `dateheaderconverttimezone [<Boolean>]>` option converts `<SMTPDateHeader>`
|
||||
* `showsize` - Display the message size
|
||||
* `showsnippet` - Display the message snippet
|
||||
|
||||
### Display message count and optionally cumulative message size
|
||||
### Display message counts
|
||||
* `countsonly` - Display the count of the number of messages
|
||||
* `showsize` - Display the cumulative message size
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
## API documentation
|
||||
* https://developers.google.com/gmail/api/reference/rest/v1/users.settings.sendAs
|
||||
* https://developers.google.com/gmail/api/v1/reference/users/settings
|
||||
* https://support.google.com/a/answer/1710338
|
||||
|
||||
## Definitions
|
||||
* [`<UserTypeEntity>`](Collections-of-Users)
|
||||
@@ -87,6 +88,8 @@ of the sendas address.
|
||||
|
||||
The `default` option sets `<EmailAddress>` as the default sendas address for the user.
|
||||
|
||||
For `treatasalias`, see: https://support.google.com/a/answer/1710338
|
||||
|
||||
You can allow users to send mail through an external SMTP server when configuring a sendas address hosted outside your email domains. You must enable
|
||||
this capability in Admin Console/Apps/Google Workspace/Gmail/Advanced settings/End User Access/Allow per-user outbound gateways.
|
||||
|
||||
@@ -145,6 +148,8 @@ gam <UserTypeEntity> signature|sig
|
||||
|
||||
The `default` option sets `<EmailAddress>` as the default sendas address for the user.
|
||||
|
||||
For `treatasalias`, see: https://support.google.com/a/answer/1710338
|
||||
|
||||
When `<UserTypeEntity>` specifies an alias, the `primary` option causes the primary
|
||||
email address signature rather than the alias signature to be set.
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
|
||||
\
|
||||
# Version and Help
|
||||
|
||||
Print the current version of Gam with details
|
||||
```
|
||||
gam version
|
||||
GAMADV-XTD3 6.66.03 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
|
||||
GAMADV-XTD3 6.66.16 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
|
||||
Ross Scroggs <ross.scroggs@gmail.com>
|
||||
Python 3.12.0 64-bit final
|
||||
MacOS Monterey 12.7 x86_64
|
||||
@@ -16,7 +16,7 @@ Time: 2023-06-02T21:10:00-07:00
|
||||
Print the current version of Gam with details and time offset information
|
||||
```
|
||||
gam version timeoffset
|
||||
GAMADV-XTD3 6.66.03 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
|
||||
GAMADV-XTD3 6.66.16 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
|
||||
Ross Scroggs <ross.scroggs@gmail.com>
|
||||
Python 3.12.0 64-bit final
|
||||
MacOS Monterey 12.7 x86_64
|
||||
@@ -28,7 +28,7 @@ Your system time differs from www.googleapis.com by less than 1 second
|
||||
Print the current version of Gam with extended details and SSL information
|
||||
```
|
||||
gam version extended
|
||||
GAMADV-XTD3 6.66.03 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
|
||||
GAMADV-XTD3 6.66.16 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
|
||||
Ross Scroggs <ross.scroggs@gmail.com>
|
||||
Python 3.12.0 64-bit final
|
||||
MacOS Monterey 12.7 x86_64
|
||||
@@ -65,7 +65,7 @@ MacOS High Sierra 10.13.6 x86_64
|
||||
Path: /Users/Admin/bin/gamadv-xtd3
|
||||
Version Check:
|
||||
Current: 5.35.08
|
||||
Latest: 6.66.03
|
||||
Latest: 6.66.16
|
||||
echo $?
|
||||
1
|
||||
```
|
||||
@@ -73,7 +73,7 @@ echo $?
|
||||
Print the current version number without details
|
||||
```
|
||||
gam version simple
|
||||
6.66.03
|
||||
6.66.16
|
||||
```
|
||||
In Linux/MacOS you can do:
|
||||
```
|
||||
@@ -83,7 +83,7 @@ echo $VER
|
||||
Print the current version of Gam and address of this Wiki
|
||||
```
|
||||
gam help
|
||||
GAM 6.66.03 - https://github.com/taers232c/GAMADV-XTD3
|
||||
GAM 6.66.16 - https://github.com/taers232c/GAMADV-XTD3
|
||||
Ross Scroggs <ross.scroggs@gmail.com>
|
||||
Python 3.12.0 64-bit final
|
||||
MacOS Monterey 12.7 x86_64
|
||||
|
||||
@@ -1457,7 +1457,7 @@ gam print alertfeedback [todrive <ToDriveAttribute>*] [alert <AlertID>] [filter
|
||||
gam create|add alias|aliases <EmailAddressEntity> user|group|target <UniqueID>|<EmailAddress>
|
||||
[verifynotinvitable]
|
||||
gam update alias|aliases <EmailAddressEntity> user|group|target <UniqueID>|<EmailAddress>
|
||||
[notargetverify]
|
||||
[notargetverify] [waitafterdelete <Integer>]
|
||||
gam delete alias|aliases [user|group|target] <EmailAddressEntity>
|
||||
gam remove aliases|nicknames <EmailAddress> user|group <EmailAddressEntity>
|
||||
gam <UserTypeEntity> delete alias|aliases
|
||||
@@ -2475,7 +2475,7 @@ gam print crostelemetry [todrive <ToDriveAttribute>*]
|
||||
|
||||
gam create chromepolicyimage <ChromePolicyImageSchemaName> <FileName>
|
||||
|
||||
gam update chromepolicy
|
||||
gam update chromepolicy [convertcrnl]
|
||||
(<SchemaName> ((<Field> <Value>)+ | <JSONData>))+
|
||||
ou|org|orgunit <OrgUnitItem> [(printerid <PrinterID>)|(appid <AppID>)]
|
||||
gam delete chromepolicy
|
||||
@@ -6006,7 +6006,9 @@ gam <UserTypeEntity> copy drivefile <DriveFileEntity>
|
||||
[summary [<Boolean>]] [showpermissionmessages [<Boolean>]]
|
||||
[<DriveFileParentAttribute>]
|
||||
[mergewithparent [<Boolean>]] [recursive [depth <Number>]]
|
||||
[copysubfiles [<Boolean>]] [filenamematchpattern <RegularExpression>] [filemimetype [not] <MimeTypeList>]
|
||||
[copysubfiles [<Boolean>]] [filenamematchpattern <RegularExpression>]
|
||||
[filemimetype [not] <MimeTypeList>]
|
||||
[copysubfilesownedby any|me|others]
|
||||
[copysubfolders [<Boolean>]] [foldernamematchpattern <RegularExpression>]
|
||||
[copysubshortcuts [<Boolean>]] [shortcutnamematchpattern <RegularExpression>]
|
||||
<DriveFileCopyAttribute>*
|
||||
@@ -6284,6 +6286,7 @@ gam <UserTypeEntity> collect orphans
|
||||
canaddfolderfromanotherdrive|
|
||||
canaddmydriveparent|
|
||||
canchangecopyrequireswriterpermission|
|
||||
canchangecopyrequireswriterpermissionrestriction|
|
||||
canchangedomainusersonlyrestriction|
|
||||
canchangedrivebackground|
|
||||
canchangedrivemembersonlyrestriction|
|
||||
@@ -6301,11 +6304,14 @@ gam <UserTypeEntity> collect orphans
|
||||
canmanagemembers|
|
||||
canmodifycontent|
|
||||
canmodifycontentrestriction|
|
||||
canmodifyeditorcontentrestriction|
|
||||
canmodifylabels|
|
||||
canmodifyownercontentrestriction|
|
||||
canmovechildrenoutofdrive|
|
||||
canmovechildrenoutofteamdrive|
|
||||
canmovechildrenwithindrive|
|
||||
canmovechildrenwithinteamdrive|
|
||||
canmoveitemintodrive|
|
||||
canmoveitemintoteamdrive|
|
||||
canmoveitemoutofdrive|
|
||||
canmoveitemoutofteamdrive|
|
||||
@@ -6317,6 +6323,7 @@ gam <UserTypeEntity> collect orphans
|
||||
canreadrevisions|
|
||||
canreadteamdrive|
|
||||
canremovechildren|
|
||||
canremovecontentrestriction|
|
||||
canremovemydriveparent|
|
||||
canrename|
|
||||
canrenamedrive|
|
||||
@@ -6608,6 +6615,9 @@ gam <UserTypeEntity> show filetree
|
||||
(orderby <DriveFileOrderByFieldName> [ascending|descending])* [delimiter <Character>]
|
||||
[stripcrsfromname]
|
||||
|
||||
gam <UserTypeEntity> print fileparenttree <DriveFileEntity> [todrive <ToDriveAttribute>*]
|
||||
[stripcrsfromname]
|
||||
|
||||
gam <UserTypeEntity> print filelist [todrive <ToDriveAttribute>*]
|
||||
[((query <QueryDriveFile>) | (fullquery <QueryDriveFile>) | <DriveFileQueryShortcut>)
|
||||
(querytime<String> <Time>)*]
|
||||
|
||||
@@ -2,6 +2,104 @@
|
||||
|
||||
Merged GAM-Team version
|
||||
|
||||
6.66.16
|
||||
|
||||
Added option `convertcrnl` to `gam update chromepolicy` to properly handle carriage returns (\r) and line feeds (\n)
|
||||
in value strings entered on the command line in the `<Field> <Value>` form.
|
||||
```
|
||||
gam update chromepolicy convertcrnl chrome.devices.DisabledDeviceReturnInstructions
|
||||
deviceDisabledMessage "Please return device to:\nSchool\n123 Main Street\nAnytown US" ou /Path/to/OU
|
||||
```
|
||||
|
||||
6.66.15
|
||||
|
||||
Added option `copysubfilesownedby any|me|others` to `gam <UserTypeEntity> copy drivefile` that allows
|
||||
specification of which source folder sub files to copy based on file ownership; the default is `any`.
|
||||
This only applies when files are being copied from a 'My Drive'.
|
||||
|
||||
6.66.14
|
||||
|
||||
Updated `gam <UserTypeEntity> modify messages` to recognize the following error:
|
||||
```
|
||||
ERROR: 400: invalid - Invalid label: SENT
|
||||
```
|
||||
|
||||
Updated `gam update alias <EmailAddressEntity> user|group|target <EmailAddress>`
|
||||
to avoid the following problem.
|
||||
```
|
||||
$ gam update alias testalias@domain.com user testuser
|
||||
User Alias: testalias@domain.com, Deleted
|
||||
User Alias: testalias@domain.com, User: testuser@domain.com, Update Failed: Duplicate, Email Address: testalias@domain.com
|
||||
```
|
||||
GAM updates an alias to point to a new target by deleting the alias and then recreating the alias pointing to the new target.
|
||||
Unfortunately, if these commands are executed back-to-back; Google generates the `Update Failed: Duplicate` error.
|
||||
Now, GAM waits 2 seconds between the delete and the insert which seems to eliminate the problem. If the problem persists,
|
||||
the option `waitafterdelete <Integer>` can be used to increase the wait time to a maximum of 10 seconds.
|
||||
|
||||
6.66.13
|
||||
|
||||
Updated functionality of option `preservefiletimes` in `gam <UserTypeEntity> update drivefile <DriveFileEntity>`.
|
||||
|
||||
* Current
|
||||
* `preservefiletimes localfile <FileName>` - `modifiedTime` of `<DriveFileEntity>` is set to that of `localfile <FileName>`
|
||||
* `preservefiletimes` - No effect
|
||||
* Updated
|
||||
* `preservefiletimes localfile <FileName>` - `modifiedTime` of `<DriveFileEntity>` is set to that of `localfile <FileName>`
|
||||
* `preservefiletimes` - `modifiedTime` of `<DriveFileEntity>` retains its current value
|
||||
|
||||
6.66.12
|
||||
|
||||
Upgraded to Python 3.12.1 where possible.
|
||||
|
||||
Updated all drive commands to handle the following error:
|
||||
```
|
||||
ERROR: 401: Active session is invalid. Error code: 4 - authError
|
||||
```
|
||||
This is due to the Drive SDK API being disabled in the user's OU.
|
||||
* See: https://support.google.com/a/answer/6105699
|
||||
|
||||
6.66.11
|
||||
|
||||
Fixed/improved handling of shortcuts in `gam <UserTypeEntity> transfer drive`.
|
||||
|
||||
6.66.10
|
||||
|
||||
Updated `gam create datatransfer` to handle the following error:
|
||||
```
|
||||
ERROR: 401: Active session is invalid. Error code: 4 - authError
|
||||
```
|
||||
|
||||
6.66.09
|
||||
|
||||
Fixed bug in `gam <UserTypeEntity> print filelist ... allfields` that caused a trap
|
||||
when `gam.cfg` contained `drive_v3_native_names = False`.
|
||||
|
||||
6.66.08
|
||||
|
||||
Added additional columns `isBase` and `baseId` to `gam <UserTypeEntity> print fileparenttree`
|
||||
to simplify processing the output in a script.
|
||||
|
||||
6.66.07
|
||||
|
||||
Fixed bug in `gam <UserTypeEntity> print diskusage` that caused a trap.
|
||||
|
||||
6.66.06
|
||||
|
||||
Added a command the print the parent tree of file/folder.
|
||||
```
|
||||
gam <UserTypeEntity> print fileparenttree <DriveFileEntity> [todrive <ToDriveAttribute>*]
|
||||
[stripcrsfromname]
|
||||
```
|
||||
* See: https://github.com/taers232c/GAMADV-XTD3/wiki/Users-Drive-Files-Display#display-file-parent-tree
|
||||
|
||||
6.66.05
|
||||
|
||||
Added column `space.name` to `gam <UserTypeEntity> print chatmembers`.
|
||||
|
||||
6.66.04
|
||||
|
||||
Updated Chat info|show|print commands to display all time fields in local time if specified in `gam.cfg`.
|
||||
|
||||
6.66.03
|
||||
|
||||
Fixed bug in `gam <UserTypeEntity> print filelist select <DriveFileEntity>` where `stripcrsfromname` was not being
|
||||
|
||||
@@ -370,7 +370,7 @@ YUBIKEY_VALUE_ERROR_RC = 85
|
||||
YUBIKEY_MULTIPLE_CONNECTED_RC = 86
|
||||
YUBIKEY_NOT_FOUND_RC = 87
|
||||
|
||||
# Multiprocessing lock
|
||||
# Multiprocessing lock
|
||||
mplock = None
|
||||
|
||||
# stdin/stdout/stderr
|
||||
@@ -2521,7 +2521,7 @@ def entityBadRequestWarning(entityValueList, errMessage, i=0, count=0):
|
||||
currentCountNL(i, count)))
|
||||
|
||||
def userSvcNotApplicableOrDriveDisabled(user, errMessage, i=0, count=0):
|
||||
if errMessage.find('Drive apps') == -1:
|
||||
if errMessage.find('Drive apps') == -1 and errMessage.find('Active session is invalid') == -1:
|
||||
entityServiceNotApplicableWarning(Ent.USER, user, i, count)
|
||||
else:
|
||||
entityActionNotPerformedWarning([Ent.USER, user], errMessage, i, count)
|
||||
@@ -5082,15 +5082,16 @@ def checkGAPIError(e, softErrors=False, retryOnHttpError=False, mapNotFound=True
|
||||
systemErrorExit(HTTP_ERROR_RC, eContent)
|
||||
if 'error' in error:
|
||||
http_status = error['error']['code']
|
||||
reason = ''
|
||||
if 'errors' in error['error'] and 'message' in error['error']['errors'][0]:
|
||||
message = error['error']['errors'][0]['message']
|
||||
status = ''
|
||||
if 'reason' in error['error']['errors'][0]:
|
||||
reason = error['error']['errors'][0]['reason']
|
||||
elif 'errors' in error['error'] and 'Unknown Error' in error['error']['message'] and 'reason' in error['error']['errors'][0]:
|
||||
message = error['error']['errors'][0]['reason']
|
||||
status = error['error'].get('status', '')
|
||||
else:
|
||||
message = error['error']['message']
|
||||
status = error['error'].get('status', '')
|
||||
status = error['error'].get('status', '')
|
||||
lmessage = message.lower() if message is not None else ''
|
||||
if http_status == 500:
|
||||
if not lmessage or status == 'UNKNOWN':
|
||||
@@ -5109,30 +5110,36 @@ def checkGAPIError(e, softErrors=False, retryOnHttpError=False, mapNotFound=True
|
||||
error = makeErrorDict(http_status, GAPI.OPERATION_NOT_SUPPORTED, message)
|
||||
elif 'failed status in update settings response' in lmessage:
|
||||
error = makeErrorDict(http_status, GAPI.INVALID_INPUT, message)
|
||||
elif status == 'INTERNAL':
|
||||
error = makeErrorDict(http_status, GAPI.INTERNAL_ERROR, message)
|
||||
elif 'cannot delete a field in use.resource.fields' in lmessage:
|
||||
error = makeErrorDict(http_status, GAPI.FIELD_IN_USE, message)
|
||||
elif status == 'INTERNAL':
|
||||
error = makeErrorDict(http_status, GAPI.INTERNAL_ERROR, message)
|
||||
elif http_status == 502:
|
||||
if 'bad gateway' in lmessage:
|
||||
error = makeErrorDict(http_status, GAPI.BAD_GATEWAY, message)
|
||||
elif http_status == 503:
|
||||
if status == 'UNAVAILABLE' or 'the service is currently unavailable' in lmessage:
|
||||
error = makeErrorDict(http_status, GAPI.SERVICE_NOT_AVAILABLE, message)
|
||||
elif message.startswith('quota exceeded for the current request'):
|
||||
if message.startswith('quota exceeded for the current request'):
|
||||
error = makeErrorDict(http_status, GAPI.QUOTA_EXCEEDED, message)
|
||||
elif status == 'UNAVAILABLE' or 'the service is currently unavailable' in lmessage:
|
||||
error = makeErrorDict(http_status, GAPI.SERVICE_NOT_AVAILABLE, message)
|
||||
elif http_status == 504:
|
||||
if 'gateway timeout' in lmessage:
|
||||
error = makeErrorDict(http_status, GAPI.GATEWAY_TIMEOUT, message)
|
||||
elif http_status == 400:
|
||||
if '@attachmentnotvisible' in lmessage:
|
||||
error = makeErrorDict(http_status, GAPI.BAD_REQUEST, message)
|
||||
elif 'does not match' in lmessage or 'invalid' in lmessage:
|
||||
error = makeErrorDict(http_status, GAPI.INVALID, message)
|
||||
elif status == 'FAILED_PRECONDITION' or 'precondition check failed' in lmessage:
|
||||
error = makeErrorDict(http_status, GAPI.FAILED_PRECONDITION, message)
|
||||
elif status == 'INVALID_ARGUMENT':
|
||||
error = makeErrorDict(http_status, GAPI.INVALID_ARGUMENT, message)
|
||||
elif 'does not match' in lmessage or 'invalid' in lmessage:
|
||||
error = makeErrorDict(http_status, GAPI.INVALID, message)
|
||||
elif http_status == 401:
|
||||
if 'active session is invalid' in lmessage and reason == 'authError':
|
||||
message += ' Drive SDK API access disabled'
|
||||
error = makeErrorDict(http_status, GAPI.AUTH_ERROR, message)
|
||||
elif status == 'PERMISSION_DENIED':
|
||||
error = makeErrorDict(http_status, GAPI.PERMISSION_DENIED, message)
|
||||
elif http_status == 403:
|
||||
if 'quota exceeded for quota metric' in lmessage:
|
||||
error = makeErrorDict(http_status, GAPI.QUOTA_EXCEEDED, message)
|
||||
@@ -7695,7 +7702,6 @@ class CSVPrintFile():
|
||||
|
||||
def MapDrive3TitlesToDrive2(self):
|
||||
_mapDrive3TitlesToDrive2(self.titlesList, API.DRIVE3_TO_DRIVE2_FILES_FIELDS_MAP)
|
||||
_mapDrive3TitlesToDrive2(self.titlesList, API.DRIVE3_TO_DRIVE2_CAPABILITIES_TITLES_MAP)
|
||||
self.titlesSet = set(self.titlesList)
|
||||
|
||||
def AddJSONTitle(self, title):
|
||||
@@ -17327,7 +17333,7 @@ ALIAS_TARGET_TYPES = ['user', 'group', 'target']
|
||||
# gam create aliases|nicknames <EmailAddressEntity> user|group|target <UniqueID>|<EmailAddress>
|
||||
# [verifynotinvitable]
|
||||
# gam update aliases|nicknames <EmailAddressEntity> user|group|target <UniqueID>|<EmailAddress>
|
||||
# [notargetverify]
|
||||
# [notargetverify] [waitafterdelete <Integer>]
|
||||
def doCreateUpdateAliases():
|
||||
def verifyAliasTargetExists():
|
||||
if targetType != 'group':
|
||||
@@ -17349,6 +17355,42 @@ def doCreateUpdateAliases():
|
||||
GAPI.badRequest, GAPI.invalid, GAPI.systemError):
|
||||
return None
|
||||
|
||||
def deleteAliasOnUpdate():
|
||||
# User alias
|
||||
if targetType != 'group':
|
||||
try:
|
||||
callGAPI(cd.users().aliases(), 'delete',
|
||||
throwReasons=[GAPI.USER_NOT_FOUND, GAPI.BAD_REQUEST, GAPI.INVALID, GAPI.FORBIDDEN, GAPI.INVALID_RESOURCE,
|
||||
GAPI.CONDITION_NOT_MET],
|
||||
userKey=aliasEmail, alias=aliasEmail)
|
||||
printEntityKVList([Ent.USER_ALIAS, aliasEmail], [Act.PerformedName(Act.DELETE)], i, count)
|
||||
time.sleep(waitAfterDelete)
|
||||
return True
|
||||
except GAPI.conditionNotMet as e:
|
||||
entityActionFailedWarning([Ent.USER_ALIAS, aliasEmail], str(e), i, count)
|
||||
return False
|
||||
except (GAPI.userNotFound, GAPI.badRequest, GAPI.invalid, GAPI.forbidden, GAPI.invalidResource):
|
||||
if targetType == 'user':
|
||||
entityUnknownWarning(Ent.USER_ALIAS, aliasEmail, i, count)
|
||||
return False
|
||||
# Group alias
|
||||
try:
|
||||
callGAPI(cd.groups().aliases(), 'delete',
|
||||
throwReasons=[GAPI.GROUP_NOT_FOUND, GAPI.BAD_REQUEST, GAPI.INVALID, GAPI.FORBIDDEN, GAPI.INVALID_RESOURCE,
|
||||
GAPI.CONDITION_NOT_MET],
|
||||
groupKey=aliasEmail, alias=aliasEmail)
|
||||
time.sleep(waitAfterDelete)
|
||||
return True
|
||||
except GAPI.conditionNotMet as e:
|
||||
entityActionFailedWarning([Ent.GROUP_ALIAS, aliasEmail], str(e), i, count)
|
||||
return False
|
||||
except GAPI.forbidden:
|
||||
entityUnknownWarning(Ent.GROUP_ALIAS, aliasEmail, i, count)
|
||||
return False
|
||||
except (GAPI.groupNotFound, GAPI.badRequest, GAPI.invalid, GAPI.invalidResource):
|
||||
entityUnknownWarning(Ent.GROUP_ALIAS, aliasEmail, i, count)
|
||||
return False
|
||||
|
||||
cd = buildGAPIObject(API.DIRECTORY)
|
||||
ci = None
|
||||
updateCmd = Act.Get() == Act.UPDATE
|
||||
@@ -17358,12 +17400,15 @@ def doCreateUpdateAliases():
|
||||
entityLists = targetEmails if isinstance(targetEmails, dict) else None
|
||||
verifyNotInvitable = False
|
||||
verifyTarget = updateCmd
|
||||
waitAfterDelete = 2
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if (not updateCmd) and myarg == 'verifynotinvitable':
|
||||
verifyNotInvitable = True
|
||||
elif updateCmd and myarg == 'notargetverify':
|
||||
verifyTarget = False
|
||||
elif updateCmd and myarg == 'waitafterdelete':
|
||||
waitAfterDelete = getInteger(minVal=2, maxVal=10)
|
||||
else:
|
||||
unknownArgumentExit()
|
||||
i = 0
|
||||
@@ -17388,30 +17433,9 @@ def doCreateUpdateAliases():
|
||||
if targetType is None:
|
||||
entityUnknownWarning(Ent.ALIAS_TARGET, targetEmail, i, count)
|
||||
continue
|
||||
if updateCmd:
|
||||
try:
|
||||
callGAPI(cd.users().aliases(), 'delete',
|
||||
throwReasons=[GAPI.USER_NOT_FOUND, GAPI.BAD_REQUEST, GAPI.INVALID, GAPI.FORBIDDEN, GAPI.INVALID_RESOURCE,
|
||||
GAPI.CONDITION_NOT_MET],
|
||||
userKey=aliasEmail, alias=aliasEmail)
|
||||
printEntityKVList([Ent.USER_ALIAS, aliasEmail], [Act.PerformedName(Act.DELETE)], i, count)
|
||||
except GAPI.conditionNotMet as e:
|
||||
entityActionFailedWarning([Ent.USER_ALIAS, aliasEmail], str(e), i, count)
|
||||
continue
|
||||
except (GAPI.userNotFound, GAPI.badRequest, GAPI.invalid, GAPI.forbidden, GAPI.invalidResource):
|
||||
try:
|
||||
callGAPI(cd.groups().aliases(), 'delete',
|
||||
throwReasons=[GAPI.GROUP_NOT_FOUND, GAPI.BAD_REQUEST, GAPI.INVALID, GAPI.FORBIDDEN, GAPI.INVALID_RESOURCE,
|
||||
GAPI.CONDITION_NOT_MET],
|
||||
groupKey=aliasEmail, alias=aliasEmail)
|
||||
except GAPI.conditionNotMet as e:
|
||||
entityActionFailedWarning([Ent.GROUP_ALIAS, aliasEmail], str(e), i, count)
|
||||
continue
|
||||
except GAPI.forbidden:
|
||||
entityUnknownWarning(Ent.GROUP_ALIAS, aliasEmail, i, count)
|
||||
continue
|
||||
except (GAPI.groupNotFound, GAPI.badRequest, GAPI.invalid, GAPI.invalidResource):
|
||||
entityUnknownWarning(Ent.ALIAS, aliasEmail, i, count)
|
||||
if updateCmd and not deleteAliasOnUpdate():
|
||||
continue
|
||||
# User alias
|
||||
if targetType != 'group':
|
||||
try:
|
||||
callGAPI(cd.users().aliases(), 'insert',
|
||||
@@ -17434,6 +17458,7 @@ def doCreateUpdateAliases():
|
||||
if targetType == 'user':
|
||||
entityUnknownWarning(Ent.ALIAS_TARGET, targetEmail, i, count)
|
||||
continue
|
||||
# Group alias
|
||||
try:
|
||||
callGAPI(cd.groups().aliases(), 'insert',
|
||||
throwReasons=[GAPI.GROUP_NOT_FOUND, GAPI.USER_NOT_FOUND, GAPI.BAD_REQUEST,
|
||||
@@ -24578,6 +24603,8 @@ def doPrintShowBrowsers():
|
||||
csvPF.SetSortTitles(['deviceId'])
|
||||
csvPF.writeCSVfile('Browsers')
|
||||
|
||||
BROWSER_TOKEN_TIME_OBJECTS = {'createTime', 'expireTime', 'revokeTime'}
|
||||
|
||||
def _showBrowserToken(browser, FJQC, i=0, count=0):
|
||||
if FJQC.formatJSON:
|
||||
printLine(json.dumps(cleanJSON(browser), ensure_ascii=False, sort_keys=True))
|
||||
@@ -24633,8 +24660,6 @@ def doRevokeBrowserToken():
|
||||
except GAPI.forbidden:
|
||||
accessErrorExit(None)
|
||||
|
||||
BROWSER_TOKEN_TIME_OBJECTS = {'createTime', 'expireTime', 'revokeTime'}
|
||||
|
||||
BROWSER_TOKEN_FIELDS_CHOICE_MAP = {
|
||||
'createtime': 'createTime',
|
||||
'creatorid': 'creatorId',
|
||||
@@ -24788,14 +24813,17 @@ def _cleanChatSpace(space):
|
||||
space.pop('type', None)
|
||||
space.pop('threaded', None)
|
||||
|
||||
CHAT_SPACE_TIME_OBJECTS = {'createTime'}
|
||||
|
||||
def _showChatSpace(space, FJQC, i=0, count=0):
|
||||
_cleanChatSpace(space)
|
||||
if FJQC.formatJSON:
|
||||
printLine(json.dumps(cleanJSON(space), ensure_ascii=False, sort_keys=True))
|
||||
printLine(json.dumps(cleanJSON(space, timeObjects=CHAT_SPACE_TIME_OBJECTS),
|
||||
ensure_ascii=False, sort_keys=True))
|
||||
return
|
||||
printEntity([Ent.CHAT_SPACE, space['name']], i, count)
|
||||
Ind.Increment()
|
||||
showJSON(None, space)
|
||||
showJSON(None, space, timeObjects=CHAT_SPACE_TIME_OBJECTS)
|
||||
Ind.Decrement()
|
||||
|
||||
def getChatSpaceParameters(myarg, body, typeChoicesMap):
|
||||
@@ -25059,7 +25087,7 @@ CHAT_PAGE_SIZE = 1000
|
||||
def printShowChatSpaces(users):
|
||||
def _printChatSpace(user, space):
|
||||
_cleanChatSpace(space)
|
||||
row = flattenJSON(space)
|
||||
row = flattenJSON(space, timeObjects=CHAT_SPACE_TIME_OBJECTS)
|
||||
if user is not None:
|
||||
row['User'] = user
|
||||
if not FJQC.formatJSON:
|
||||
@@ -25067,7 +25095,7 @@ def printShowChatSpaces(users):
|
||||
elif csvPF.CheckRowTitles(row):
|
||||
row = {'User': user} if user is not None else {}
|
||||
row.update({'name': space['name'],
|
||||
'JSON': json.dumps(cleanJSON(space),
|
||||
'JSON': json.dumps(cleanJSON(space, timeObjects=CHAT_SPACE_TIME_OBJECTS),
|
||||
ensure_ascii=False, sort_keys=True)})
|
||||
csvPF.WriteRowNoFilter(row)
|
||||
|
||||
@@ -25160,16 +25188,18 @@ def createChatMember(users):
|
||||
entityPerformActionNumItems(kvList, jcount, entityType, i, count)
|
||||
if jcount == 0:
|
||||
return
|
||||
kvList.extend([entityType, ''])
|
||||
Ind.Increment()
|
||||
j = 0
|
||||
for body in members:
|
||||
j += 1
|
||||
kvList[-1] = body[field]['name']
|
||||
try:
|
||||
member = callGAPI(chat.spaces().members(), 'create',
|
||||
bailOnInternalError=True,
|
||||
throwReasons=[GAPI.NOT_FOUND, GAPI.INVALID_ARGUMENT, GAPI.PERMISSION_DENIED, GAPI.INTERNAL_ERROR],
|
||||
throwReasons=[GAPI.ALREADY_EXISTS, GAPI.NOT_FOUND, GAPI.INVALID, GAPI.INVALID_ARGUMENT, GAPI.PERMISSION_DENIED, GAPI.INTERNAL_ERROR],
|
||||
parent=parent, body=body)
|
||||
if role != 'ROLE_MEMBER':
|
||||
if role != 'ROLE_MEMBER' and entityType == Ent.CHAT_MANAGER_USER:
|
||||
member = callGAPI(chat.spaces().members(), 'patch',
|
||||
bailOnInternalError=True,
|
||||
throwReasons=[GAPI.NOT_FOUND, GAPI.INVALID_ARGUMENT, GAPI.PERMISSION_DENIED, GAPI.INTERNAL_ERROR],
|
||||
@@ -25184,7 +25214,7 @@ def createChatMember(users):
|
||||
Ind.Decrement()
|
||||
else:
|
||||
writeStdout(f'{member["name"]}\n')
|
||||
except (GAPI.notFound, GAPI.invalidArgument, GAPI.permissionDenied, GAPI.internalError) as e:
|
||||
except (GAPI.alreadyExists, GAPI.notFound, GAPI.invalid, GAPI.invalidArgument, GAPI.permissionDenied, GAPI.internalError) as e:
|
||||
entityActionFailedWarning(kvList, str(e))
|
||||
Ind.Decrement()
|
||||
|
||||
@@ -25221,6 +25251,7 @@ def createChatMember(users):
|
||||
missingArgumentExit('space')
|
||||
if not userList and not groupList:
|
||||
missingArgumentExit('user|members|group|groups')
|
||||
userEntityType = Ent.CHAT_MEMBER_USER if role == 'ROLE_MEMBER' else Ent.CHAT_MANAGER_USER
|
||||
userMembers = []
|
||||
for user in userList:
|
||||
name = normalizeEmailAddressOrUID(user)
|
||||
@@ -25232,13 +25263,15 @@ def createChatMember(users):
|
||||
i, count, users = getEntityArgument(users)
|
||||
for user in users:
|
||||
i += 1
|
||||
user, chat, kvList = buildChatServiceObject(API.CHAT_MEMBERSHIPS, user, i, count, [Ent.CHAT_SPACE, parent, Ent.CHAT_MEMBER, ''])
|
||||
user, chat, kvList = buildChatServiceObject(API.CHAT_MEMBERSHIPS, user, i, count, [Ent.CHAT_SPACE, parent])
|
||||
if not chat:
|
||||
continue
|
||||
Ind.Increment()
|
||||
if userMembers:
|
||||
addMembers(userMembers, 'member', Ent.USER, i, count)
|
||||
addMembers(userMembers, 'member', userEntityType, i, count)
|
||||
if groupMembers:
|
||||
addMembers(groupMembers, 'groupMember', Ent.GROUP, i, count)
|
||||
addMembers(groupMembers, 'groupMember', Ent.CHAT_MEMBER_GROUP, i, count)
|
||||
Ind.Decrement()
|
||||
|
||||
def _deleteChatMembers(chat, kvList, jcount, memberNames, i, count):
|
||||
j = 0
|
||||
@@ -25314,7 +25347,7 @@ def deleteUpdateChatMember(users):
|
||||
i, count, users = getEntityArgument(users)
|
||||
for user in users:
|
||||
i += 1
|
||||
user, chat, kvList = buildChatServiceObject(API.CHAT_MEMBERSHIPS, user, i, count)
|
||||
user, chat, kvList = buildChatServiceObject(API.CHAT_MEMBERSHIPS, user, i, count, [Ent.CHAT_SPACE, parent] if parent is not None else None)
|
||||
if not chat:
|
||||
continue
|
||||
jcount = len(memberNames)
|
||||
@@ -25351,12 +25384,12 @@ CHAT_SYNC_PREVIEW_TITLES = ['space', 'member', 'role', 'action', 'message']
|
||||
# [preview [actioncsv]]
|
||||
# (users <UserTypeEntity>)* (groups <GroupEntity>)*
|
||||
def syncChatMembers(users):
|
||||
def _previewAction(members, jcount, action):
|
||||
def _previewAction(members, entityType, jcount, action):
|
||||
Ind.Increment()
|
||||
j = 0
|
||||
for member in members:
|
||||
j += 1
|
||||
entityActionPerformed([Ent.CHAT_SPACE, parent, Ent.CHAT_MEMBER, member, Ent.ROLE, role], j, jcount)
|
||||
entityActionPerformed([Ent.CHAT_SPACE, parent, entityType, member, Ent.ROLE, role], j, jcount)
|
||||
Ind.Decrement()
|
||||
if csvPF:
|
||||
for member in members:
|
||||
@@ -25364,11 +25397,11 @@ def syncChatMembers(users):
|
||||
|
||||
def addMembers(memberNames, members, entityType, i, count):
|
||||
jcount = len(memberNames)
|
||||
entityPerformActionNumItems(kvList, jcount, Ent.CHAT_MEMBER, i, count)
|
||||
entityPerformActionNumItems(kvList, jcount, entityType, i, count)
|
||||
if jcount == 0:
|
||||
return
|
||||
if preview:
|
||||
_previewAction(memberNames, jcount, Act.REMOVE)
|
||||
_previewAction(memberNames, entityType, jcount, Act.REMOVE)
|
||||
return
|
||||
kvList.extend([entityType, ''])
|
||||
Ind.Increment()
|
||||
@@ -25380,26 +25413,26 @@ def syncChatMembers(users):
|
||||
try:
|
||||
callGAPI(chat.spaces().members(), 'create',
|
||||
bailOnInternalError=True,
|
||||
throwReasons=[GAPI.NOT_FOUND, GAPI.INVALID_ARGUMENT, GAPI.PERMISSION_DENIED, GAPI.INTERNAL_ERROR],
|
||||
throwReasons=[GAPI.ALREADY_EXISTS, GAPI.NOT_FOUND, GAPI.INVALID, GAPI.INVALID_ARGUMENT, GAPI.PERMISSION_DENIED, GAPI.INTERNAL_ERROR],
|
||||
parent=parent, body=body)
|
||||
if role != 'ROLE_MEMBER':
|
||||
if role != 'ROLE_MEMBER' and entityType == Ent.CHAT_MANAGER_USER:
|
||||
callGAPI(chat.spaces().members(), 'patch',
|
||||
bailOnInternalError=True,
|
||||
throwReasons=[GAPI.NOT_FOUND, GAPI.INVALID_ARGUMENT, GAPI.PERMISSION_DENIED, GAPI.INTERNAL_ERROR],
|
||||
name=memberName, updateMask='role', body={'role': role})
|
||||
entityActionPerformed(kvList, j, jcount)
|
||||
except (GAPI.notFound, GAPI.invalidArgument, GAPI.permissionDenied, GAPI.internalError) as e:
|
||||
except (GAPI.alreadyExists, GAPI.notFound, GAPI.invalid, GAPI.invalidArgument, GAPI.permissionDenied, GAPI.internalError) as e:
|
||||
entityActionFailedWarning(kvList, str(e), j, jcount)
|
||||
Ind.Decrement()
|
||||
del kvList[-2:]
|
||||
|
||||
def deleteMembers(memberNames, entityType, i, count):
|
||||
jcount = len(memberNames)
|
||||
entityPerformActionNumItems(kvList, jcount, Ent.CHAT_MEMBER, i, count)
|
||||
entityPerformActionNumItems(kvList, jcount, entityType, i, count)
|
||||
if jcount == 0:
|
||||
return
|
||||
if preview:
|
||||
_previewAction(memberNames, jcount, Act.ADD)
|
||||
_previewAction(memberNames, entityType, jcount, Act.ADD)
|
||||
return
|
||||
kvList.extend([entityType, ''])
|
||||
Ind.Increment()
|
||||
@@ -25408,7 +25441,6 @@ def syncChatMembers(users):
|
||||
del kvList[-2:]
|
||||
|
||||
cd = buildGAPIObject(API.DIRECTORY)
|
||||
kwargs = {}
|
||||
parent = None
|
||||
role = CHAT_MEMBER_ROLE_MAP['member']
|
||||
mtype = CHAT_MEMBER_TYPE_MAP['human']
|
||||
@@ -25447,6 +25479,7 @@ def syncChatMembers(users):
|
||||
unknownArgumentExit()
|
||||
if not parent:
|
||||
missingArgumentExit('space')
|
||||
userEntityType = Ent.CHAT_MEMBER_USER if role == 'ROLE_MEMBER' else Ent.CHAT_MANAGER_USER
|
||||
userMembers = {}
|
||||
syncUsersSet = set()
|
||||
for user in userList:
|
||||
@@ -25461,12 +25494,11 @@ def syncChatMembers(users):
|
||||
memberName = f'{parent}/members/{name}'
|
||||
groupMembers[memberName] = {'groupMember': {'name': f'groups/{name}'}}
|
||||
syncGroupsSet.add(memberName)
|
||||
kwargs['filter'] = f'member.type = "{mtype}" AND role = "{role}"'
|
||||
qfilter = f'{Ent.Singular(Ent.CHAT_SPACE)}: {parent}, {kwargs["filter"]}'
|
||||
qfilter = f'{Ent.Singular(Ent.CHAT_SPACE)}: {parent}'
|
||||
i, count, users = getEntityArgument(users)
|
||||
for user in users:
|
||||
i += 1
|
||||
user, chat, kvList = buildChatServiceObject(API.CHAT_MEMBERSHIPS, user, i, count)
|
||||
user, chat, kvList = buildChatServiceObject(API.CHAT_MEMBERSHIPS, user, i, count, [Ent.CHAT_SPACE, parent])
|
||||
if not chat:
|
||||
continue
|
||||
currentUsersSet = set()
|
||||
@@ -25475,12 +25507,14 @@ def syncChatMembers(users):
|
||||
members = callGAPIpages(chat.spaces().members(), 'list', 'memberships',
|
||||
pageMessage=_getChatPageMessage(Ent.CHAT_MEMBER, user, i, count, qfilter),
|
||||
throwReasons=[GAPI.NOT_FOUND, GAPI.INVALID_ARGUMENT, GAPI.PERMISSION_DENIED],
|
||||
pageSize=CHAT_PAGE_SIZE, parent=parent, **kwargs)
|
||||
pageSize=CHAT_PAGE_SIZE, parent=parent, showGroups=groupsSpecified)
|
||||
for member in members:
|
||||
_getChatMemberEmail(cd, member)
|
||||
if 'member' in member:
|
||||
currentUsersSet.add(f"{parent}/members/{member['member']['email']}")
|
||||
if member['member']['type'] == mtype and member['role'] == role:
|
||||
_getChatMemberEmail(cd, member)
|
||||
currentUsersSet.add(f"{parent}/members/{member['member']['email']}")
|
||||
elif 'groupMember' in member:
|
||||
_getChatMemberEmail(cd, member)
|
||||
currentGroupsSet.add(f"{parent}/members/{member['groupMember']['email']}")
|
||||
except (GAPI.notFound, GAPI.invalidArgument, GAPI.permissionDenied) as e:
|
||||
exitIfChatNotConfigured(chat, kvList, str(e), i, count)
|
||||
@@ -25488,15 +25522,15 @@ def syncChatMembers(users):
|
||||
if syncOperation != 'addonly':
|
||||
Act.Set([Act.REMOVE, Act.REMOVE_PREVIEW][preview])
|
||||
if usersSpecified:
|
||||
deleteMembers(currentUsersSet-syncUsersSet, Ent.USER, i, count)
|
||||
deleteMembers(currentUsersSet-syncUsersSet, userEntityType, i, count)
|
||||
if groupsSpecified:
|
||||
deleteMembers(currentGroupsSet-syncGroupsSet, Ent.GROUP, i, count)
|
||||
deleteMembers(currentGroupsSet-syncGroupsSet, Ent.CHAT_MEMBER_GROUP, i, count)
|
||||
if syncOperation != 'removeonly':
|
||||
Act.Set([Act.ADD, Act.ADD_PREVIEW][preview])
|
||||
if usersSpecified:
|
||||
addMembers(syncUsersSet-currentUsersSet, userMembers, Ent.USER, i, count)
|
||||
addMembers(syncUsersSet-currentUsersSet, userMembers, userEntityType, i, count)
|
||||
if groupsSpecified:
|
||||
addMembers(syncGroupsSet-currentGroupsSet, groupMembers, Ent.GROUP, i, count)
|
||||
addMembers(syncGroupsSet-currentGroupsSet, groupMembers, Ent.CHAT_MEMBER_GROUP, i, count)
|
||||
if csvPF:
|
||||
csvPF.writeCSVfile('Chat Member Updates')
|
||||
|
||||
@@ -25554,17 +25588,18 @@ def printShowChatMembers(users):
|
||||
row = flattenJSON(member, timeObjects=CHAT_MEMBER_TIME_OBJECTS)
|
||||
if user is not None:
|
||||
row['User'] = user
|
||||
row['space.name'] = parent
|
||||
if not FJQC.formatJSON:
|
||||
csvPF.WriteRowTitles(row)
|
||||
elif csvPF.CheckRowTitles(row):
|
||||
row = {'User': user} if user is not None else {}
|
||||
row = {'User': user, 'space.name': parent} if user is not None else {'space.name': parent}
|
||||
row.update({'name': member['name'],
|
||||
'JSON': json.dumps(cleanJSON(member, timeObjects=CHAT_MEMBER_TIME_OBJECTS),
|
||||
ensure_ascii=False, sort_keys=True)})
|
||||
csvPF.WriteRowNoFilter(row)
|
||||
|
||||
cd = buildGAPIObject(API.DIRECTORY)
|
||||
csvPF = CSVPrintFile(['User', 'name'] if not isinstance(users, list) else ['name']) if Act.csvFormat() else None
|
||||
csvPF = CSVPrintFile(['User', 'space.name', 'name'] if not isinstance(users, list) else ['space.name', 'name']) if Act.csvFormat() else None
|
||||
FJQC = FormatJSONQuoteChar(csvPF)
|
||||
kwargs = {}
|
||||
parent = None
|
||||
@@ -25773,7 +25808,7 @@ def doDeleteChatMessage():
|
||||
def _cleanChatMessage(message):
|
||||
message.pop('cards', None)
|
||||
|
||||
CHAT_MESSAGE_TIME_OBJECTS = {'createTime'}
|
||||
CHAT_MESSAGE_TIME_OBJECTS = {'createTime', 'deleteTime', 'lastUpdateTime'}
|
||||
|
||||
def _showChatMessage(message, FJQC, i=0, count=0):
|
||||
_cleanChatMessage(message)
|
||||
@@ -25844,7 +25879,7 @@ def printShowChatMessages(users):
|
||||
csvPF.WriteRowNoFilter(row)
|
||||
|
||||
cd = buildGAPIObject(API.DIRECTORY)
|
||||
csvPF = CSVPrintFile(['User', 'name'] if not isinstance(users, list) else ['name']) if Act.csvFormat() else None
|
||||
csvPF = CSVPrintFile(['User', 'space.name', 'name'] if not isinstance(users, list) else ['space.name', 'name']) if Act.csvFormat() else None
|
||||
FJQC = FormatJSONQuoteChar(csvPF)
|
||||
parent = pfilter = None
|
||||
showDeleted = False
|
||||
@@ -26116,7 +26151,7 @@ CHROME_SCHEMA_TYPE_MESSAGE = {
|
||||
CHROME_TARGET_VERSION_CHANNEL_MINUS_PATTERN = re.compile(r'^([a-z]+)-(\d+)$')
|
||||
CHROME_TARGET_VERSION_PATTERN = re.compile(r'^(\d{1,4}\.){1,4}$')
|
||||
|
||||
# gam update chromepolicy
|
||||
# gam update chromepolicy [convertcrnl]
|
||||
# (<SchemaName> ((<Field> <Value>)+ | <JSONData>))+
|
||||
# ou|orgunit <OrgUnitItem> [(printerid <PrinterID>)|(appid <AppID>)]
|
||||
def doUpdateChromePolicy():
|
||||
@@ -26137,6 +26172,7 @@ def doUpdateChromePolicy():
|
||||
app_id = channelMap = orgUnit = printer_id = None
|
||||
body = {'requests': []}
|
||||
schemaNameList = []
|
||||
convertCRsNLs = False
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if myarg in {'ou', 'org', 'orgunit'}:
|
||||
@@ -26145,6 +26181,8 @@ def doUpdateChromePolicy():
|
||||
printer_id = getString(Cmd.OB_PRINTER_ID)
|
||||
elif myarg == 'appid':
|
||||
app_id = getString(Cmd.OB_APP_ID)
|
||||
elif myarg == 'convertcrnl':
|
||||
convertCRsNLs = True
|
||||
else:
|
||||
schemaName, schema = simplifyChromeSchema(_getChromePolicySchema(cp, Cmd.Previous(), '*'))
|
||||
body['requests'].append({'policyValue': {'policySchema': schemaName, 'value': {}},
|
||||
@@ -26183,11 +26221,11 @@ def doUpdateChromePolicy():
|
||||
value = field['value']
|
||||
if vtype in ['TYPE_INT64', 'TYPE_INT32', 'TYPE_UINT64']:
|
||||
value = int(value)
|
||||
elif vtype in ['TYPE_BOOL']:
|
||||
elif vtype == 'TYPE_BOOL':
|
||||
pass
|
||||
elif vtype in ['TYPE_ENUM']:
|
||||
elif vtype == 'TYPE_ENUM':
|
||||
value = f"{schema['settings'][lowerField]['enum_prefix']}{value}"
|
||||
elif vtype in ['TYPE_LIST']:
|
||||
elif vtype == 'TYPE_LIST':
|
||||
value = value.split(',') if value else []
|
||||
if myarg == 'chrome.users.chromebrowserupdates' and casedField == 'targetVersionPrefixSetting':
|
||||
mg = CHROME_TARGET_VERSION_CHANNEL_MINUS_PATTERN.match(value)
|
||||
@@ -26237,7 +26275,7 @@ def doUpdateChromePolicy():
|
||||
Cmd.Backup()
|
||||
invalidArgumentExit(integerLimits(None, None))
|
||||
value = int(value)
|
||||
elif vtype in ['TYPE_BOOL']:
|
||||
elif vtype == 'TYPE_BOOL':
|
||||
value = value.lower()
|
||||
if value in TRUE_VALUES:
|
||||
value = True
|
||||
@@ -26245,7 +26283,7 @@ def doUpdateChromePolicy():
|
||||
value = False
|
||||
else:
|
||||
invalidChoiceExit(value, TRUE_FALSE, True)
|
||||
elif vtype in ['TYPE_ENUM']:
|
||||
elif vtype == 'TYPE_ENUM':
|
||||
value = value.upper()
|
||||
prefix = schema['settings'][field]['enum_prefix']
|
||||
enum_values = schema['settings'][field]['enums']
|
||||
@@ -26255,8 +26293,10 @@ def doUpdateChromePolicy():
|
||||
pass
|
||||
else:
|
||||
invalidChoiceExit(value, enum_values, True)
|
||||
elif vtype in ['TYPE_LIST']:
|
||||
elif vtype == 'TYPE_LIST':
|
||||
value = value.split(',') if value else []
|
||||
elif vtype == 'TYPE_STRING' and convertCRsNLs:
|
||||
value = unescapeCRsNLs(value)
|
||||
if myarg == 'chrome.users.chromebrowserupdates' and casedField == 'targetVersionPrefixSetting':
|
||||
mg = CHROME_TARGET_VERSION_CHANNEL_MINUS_PATTERN.match(value)
|
||||
if mg:
|
||||
@@ -50975,6 +51015,7 @@ DRIVE_CAPABILITIES_SUBFIELDS_CHOICE_MAP = {
|
||||
'canaddfolderfromanotherdrive': 'canAddFolderFromAnotherDrive',
|
||||
'canaddmydriveparent': 'canAddMyDriveParent',
|
||||
'canchangecopyrequireswriterpermission': 'canChangeCopyRequiresWriterPermission',
|
||||
'canchangecopyrequireswriterpermissionrestriction': 'canChangeCopyRequiresWriterPermissionRestriction',
|
||||
'canchangedomainusersonlyrestriction': 'canChangeDomainUsersOnlyRestriction',
|
||||
'canchangedrivebackground': 'canChangeDriveBackground',
|
||||
'canchangedrivemembersonlyrestriction': 'canChangeDriveMembersOnlyRestriction',
|
||||
@@ -50992,22 +51033,26 @@ DRIVE_CAPABILITIES_SUBFIELDS_CHOICE_MAP = {
|
||||
'canmanagemembers': 'canManageMembers',
|
||||
'canmodifycontent': 'canModifyContent',
|
||||
'canmodifycontentrestriction': 'canModifyContentRestriction',
|
||||
'canmodifyeditorcontentrestriction': 'canModifyEditorContentRestriction',
|
||||
'canmodifylabels': 'canModifyLabels',
|
||||
'canmodifyownercontentrestriction': 'canModifyOwnerContentRestriction',
|
||||
'canmovechildrenoutofdrive': 'canMoveChildrenOutOfDrive',
|
||||
'canmovechildrenoutofteamdrive': 'canMoveChildrenOutOfDrive',
|
||||
'canmovechildrenwithindrive': 'canMoveChildrenWithinDrive',
|
||||
'canmovechildrenwithinteamdrive': 'canMoveChildrenWithinDrive',
|
||||
'canmoveitemintoteamdrive': 'canMoveItemOutOfDrive',
|
||||
'canmoveitemintodrive': 'canMoveItemIntoDrive',
|
||||
'canmoveitemintoteamdrive': 'canMoveItemIntoDrive',
|
||||
'canmoveitemoutofdrive': 'canMoveItemOutOfDrive',
|
||||
'canmoveitemoutofteamdrive': 'canMoveItemOutOfDrive',
|
||||
'canmoveitemwithindrive': 'canMoveItemWithinDrive',
|
||||
'canmoveitemwithinteamdrive': 'canMoveItemWithinDrive',
|
||||
'canmoveteamdriveitem': ['canMoveItemOutOfDrive', 'canMoveItemWithinDrive'],
|
||||
'canmoveteamdriveitem': 'canMoveTeamDriveItem',
|
||||
'canreaddrive': 'canReadDrive',
|
||||
'canreadlabels': 'canReadLabels',
|
||||
'canreadrevisions': 'canReadRevisions',
|
||||
'canreadteamdrive': 'canReadDrive',
|
||||
'canremovechildren': 'canRemoveChildren',
|
||||
'canremovecontentrestriction': 'canRemoveContentRestriction',
|
||||
'canremovemydriveparent': 'canRemoveMyDriveParent',
|
||||
'canrename': 'canRename',
|
||||
'canrenamedrive': 'canRenameDrive',
|
||||
@@ -53336,6 +53381,76 @@ def printShowFilePaths(users):
|
||||
if csvPF:
|
||||
csvPF.writeCSVfile('Drive File Paths')
|
||||
|
||||
# gam <UserTypeEntity> print fileparenttree <DriveFileEntity> [todrive <ToDriveAttribute>*]
|
||||
# [stripcrsfromname]
|
||||
def printFileParentTree(users):
|
||||
fileNameTitle = 'title' if not GC.Values[GC.DRIVE_V3_NATIVE_NAMES] else 'name'
|
||||
csvPF = CSVPrintFile(['Owner', 'isBase', 'baseId', 'id', fileNameTitle, 'parentId', 'depth', 'isRoot'], 'sortall')
|
||||
fileIdEntity = getDriveFileEntity()
|
||||
stripCRsFromName = False
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if myarg == 'todrive':
|
||||
csvPF.GetTodriveParameters()
|
||||
elif myarg == 'stripcrsfromname':
|
||||
stripCRsFromName = True
|
||||
else:
|
||||
unknownArgumentExit()
|
||||
i, count, users = getEntityArgument(users)
|
||||
for user in users:
|
||||
i += 1
|
||||
user, drive, jcount = _validateUserGetFileIDs(user, i, count, fileIdEntity, entityType=Ent.FILE_PARENT_TREE)
|
||||
if jcount == 0:
|
||||
continue
|
||||
try:
|
||||
rootId = callGAPI(drive.files(), 'get',
|
||||
throwReasons=GAPI.DRIVE_USER_THROW_REASONS,
|
||||
fileId=ROOT, fields='id')['id']
|
||||
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
|
||||
userSvcNotApplicableOrDriveDisabled(user, str(e), i, count)
|
||||
continue
|
||||
j = 0
|
||||
for fileId in fileIdEntity['list']:
|
||||
j += 1
|
||||
fileList = []
|
||||
baseId = fileId
|
||||
while True:
|
||||
try:
|
||||
result = callGAPI(drive.files(), 'get',
|
||||
throwReasons=GAPI.DRIVE_GET_THROW_REASONS,
|
||||
fileId=fileId, fields='id,name,mimeType,parents,driveId', supportsAllDrives=True)
|
||||
if stripCRsFromName:
|
||||
result['name'] = _stripControlCharsFromName(result['name'])
|
||||
result['isRoot'] = False
|
||||
if not result.get('parents', []):
|
||||
if fileId == rootId:
|
||||
result['isRoot'] = True
|
||||
else:
|
||||
driveId = result.get('driveId')
|
||||
if driveId:
|
||||
if result['mimeType'] == MIMETYPE_GA_FOLDER and result['name'] == TEAM_DRIVE:
|
||||
result['name'] = _getSharedDriveNameFromId(drive, driveId)
|
||||
result['isRoot'] = True
|
||||
result['parents'] = ['']
|
||||
fileList.append(result)
|
||||
break
|
||||
fileList.append(result)
|
||||
fileId = result['parents'][0]
|
||||
except GAPI.fileNotFound:
|
||||
entityActionFailedWarning([Ent.USER, user, Ent.DRIVE_FILE_OR_FOLDER_ID, fileId], Msg.DOES_NOT_EXIST, j, jcount)
|
||||
break
|
||||
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
|
||||
userSvcNotApplicableOrDriveDisabled(user, str(e), i, count)
|
||||
break
|
||||
kcount = len(fileList)
|
||||
isBase = True
|
||||
for result in fileList:
|
||||
csvPF.WriteRow({'Owner': user, 'isBase': isBase, 'baseId': baseId, 'id': result['id'], fileNameTitle: result['name'],
|
||||
'parentId': result['parents'][0], 'depth': kcount, 'isRoot': result['isRoot']})
|
||||
isBase = False
|
||||
kcount -= 1
|
||||
csvPF.writeCSVfile('Drive File Parent Tree')
|
||||
|
||||
# gam <UserTypeEntity> print filecounts [todrive <ToDriveAttribute>*]
|
||||
# [((query <QueryDriveFile>) | (fullquery <QueryDriveFile>) | <DriveFileQueryShortcut>) (querytime<String> <Time>)*]
|
||||
# [corpora <CorporaAttribute>]
|
||||
@@ -53588,6 +53703,7 @@ def printDiskUsage(users):
|
||||
if stripCRsFromName:
|
||||
childEntryInfo['name'] = _stripControlCharsFromName(childEntryInfo['name'])
|
||||
childEntryInfo['path'] = fileEntry['path']+pathDelimiter+childEntryInfo['name']
|
||||
childEntryInfo.pop(sizeField, None)
|
||||
foldersList.append(childEntryInfo)
|
||||
_getChildDriveFolderInfo(drive, childEntryInfo, user, i, count)
|
||||
fileEntry['totalFileCount'] += childEntryInfo['totalFileCount']
|
||||
@@ -53710,6 +53826,7 @@ def printDiskUsage(users):
|
||||
topFolder.pop('ownedByMe', None)
|
||||
topFolder.pop('parents', None)
|
||||
topFolder.update(zeroFolderInfo)
|
||||
topFolder.pop(sizeField, None)
|
||||
foldersList.append(topFolder)
|
||||
_getChildDriveFolderInfo(drive, topFolder, user, i, count)
|
||||
except GAPI.fileNotFound:
|
||||
@@ -54738,6 +54855,7 @@ def updateDriveFile(users):
|
||||
addSheetEntity = None
|
||||
updateSheetEntity = None
|
||||
clearFilter = False
|
||||
preserveModifiedTime = False
|
||||
encoding = GC.Values[GC.CHARSET]
|
||||
columnDelimiter = GC.Values[GC.CSV_INPUT_COLUMN_DELIMITER]
|
||||
returnIdLink = None
|
||||
@@ -54790,6 +54908,8 @@ def updateDriveFile(users):
|
||||
addSheetEntity = updateSheetEntity = None
|
||||
media_body = getMediaBody(parameters)
|
||||
body['mimeType'] = parameters[DFA_LOCALMIMETYPE]
|
||||
elif operation == 'update' and parameters[DFA_PRESERVE_FILE_TIMES]:
|
||||
preserveModifiedTime = True
|
||||
i, count, users = getEntityArgument(users)
|
||||
for user in users:
|
||||
i += 1
|
||||
@@ -54811,15 +54931,17 @@ def updateDriveFile(users):
|
||||
try:
|
||||
addParents = addParentsBase[:]
|
||||
removeParents = removeParentsBase[:]
|
||||
if newParents or (not newName and parameters[DFA_REPLACEFILENAME]):
|
||||
if newParents or (not newName and parameters[DFA_REPLACEFILENAME]) or preserveModifiedTime:
|
||||
result = callGAPI(drive.files(), 'get',
|
||||
throwReasons=GAPI.DRIVE_GET_THROW_REASONS,
|
||||
fileId=fileId, fields='name,parents', supportsAllDrives=True)
|
||||
fileId=fileId, fields='name,parents,modifiedTime', supportsAllDrives=True)
|
||||
if newParents:
|
||||
addParents.extend(newParents)
|
||||
removeParents.extend(result.get('parents', []))
|
||||
if not newName and parameters[DFA_REPLACEFILENAME]:
|
||||
body['name'] = processFilenameReplacements(result['name'], parameters[DFA_REPLACEFILENAME])
|
||||
if preserveModifiedTime:
|
||||
body['modifiedTime'] = result['modifiedTime']
|
||||
if newName:
|
||||
if parameters[DFA_REPLACEFILENAME]:
|
||||
body['name'] = processFilenameReplacements(newName, parameters[DFA_REPLACEFILENAME])
|
||||
@@ -55146,6 +55268,7 @@ def initCopyMoveOptions(copyCmd):
|
||||
'shortcutNameMatchPattern': None,
|
||||
'fileMimeTypes': set(),
|
||||
'notMimeTypes': False,
|
||||
'copySubFilesOwnedBy': None,
|
||||
}
|
||||
|
||||
DUPLICATE_FILE_CHOICES = {
|
||||
@@ -55261,6 +55384,8 @@ def getCopyMoveOptions(myarg, copyMoveOptions):
|
||||
copyMoveOptions['notMimeTypes'] = checkArgumentPresent('not')
|
||||
for mimeType in getString(Cmd.OB_MIMETYPE_LIST).lower().replace(',', ' ').split():
|
||||
copyMoveOptions['fileMimeTypes'].add(validateMimeType(mimeType))
|
||||
elif myarg == 'copysubfilesownedby':
|
||||
copyMoveOptions['copySubFilesOwnedBy'] = getChoice(SHOW_OWNED_BY_CHOICE_MAP, mapChoice=True)
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
@@ -55983,7 +56108,7 @@ def copyDriveFile(users):
|
||||
entityActionFailedWarning(kvList+[Ent.DRIVE_FILE_SHORTCUT, childName], str(e), k, kcount)
|
||||
_incrStatistic(statistics, STAT_FILE_FAILED)
|
||||
|
||||
def _checkChildCopyAllowed(childMimeType, childName):
|
||||
def _checkChildCopyAllowed(childMimeType, childName, child):
|
||||
if childMimeType == MIMETYPE_GA_FOLDER:
|
||||
if not copyMoveOptions['copySubFolders']:
|
||||
return False
|
||||
@@ -55995,6 +56120,9 @@ def copyDriveFile(users):
|
||||
else:
|
||||
if not copyMoveOptions['copySubFiles']:
|
||||
return False
|
||||
if copyMoveOptions['copySubFilesOwnedBy'] is not None:
|
||||
if child.get('driveId', None) is None and child.get('ownedByMe', False) != copyMoveOptions['copySubFilesOwnedBy']:
|
||||
return False
|
||||
if copyMoveOptions['fileMimeTypes']:
|
||||
if not copyMoveOptions['notMimeTypes']:
|
||||
if childMimeType not in copyMoveOptions['fileMimeTypes']:
|
||||
@@ -56026,7 +56154,7 @@ def copyDriveFile(users):
|
||||
q=WITH_PARENTS.format(folderId),
|
||||
orderBy='folder desc,name,modifiedTime desc',
|
||||
fields='nextPageToken,files(id,name,parents,appProperties,capabilities,contentHints,copyRequiresWriterPermission,'\
|
||||
'description,folderColorRgb,mimeType,modifiedTime,properties,starred,driveId,trashed,viewedByMeTime,writersCanShare,'\
|
||||
'description,folderColorRgb,mimeType,modifiedTime,ownedByMe,properties,starred,driveId,trashed,viewedByMeTime,writersCanShare,'\
|
||||
'shortcutDetails(targetId,targetMimeType))',
|
||||
pageSize=GC.Values[GC.DRIVE_MAX_RESULTS], **sourceSearchArgs)
|
||||
kcount = len(sourceChildren)
|
||||
@@ -56052,10 +56180,11 @@ def copyDriveFile(users):
|
||||
if childId in copiedTargetFiles: # Don't recopy file/folder copied into a sub-folder
|
||||
continue
|
||||
kvList = [Ent.USER, user, _getEntityMimeType(child), childNameId]
|
||||
if not _checkChildCopyAllowed(childMimeType, childName):
|
||||
if not _checkChildCopyAllowed(childMimeType, childName, child):
|
||||
if not suppressNotSelectedMessages:
|
||||
entityActionNotPerformedWarning(kvList, Msg.NOT_SELECTED, k, kcount)
|
||||
continue
|
||||
child.pop('ownedByMe', None)
|
||||
trashed = child.pop('trashed', False)
|
||||
if (childId == newFolderId) or (excludeTrashed and trashed):
|
||||
entityActionNotPerformedWarning(kvList,
|
||||
@@ -57838,17 +57967,55 @@ def transferDrive(users):
|
||||
entityModifierItemValueListActionPerformed(kvList, Act.MODIFIER_IN,
|
||||
[Ent.DRIVE_FOLDER, newParentId, targetEntityType, f"{childName}({result['id']})"],
|
||||
j, jcount)
|
||||
Act.Set(action)
|
||||
except (GAPI.forbidden, GAPI.insufficientFilePermissions, GAPI.insufficientParentPermissions, GAPI.invalid, GAPI.badRequest,
|
||||
GAPI.fileNotFound, GAPI.unknownError, GAPI.storageQuotaExceeded, GAPI.teamDrivesSharingRestrictionNotAllowed,
|
||||
GAPI.teamDriveHierarchyTooDeep, GAPI.shortcutTargetInvalid) as e:
|
||||
entityActionFailedWarning(kvList+[Ent.DRIVE_FILE_SHORTCUT, childName], str(e), j, jcount)
|
||||
Act.Set(action)
|
||||
|
||||
# Recreate source user shortcut in target user
|
||||
def _transferShortcut(j, jcount, childEntryInfo, childId, childName, newParentId):
|
||||
entityType = Ent.DRIVE_FOLDER_SHORTCUT if childEntryInfo['shortcutDetails']['targetMimeType'] == MIMETYPE_GA_FOLDER else Ent.DRIVE_FILE_SHORTCUT
|
||||
kvList = [Ent.USER, sourceUser, entityType, f'{childName}({childId})']
|
||||
action = Act.Get()
|
||||
body = {'name': childName, 'mimeType': MIMETYPE_GA_SHORTCUT,
|
||||
'parents': [newParentId], 'shortcutDetails': {'targetId': childEntryInfo['shortcutDetails']['targetId']}}
|
||||
Act.Set(Act.RECREATE)
|
||||
try:
|
||||
result = callGAPI(targetDrive.files(), 'create',
|
||||
throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.FORBIDDEN, GAPI.INSUFFICIENT_PERMISSIONS, GAPI.INSUFFICIENT_PARENT_PERMISSIONS,
|
||||
GAPI.INVALID, GAPI.BAD_REQUEST, GAPI.FILE_NOT_FOUND, GAPI.UNKNOWN_ERROR,
|
||||
GAPI.STORAGE_QUOTA_EXCEEDED, GAPI.TEAMDRIVES_SHARING_RESTRICTION_NOT_ALLOWED,
|
||||
GAPI.TEAMDRIVE_HIERARCHY_TOO_DEEP, GAPI.SHORTCUT_TARGET_INVALID],
|
||||
body=body, fields='id', supportsAllDrives=True)
|
||||
shortcutId = result['id']
|
||||
entityModifierNewValueItemValueListActionPerformed(kvList, Act.MODIFIER_IN, None, [Ent.USER, targetUser,
|
||||
Ent.DRIVE_FOLDER, newParentId, entityType, f"{shortcutId})"],
|
||||
j, jcount)
|
||||
except (GAPI.forbidden, GAPI.insufficientFilePermissions, GAPI.insufficientParentPermissions, GAPI.invalid, GAPI.badRequest,
|
||||
GAPI.fileNotFound, GAPI.unknownError, GAPI.storageQuotaExceeded, GAPI.teamDrivesSharingRestrictionNotAllowed,
|
||||
GAPI.teamDriveHierarchyTooDeep, GAPI.shortcutTargetInvalid) as e:
|
||||
entityActionFailedWarning(kvList+[Ent.DRIVE_FILE_SHORTCUT, childName], str(e), j, jcount)
|
||||
Act.Set(action)
|
||||
return
|
||||
if ownerRetainRoleBody['role'] == 'none':
|
||||
Act.Set(Act.DELETE_SHORTCUT)
|
||||
kvList = [Ent.USER, sourceUser, entityType, f'{childName}({childId})']
|
||||
try:
|
||||
callGAPI(sourceDrive.files(), 'delete',
|
||||
throwReasons=GAPI.DRIVE_ACCESS_THROW_REASONS+[GAPI.FILE_NEVER_WRITABLE],
|
||||
fileId=childId, supportsAllDrives=True)
|
||||
entityActionPerformed(kvList, j, jcount)
|
||||
except (GAPI.fileNotFound, GAPI.forbidden, GAPI.internalError, GAPI.insufficientFilePermissions, GAPI.unknownError, GAPI.fileNeverWritable) as e:
|
||||
entityActionFailedWarning(kvList, str(e), j, jcount)
|
||||
Act.Set(action)
|
||||
|
||||
def _transferFile(childEntry, i, count, j, jcount, atSelectTop):
|
||||
childEntryInfo = childEntry['info']
|
||||
childFileId = childEntryInfo['id']
|
||||
childFileName = childEntryInfo['name']
|
||||
childFileType = _getEntityMimeType(childEntryInfo)
|
||||
# Owned files
|
||||
if childEntryInfo['ownedByMe']:
|
||||
childEntryInfo['sourcePermission'] = {'role': 'owner'}
|
||||
for permission in childEntryInfo.get('permissions', []):
|
||||
@@ -57894,20 +58061,25 @@ def transferDrive(users):
|
||||
GAPI.PERMISSION_NOT_FOUND, GAPI.SHARING_RATE_LIMIT_EXCEEDED],
|
||||
fileId=childFileId, permissionId=targetPermissionId,
|
||||
transferOwnership=True, body={'role': 'owner'}, fields='')
|
||||
if removeSourceParents:
|
||||
op = 'Remove Source Parents'
|
||||
callGAPI(sourceDrive.files(), 'update',
|
||||
throwReasons=GAPI.DRIVE_ACCESS_THROW_REASONS, retryReasons=[GAPI.BAD_REQUEST, GAPI.FILE_NOT_FOUND], triesLimit=3,
|
||||
fileId=childFileId, removeParents=','.join(removeSourceParents), fields='')
|
||||
actionUser = targetUser
|
||||
if addTargetParent or removeTargetParents:
|
||||
op = 'Add/Remove Target Parents'
|
||||
callGAPI(targetDrive.files(), 'update',
|
||||
throwReasons=GAPI.DRIVE_ACCESS_THROW_REASONS+[GAPI.INSUFFICIENT_PARENT_PERMISSIONS],
|
||||
retryReasons=[GAPI.BAD_REQUEST, GAPI.FILE_NOT_FOUND], triesLimit=3,
|
||||
fileId=childFileId,
|
||||
addParents=addTargetParent, removeParents=','.join(removeTargetParents), fields='')
|
||||
entityModifierNewValueItemValueListActionPerformed([Ent.USER, sourceUser, childFileType, childFileName], Act.MODIFIER_TO, None, [Ent.USER, targetUser], j, jcount)
|
||||
if removeSourceParents:
|
||||
op = 'Remove Source Parents'
|
||||
callGAPI(sourceDrive.files(), 'update',
|
||||
throwReasons=GAPI.DRIVE_ACCESS_THROW_REASONS, retryReasons=[GAPI.BAD_REQUEST, GAPI.FILE_NOT_FOUND], triesLimit=3,
|
||||
fileId=childFileId, removeParents=','.join(removeSourceParents), fields='')
|
||||
actionUser = targetUser
|
||||
if addTargetParent or removeTargetParents:
|
||||
op = 'Add/Remove Target Parents'
|
||||
callGAPI(targetDrive.files(), 'update',
|
||||
throwReasons=GAPI.DRIVE_ACCESS_THROW_REASONS+[GAPI.INSUFFICIENT_PARENT_PERMISSIONS],
|
||||
retryReasons=[GAPI.BAD_REQUEST, GAPI.FILE_NOT_FOUND], triesLimit=3,
|
||||
fileId=childFileId,
|
||||
addParents=addTargetParent, removeParents=','.join(removeTargetParents), fields='')
|
||||
entityModifierNewValueItemValueListActionPerformed([Ent.USER, sourceUser, childFileType, childFileName], Act.MODIFIER_TO, None, [Ent.USER, targetUser], j, jcount)
|
||||
else:
|
||||
if topSourceId in childParents:
|
||||
_transferShortcut(j, jcount, childEntryInfo, childFileId, childFileName, addTargetParent)
|
||||
else:
|
||||
entityModifierNewValueItemValueListActionPerformed([Ent.USER, sourceUser, childFileType, childFileName], Act.MODIFIER_TO, None, [Ent.USER, targetUser], j, jcount)
|
||||
except (GAPI.fileNotFound, GAPI.forbidden, GAPI.internalError, GAPI.unknownError,
|
||||
GAPI.badRequest, GAPI.sharingRateLimitExceeded, GAPI.insufficientParentPermissions) as e:
|
||||
entityActionFailedWarning([Ent.USER, actionUser, childFileType, childFileName], f'{op}: {str(e)}', j, jcount)
|
||||
@@ -57922,6 +58094,7 @@ def transferDrive(users):
|
||||
entityActionFailedWarning([Ent.USER, actionUser, childFileType, childFileName], Ent.TypeNameMessage(Ent.PERMISSION_ID, targetPermissionId, str(e)), j, jcount)
|
||||
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
|
||||
userSvcNotApplicableOrDriveDisabled(actionUser, str(e), i, count)
|
||||
# Non-owned files
|
||||
else:
|
||||
Act.Set(Act.PROCESS)
|
||||
for permission in childEntryInfo.get('permissions', []):
|
||||
@@ -58064,6 +58237,10 @@ def transferDrive(users):
|
||||
childFileId = childEntryInfo['id']
|
||||
childFileName = childEntryInfo['name']
|
||||
childFileType = _getEntityMimeType(childEntryInfo)
|
||||
if childEntryInfo['mimeType'] == MIMETYPE_GA_SHORTCUT:
|
||||
if showRetentionMessages:
|
||||
entityActionNotPerformedWarning([Ent.USER, sourceUser, childFileType, childFileName, Ent.ROLE, ownerRetainRoleBody['role']], Msg.NOT_APPROPRIATE, j, jcount)
|
||||
return
|
||||
if childEntryInfo['ownedByMe']:
|
||||
try:
|
||||
if ownerRetainRoleBody['role'] != 'none':
|
||||
@@ -58212,7 +58389,7 @@ def transferDrive(users):
|
||||
throwReasons=GAPI.DRIVE_USER_THROW_REASONS,
|
||||
retryReasons=[GAPI.UNKNOWN_ERROR],
|
||||
orderBy=OBY.orderBy, q=WITH_PARENTS.format(fileId),
|
||||
fields='nextPageToken,files(id,name,parents,mimeType,ownedByMe,trashed,owners(emailAddress,permissionId),permissions(id,role))',
|
||||
fields='nextPageToken,files(id,name,parents,mimeType,ownedByMe,trashed,owners(emailAddress,permissionId),permissions(id,role),shortcutDetails)',
|
||||
pageSize=GC.Values[GC.DRIVE_MAX_RESULTS])
|
||||
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
|
||||
userSvcNotApplicableOrDriveDisabled(sourceUser, str(e), i, count)
|
||||
@@ -58431,6 +58608,7 @@ def transferDrive(users):
|
||||
return
|
||||
Ind.Increment()
|
||||
if buildTree:
|
||||
topSourceId = sourceRootId
|
||||
parentIdMap = {sourceRootId: targetIds[TARGET_PARENT_ID]}
|
||||
printGettingAllEntityItemsForWhom(Ent.DRIVE_FILE_OR_FOLDER, Ent.TypeName(Ent.SOURCE_USER, user), i, count)
|
||||
feed = callGAPIpages(sourceDrive.files(), 'list', 'files',
|
||||
@@ -58438,7 +58616,7 @@ def transferDrive(users):
|
||||
throwReasons=GAPI.DRIVE_USER_THROW_REASONS,
|
||||
retryReasons=[GAPI.UNKNOWN_ERROR],
|
||||
orderBy=OBY.orderBy, q=NON_TRASHED,
|
||||
fields='nextPageToken,files(id,name,parents,mimeType,ownedByMe,owners(emailAddress,permissionId),permissions(id,role))',
|
||||
fields='nextPageToken,files(id,name,parents,mimeType,ownedByMe,owners(emailAddress,permissionId),permissions(id,role),shortcutDetails)',
|
||||
pageSize=GC.Values[GC.DRIVE_MAX_RESULTS])
|
||||
fileTree = buildFileTree(feed, sourceDrive)
|
||||
del feed
|
||||
@@ -58465,7 +58643,7 @@ def transferDrive(users):
|
||||
fileEntry = callGAPI(sourceDrive.files(), 'get',
|
||||
throwReasons=GAPI.DRIVE_GET_THROW_REASONS,
|
||||
fileId=fileId,
|
||||
fields='id,name,parents,mimeType,ownedByMe,trashed,owners(emailAddress,permissionId),permissions(id,role)')
|
||||
fields='id,name,parents,mimeType,ownedByMe,trashed,owners(emailAddress,permissionId),permissions(id,role),shortcutDetails')
|
||||
entityType = _getEntityMimeType(fileEntry)
|
||||
if fileId in skipFileIdEntity['list']:
|
||||
entityActionNotPerformedWarning([Ent.USER, sourceUser, entityType, f'{fileEntry["name"]} ({fileId})'],
|
||||
@@ -58473,9 +58651,11 @@ def transferDrive(users):
|
||||
continue
|
||||
entityPerformActionItemValue([Ent.USER, sourceUser], entityType, f'{fileEntry["name"]} ({fileId})', j, jcount)
|
||||
if not mergeWithTarget:
|
||||
topSourceId = None
|
||||
for parentId in fileEntry.get('parents', []):
|
||||
parentIdMap[parentId] = targetIds[TARGET_PARENT_ID]
|
||||
else:
|
||||
topSourceId = fileId
|
||||
parentIdMap[fileId] = targetIds[TARGET_PARENT_ID]
|
||||
_identifyDriveFileAndChildren(fileEntry, i, count)
|
||||
filesTransferred = set()
|
||||
@@ -65341,20 +65521,27 @@ def _processMessagesThreads(users, entityType):
|
||||
bcount = min(jcount-mcount, GC.Values[GC.MESSAGE_BATCH_SIZE])
|
||||
while bcount > 0:
|
||||
body['ids'] = messageIds[mcount:mcount+bcount]
|
||||
idsCount = min(5, bcount)
|
||||
idsList = ','.join(body['ids'][0:idsCount])
|
||||
if bcount > 5:
|
||||
idsList += ',...'
|
||||
try:
|
||||
callGAPI(gmail.users().messages(), function,
|
||||
throwReasons=GAPI.GMAIL_THROW_REASONS+[GAPI.INVALID_MESSAGE_ID, GAPI.FAILED_PRECONDITION],
|
||||
throwReasons=GAPI.GMAIL_THROW_REASONS+[GAPI.INVALID_MESSAGE_ID, GAPI.INVALID, GAPI.FAILED_PRECONDITION],
|
||||
userId='me', body=body)
|
||||
for messageId in body['ids']:
|
||||
mcount += 1
|
||||
entityActionPerformed([Ent.USER, user, entityType, messageId], mcount, jcount)
|
||||
except (GAPI.serviceNotAvailable, GAPI.badRequest):
|
||||
mcount += bcount
|
||||
except GAPI.invalid as e:
|
||||
entityActionFailedWarning([Ent.USER, user, entityType, idsList], f'{str(e)} ({mcount+1}-{mcount+bcount}/{jcount})')
|
||||
mcount += bcount
|
||||
except GAPI.invalidMessageId:
|
||||
entityActionFailedWarning([Ent.USER, user, entityType, Msg.BATCH], f'{Msg.INVALID_MESSAGE_ID} ({mcount+1}-{mcount+bcount}/{jcount})')
|
||||
entityActionFailedWarning([Ent.USER, user, entityType, idsList], f'{Msg.INVALID_MESSAGE_ID} ({mcount+1}-{mcount+bcount}/{jcount})')
|
||||
mcount += bcount
|
||||
except GAPI.failedPrecondition:
|
||||
entityActionFailedWarning([Ent.USER, user, entityType, Msg.BATCH], f'{Msg.FAILED_PRECONDITION} ({mcount+1}-{mcount+bcount}/{jcount})')
|
||||
entityActionFailedWarning([Ent.USER, user, entityType, idsList], f'{Msg.FAILED_PRECONDITION} ({mcount+1}-{mcount+bcount}/{jcount})')
|
||||
mcount += bcount
|
||||
bcount = min(jcount-mcount, GC.Values[GC.MESSAGE_BATCH_SIZE])
|
||||
|
||||
@@ -66752,7 +66939,8 @@ def printShowMessagesThreads(users, entityType):
|
||||
if senderMatchPattern:
|
||||
row['Sender'] = sender
|
||||
if not show_size:
|
||||
labelsMap.pop('size', None)
|
||||
for label in labelsMap.values():
|
||||
label.pop('size', None)
|
||||
csvPF.WriteRowTitles(flattenJSON({'Labels': sorted(iter(labelsMap.values()), key=lambda k: k['name'])}, flattened=row))
|
||||
elif not senderMatchPattern:
|
||||
if not csvPF:
|
||||
@@ -71848,6 +72036,7 @@ USER_COMMANDS_WITH_OBJECTS = {
|
||||
Cmd.ARG_NOTE: printShowNotes,
|
||||
Cmd.ARG_OTHERCONTACT: printShowUserPeopleOtherContacts,
|
||||
Cmd.ARG_OUTOFOFFICE: printShowOutOfOffice,
|
||||
Cmd.ARG_FILEPARENTTREE: printFileParentTree,
|
||||
Cmd.ARG_PEOPLECONTACT: printShowUserPeopleContacts,
|
||||
Cmd.ARG_PEOPLECONTACTGROUP: printShowUserPeopleContactGroups,
|
||||
Cmd.ARG_PEOPLEPROFILE: printShowUserPeopleProfiles,
|
||||
|
||||
@@ -49,6 +49,7 @@ class GamAction():
|
||||
DELETE = 'dele'
|
||||
DELETE_EMPTY = 'delm'
|
||||
DELETE_PREVIEW = 'delp'
|
||||
DELETE_SHORTCUT = 'desc'
|
||||
DEPROVISION = 'depr'
|
||||
DISABLE = 'disa'
|
||||
DOWNLOAD = 'down'
|
||||
@@ -160,11 +161,12 @@ class GamAction():
|
||||
COPY_MERGE: ['Copied(Merge)', 'Copy(Merge)'],
|
||||
CREATE: ['Created', 'Create'],
|
||||
CREATE_PREVIEW: ['Created (Preview)', 'Create (Preview)'],
|
||||
CREATE_SHORTCUT: ['Created Shortcut', 'Create SHORTCUT'],
|
||||
CREATE_SHORTCUT: ['Created Shortcut', 'Create Shortcut'],
|
||||
DEDUP: ['Duplicates Deleted', 'Delete Duplicates'],
|
||||
DELETE: ['Deleted', 'Delete'],
|
||||
DELETE_EMPTY: ['Deleted', 'Delete Empty'],
|
||||
DELETE_PREVIEW: ['Deleted (Preview)', 'Delete (Preview)'],
|
||||
DELETE_SHORTCUT: ['Deleted Shortcut', 'Delete Shortcut'],
|
||||
DEPROVISION: ['Deprovisioned', 'Deprovision'],
|
||||
DISABLE: ['Disabled', 'Disable'],
|
||||
DOWNLOAD: ['Downloaded', 'Download'],
|
||||
|
||||
@@ -691,14 +691,6 @@ DRIVE3_TO_DRIVE2_CAPABILITIES_NAMES_MAP = {
|
||||
'canChangeViewersCanCopyContent': 'canChangeRestrictedDownload',
|
||||
}
|
||||
|
||||
DRIVE3_TO_DRIVE2_CAPABILITIES_TITLES_MAP = {
|
||||
'capabilities.canComment': 'canComment',
|
||||
'capabilities.canReadRevisions': 'canReadRevisions',
|
||||
'capabilities.canCopy': 'copyable',
|
||||
'capabilities.canEdit': 'editable',
|
||||
'capabilities.canShare': 'shareable',
|
||||
}
|
||||
|
||||
DRIVE3_TO_DRIVE2_FILES_FIELDS_MAP = {
|
||||
'allowFileDiscovery': 'withLink',
|
||||
'createdTime': 'createdDate',
|
||||
|
||||
@@ -577,6 +577,7 @@ class GamCLArgs():
|
||||
ARG_FILEDRIVELABELS = 'filedrivelabels'
|
||||
ARG_FILEINFO = 'fileinfo'
|
||||
ARG_FILELIST = 'filelist'
|
||||
ARG_FILEPARENTTREE = 'fileparenttree'
|
||||
ARG_FILEPATH = 'filepath'
|
||||
ARG_FILEPATHS = 'filepaths'
|
||||
ARG_FILEREVISION = 'filerevision'
|
||||
|
||||
@@ -84,7 +84,10 @@ class GamEntity():
|
||||
CHANNEL_PRODUCT = 'chpr'
|
||||
CHANNEL_SKU = 'chsk'
|
||||
CHAT_BOT = 'chbo'
|
||||
CHAT_MANAGER_USER = 'chgu'
|
||||
CHAT_MEMBER = 'chme'
|
||||
CHAT_MEMBER_GROUP = 'chmg'
|
||||
CHAT_MEMBER_USER = 'chmu'
|
||||
CHAT_MESSAGE = 'chms'
|
||||
CHAT_MESSAGE_ID = 'chmi'
|
||||
CHAT_SPACE = 'chsp'
|
||||
@@ -207,6 +210,7 @@ class GamEntity():
|
||||
FEATURE = 'feat'
|
||||
FIELD = 'fiel'
|
||||
FILE = 'file'
|
||||
FILE_PARENT_TREE = 'fptr'
|
||||
FILTER = 'filt'
|
||||
FORM = 'form'
|
||||
FORM_RESPONSE = 'frmr'
|
||||
@@ -413,9 +417,12 @@ class GamEntity():
|
||||
CHANNEL_PRODUCT: ['Channel Products', 'Channel Product'],
|
||||
CHANNEL_SKU: ['Channel SKUs', 'Channel SKU'],
|
||||
CHAT_BOT: ['Chat BOTs', 'Chat BOT'],
|
||||
CHAT_MANAGER_USER: ['Chat User Managers', 'Chat User Manager'],
|
||||
CHAT_MESSAGE: ['Chat Messages', 'Chat Message'],
|
||||
CHAT_MESSAGE_ID: ['Chat Message IDs', 'Chat Message ID'],
|
||||
CHAT_MEMBER: ['Chat Members', 'Chat Member'],
|
||||
CHAT_MEMBER_GROUP: ['Chat Group Members', 'Chat Group Member'],
|
||||
CHAT_MEMBER_USER: ['Chat User Members', 'Chat User Member'],
|
||||
CHAT_SPACE: ['Chat Spaces', 'Chat Space'],
|
||||
CHAT_THREAD: ['Chat Threads', 'Chat Thread'],
|
||||
CHROME_APP: ['Chrome Applications', 'Chrome Application'],
|
||||
@@ -536,6 +543,7 @@ class GamEntity():
|
||||
FEATURE: ['Features', 'Feature'],
|
||||
FIELD: ['Fields', 'Field'],
|
||||
FILE: ['Files', 'File'],
|
||||
FILE_PARENT_TREE: ['File Parent Trees', 'File Parent Tree'],
|
||||
FILTER: ['Filters', 'Filter'],
|
||||
FORM: ['Forms', 'Form'],
|
||||
FORM_RESPONSE: ['Form Responses', 'Form Response'],
|
||||
|
||||
@@ -338,6 +338,7 @@ NOT_A_MEMBER = 'Not a member'
|
||||
NOT_ACTIVE = 'Not Active'
|
||||
NOT_ALLOWED = 'Not Allowed'
|
||||
NOT_AN_ENTITY = 'Not a {0}'
|
||||
NOT_APPROPRIATE = 'Not Appropriate'
|
||||
NOT_COMPATIBLE = 'Not Compatible'
|
||||
NOT_COPYABLE = 'Not Copyable'
|
||||
NOT_COPYABLE_INTO_ITSELF = 'Not copyable into itself'
|
||||
|
||||
Reference in New Issue
Block a user