print threads, print filelist countsonly update/fixes
Some checks are pending
Build and test GAM / build (Win64, build, 10, VC-WIN64A, windows-2022) (push) Waiting to run
Build and test GAM / build (aarch64, build, 3, linux-aarch64, ubuntu-24.04-arm) (push) Waiting to run
Build and test GAM / build (aarch64, build, 4, linux-aarch64, ubuntu-22.04-arm) (push) Waiting to run
Build and test GAM / build (aarch64, build, 6, linux-aarch64, ubuntu-22.04-arm, yes) (push) Waiting to run
Build and test GAM / build (aarch64, build, 8, darwin64-arm64, macos-14) (push) Waiting to run
Build and test GAM / build (aarch64, build, 9, darwin64-arm64, macos-15) (push) Waiting to run
Build and test GAM / build (x86_64, build, 1, linux-x86_64, ubuntu-22.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 2, linux-x86_64, ubuntu-24.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 5, linux-x86_64, ubuntu-22.04, yes) (push) Waiting to run
Build and test GAM / build (x86_64, build, 7, darwin64-x86_64, macos-13) (push) Waiting to run
Build and test GAM / build (x86_64, test, 11, ubuntu-24.04, 3.10) (push) Waiting to run
Build and test GAM / build (x86_64, test, 12, ubuntu-24.04, 3.11) (push) Waiting to run
Build and test GAM / build (x86_64, test, 13, ubuntu-24.04, 3.12) (push) Waiting to run
Build and test GAM / merge (push) Blocked by required conditions
Build and test GAM / publish (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Check for Google Root CA Updates / check-apis (push) Waiting to run

This commit is contained in:
Ross Scroggs
2025-02-11 21:05:34 -08:00
parent 7bdee0927a
commit 76aa1d1cdd
4 changed files with 122 additions and 61 deletions

View File

@@ -7606,6 +7606,7 @@ gam <UserTypeEntity> show messages|threads
[countsonly|positivecountsonly] [useronly] [countsonly|positivecountsonly] [useronly]
[headers all|<SMTPHeaderList>] [dateheaderformat iso|rfc2822|<String>] [dateheaderconverttimezone [<Boolean>]] [headers all|<SMTPHeaderList>] [dateheaderformat iso|rfc2822|<String>] [dateheaderconverttimezone [<Boolean>]]
[showlabels] [delimiter <Character>] [showbody] [showhtml] [showdate] [showsize] [showsnippet] [showlabels] [delimiter <Character>] [showbody] [showhtml] [showdate] [showsize] [showsnippet]
[maxmessagesperthread <Number>]
[[attachmentnamepattern <RegularExpression>] [[attachmentnamepattern <RegularExpression>]
[showattachments [noshowtextplain]] [showattachments [noshowtextplain]]
[saveattachments [targetfolder <FilePath>] [overwrite [<Boolean>]]] [saveattachments [targetfolder <FilePath>] [overwrite [<Boolean>]]]
@@ -7617,6 +7618,7 @@ gam <UserTypeEntity> print messages|threads [todrive <ToDriveAttribute>*]
[countsonly|positivecountsonly] [useronly] [countsonly|positivecountsonly] [useronly]
[headers all|<SMTPHeaderList>] [dateheaderformat iso|rfc2822|<String> [dateheaderconverttimezone [<Boolean>]]] [headers all|<SMTPHeaderList>] [dateheaderformat iso|rfc2822|<String> [dateheaderconverttimezone [<Boolean>]]]
[showlabels] [delimiter <Character>] [showbody] [showhtml] [showdate] [showsize] [showsnippet] [showlabels] [delimiter <Character>] [showbody] [showhtml] [showdate] [showsize] [showsnippet]
[maxmessagesperthread <Number>]
[convertcrnl] [delimiter <Character>] [convertcrnl] [delimiter <Character>]
[[attachmentnamepattern <RegularExpression>] [[attachmentnamepattern <RegularExpression>]
[showattachments [noshowtextplain]]] [showattachments [noshowtextplain]]]

View File

@@ -1,3 +1,32 @@
7.03.09
Added option `maxmessagesperthread <Number>` to `gam <UserTypeEntity> print|show threads`
that limits the number of messages displayed per thread. The default is 0, there is no limit.
For example, this can be used if you anly want to see the first message of each thread.
```
gam user user@domain.com print|show threads maxmessagesperthread 1
```
Fixed bug in `gam <UserTypeEntity> print filelist countsonly` where extraneous columns
were displayed.
Fixed bug in `gam <UserTypeEntity> print filelist countsonly showsize` where sizes were
all shown as 0 unless`sizefield size` was specified.
7.03.08
Improved pip install.
Yubikey as optional should now be working also. So:
pip install --upgrade gam7
skips Yubikey.
To install with yubikey support (assuming you have installed the necessary swig and libpcsclite-dev packages already) run:
pip install --upgrade gam7[yubikey]
7.03.07 7.03.07
Updated `gam create vaultexport` to include `corpus gemini`. Updated `gam create vaultexport` to include `corpus gemini`.

View File

@@ -25,7 +25,7 @@ https://github.com/GAM-team/GAM/wiki
""" """
__author__ = 'GAM Team <google-apps-manager@googlegroups.com>' __author__ = 'GAM Team <google-apps-manager@googlegroups.com>'
__version__ = '7.03.08' __version__ = '7.03.09'
__license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)' __license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
#pylint: disable=wrong-import-position #pylint: disable=wrong-import-position
@@ -8495,14 +8495,18 @@ class CSVPrintFile():
self.AddTitle(title) self.AddTitle(title)
return RowFilterMatch(row, self.titlesList, self.rowFilter, self.rowFilterMode, self.rowDropFilter, self.rowDropFilterMode) return RowFilterMatch(row, self.titlesList, self.rowFilter, self.rowFilterMode, self.rowDropFilter, self.rowDropFilterMode)
def UpdateMimeTypeCounts(self, row, mimeTypeInfo): def UpdateMimeTypeCounts(self, row, mimeTypeInfo, sizeField):
saveList = self.titlesList[:]
saveSet = set(self.titlesSet)
for title in row: for title in row:
if title not in self.titlesSet: if title not in self.titlesSet:
self.AddTitle(title) self.AddTitle(title)
if RowFilterMatch(row, self.titlesList, self.rowFilter, self.rowFilterMode, self.rowDropFilter, self.rowDropFilterMode): if RowFilterMatch(row, self.titlesList, self.rowFilter, self.rowFilterMode, self.rowDropFilter, self.rowDropFilterMode):
mimeTypeInfo.setdefault(row['mimeType'], {'count': 0, 'size': 0}) mimeTypeInfo.setdefault(row['mimeType'], {'count': 0, 'size': 0})
mimeTypeInfo[row['mimeType']]['count'] += 1 mimeTypeInfo[row['mimeType']]['count'] += 1
mimeTypeInfo[row['mimeType']]['size'] += int(row.get('size', '0')) mimeTypeInfo[row['mimeType']]['size'] += int(row.get(sizeField, '0'))
self.titlesList = saveList[:]
self.titlesSet = set(saveSet)
def SetZeroBlankMimeTypeCounts(self, zeroBlankMimeTypeCounts): def SetZeroBlankMimeTypeCounts(self, zeroBlankMimeTypeCounts):
self.zeroBlankMimeTypeCounts = zeroBlankMimeTypeCounts self.zeroBlankMimeTypeCounts = zeroBlankMimeTypeCounts
@@ -41661,23 +41665,22 @@ def printShowUserVaultHolds(entityList):
else: else:
printKeyValueList(['Total Holds', totalHolds]) printKeyValueList(['Total Holds', totalHolds])
def _cleanVaultQuery(query, cd, drive): def _cleanVaultQuery(query, cd):
if 'query' in query: if 'query' in query:
if cd is not None: if cd is not None:
if 'orgUnitInfo' in query['query']: if 'orgUnitInfo' in query['query']:
query['query']['orgUnitInfo']['orgUnitPath'] = convertOrgUnitIDtoPath(cd, query['query']['orgUnitInfo']['orgUnitId']) query['query']['orgUnitInfo']['orgUnitPath'] = convertOrgUnitIDtoPath(cd, query['query']['orgUnitInfo']['orgUnitId'])
if drive is not None:
if 'sharedDriveInfo' in query['query']: if 'sharedDriveInfo' in query['query']:
query['query']['sharedDriveInfo']['sharedDriveNames'] = [] query['query']['sharedDriveInfo']['sharedDriveNames'] = []
for sharedDriveId in query['query']['sharedDriveInfo']['sharedDriveIds']: for sharedDriveId in query['query']['sharedDriveInfo']['sharedDriveIds']:
query['query']['sharedDriveInfo']['sharedDriveNames'].append(_getSharedDriveNameFromId(drive, sharedDriveId, useDomainAdminAccess=True)) query['query']['sharedDriveInfo']['sharedDriveNames'].append(_getSharedDriveNameFromId(sharedDriveId))
query['query'].pop('searchMethod', None) query['query'].pop('searchMethod', None)
query['query'].pop('teamDriveInfo', None) query['query'].pop('teamDriveInfo', None)
VAULT_QUERY_TIME_OBJECTS = {'createTime', 'endTime', 'startTime', 'versionDate'} VAULT_QUERY_TIME_OBJECTS = {'createTime', 'endTime', 'startTime', 'versionDate'}
def _showVaultQuery(matterNameId, query, cd, drive, FJQC, k=0, kcount=0): def _showVaultQuery(matterNameId, query, cd, FJQC, k=0, kcount=0):
_cleanVaultQuery(query, cd, drive) _cleanVaultQuery(query, cd)
if FJQC is not None and FJQC.formatJSON: if FJQC is not None and FJQC.formatJSON:
printLine(json.dumps(cleanJSON(query, timeObjects=VAULT_QUERY_TIME_OBJECTS), ensure_ascii=False, sort_keys=False)) printLine(json.dumps(cleanJSON(query, timeObjects=VAULT_QUERY_TIME_OBJECTS), ensure_ascii=False, sort_keys=False))
return return
@@ -41709,7 +41712,7 @@ def doInfoVaultQuery():
queryId, queryName, queryNameId = convertQueryNameToID(v, getString(Cmd.OB_QUERY_ITEM), matterId, matterNameId) queryId, queryName, queryNameId = convertQueryNameToID(v, getString(Cmd.OB_QUERY_ITEM), matterId, matterNameId)
else: else:
queryName = getString(Cmd.OB_QUERY_ITEM) queryName = getString(Cmd.OB_QUERY_ITEM)
cd = drive = None cd = None
fieldsList = [] fieldsList = []
FJQC = FormatJSONQuoteChar() FJQC = FormatJSONQuoteChar()
while Cmd.ArgumentsRemaining(): while Cmd.ArgumentsRemaining():
@@ -41719,8 +41722,8 @@ def doInfoVaultQuery():
queryId, queryName, queryNameId = convertQueryNameToID(v, queryName, matterId, matterNameId) queryId, queryName, queryNameId = convertQueryNameToID(v, queryName, matterId, matterNameId)
elif myarg == 'shownames': elif myarg == 'shownames':
cd = buildGAPIObject(API.DIRECTORY) cd = buildGAPIObject(API.DIRECTORY)
_, drive = buildGAPIServiceObject(API.DRIVE3, _getAdminEmail()) _, GM.Globals[GM.ADMIN_DRIVE] = buildGAPIServiceObject(API.DRIVE3, _getAdminEmail())
if drive is None: if GM.Globals[GM.ADMIN_DRIVE] is None:
return return
elif getFieldsList(myarg, VAULT_QUERY_FIELDS_CHOICE_MAP, fieldsList, initialField=['savedQueryId', 'displayName']): elif getFieldsList(myarg, VAULT_QUERY_FIELDS_CHOICE_MAP, fieldsList, initialField=['savedQueryId', 'displayName']):
pass pass
@@ -41731,7 +41734,7 @@ def doInfoVaultQuery():
query = callGAPI(v.matters().savedQueries(), 'get', query = callGAPI(v.matters().savedQueries(), 'get',
throwReasons=[GAPI.NOT_FOUND, GAPI.BAD_REQUEST, GAPI.FORBIDDEN], throwReasons=[GAPI.NOT_FOUND, GAPI.BAD_REQUEST, GAPI.FORBIDDEN],
matterId=matterId, savedQueryId=queryId, fields=fields) matterId=matterId, savedQueryId=queryId, fields=fields)
_showVaultQuery(matterNameId, query, cd, drive, FJQC) _showVaultQuery(matterNameId, query, cd, FJQC)
except (GAPI.notFound, GAPI.badRequest, GAPI.forbidden) as e: except (GAPI.notFound, GAPI.badRequest, GAPI.forbidden) as e:
entityActionFailedWarning([Ent.VAULT_MATTER, matterNameId, Ent.VAULT_QUERY, queryNameId], str(e)) entityActionFailedWarning([Ent.VAULT_MATTER, matterNameId, Ent.VAULT_QUERY, queryNameId], str(e))
@@ -41748,7 +41751,7 @@ def doPrintShowVaultQueries():
csvPF = CSVPrintFile(PRINT_VAULT_QUERIES_TITLES, 'sortall') if Act.csvFormat() else None csvPF = CSVPrintFile(PRINT_VAULT_QUERIES_TITLES, 'sortall') if Act.csvFormat() else None
FJQC = FormatJSONQuoteChar(csvPF) FJQC = FormatJSONQuoteChar(csvPF)
matters = [] matters = []
cd = drive = None cd = None
fieldsList = [] fieldsList = []
while Cmd.ArgumentsRemaining(): while Cmd.ArgumentsRemaining():
myarg = getArgument() myarg = getArgument()
@@ -41758,8 +41761,8 @@ def doPrintShowVaultQueries():
matters = shlexSplitList(getString(Cmd.OB_MATTER_ITEM_LIST)) matters = shlexSplitList(getString(Cmd.OB_MATTER_ITEM_LIST))
elif myarg == 'shownames': elif myarg == 'shownames':
cd = buildGAPIObject(API.DIRECTORY) cd = buildGAPIObject(API.DIRECTORY)
_, drive = buildGAPIServiceObject(API.DRIVE3, _getAdminEmail()) _, GM.Globals[GM.ADMIN_DRIVE] = buildGAPIServiceObject(API.DRIVE3, _getAdminEmail())
if drive is None: if GM.Globals[GM.ADMIN_DRIVE] is None:
return return
elif getFieldsList(myarg, VAULT_QUERY_FIELDS_CHOICE_MAP, fieldsList, initialField=['savedQueryId', 'displayName']): elif getFieldsList(myarg, VAULT_QUERY_FIELDS_CHOICE_MAP, fieldsList, initialField=['savedQueryId', 'displayName']):
pass pass
@@ -41821,11 +41824,11 @@ def doPrintShowVaultQueries():
k = 0 k = 0
for query in queries: for query in queries:
k += 1 k += 1
_showVaultQuery(matterNameId, query, cd, drive, FJQC, k, kcount) _showVaultQuery(matterNameId, query, cd, FJQC, k, kcount)
Ind.Decrement() Ind.Decrement()
else: else:
for query in queries: for query in queries:
_cleanVaultQuery(query, cd, drive) _cleanVaultQuery(query, cd)
row = flattenJSON(query, flattened={'matterId': matterId, 'matterName': matterName}, timeObjects=VAULT_QUERY_TIME_OBJECTS) row = flattenJSON(query, flattened={'matterId': matterId, 'matterName': matterName}, timeObjects=VAULT_QUERY_TIME_OBJECTS)
if not FJQC.formatJSON: if not FJQC.formatJSON:
csvPF.WriteRowTitles(row) csvPF.WriteRowTitles(row)
@@ -52817,16 +52820,21 @@ def _convertSharedDriveNameToId(drive, user, i, count, fileIdEntity, useDomainAd
','.join([td['id'] for td in tdlist])), i, count) ','.join([td['id'] for td in tdlist])), i, count)
return False return False
def _getSharedDriveNameFromId(drive, sharedDriveId, useDomainAdminAccess=False): def _getSharedDriveNameFromId(sharedDriveId):
sharedDriveName = GM.Globals[GM.MAP_SHAREDDRIVE_ID_TO_NAME].get(sharedDriveId) sharedDriveName = GM.Globals[GM.MAP_SHAREDDRIVE_ID_TO_NAME].get(sharedDriveId)
if not sharedDriveName: if not sharedDriveName:
if not GM.Globals[GM.ADMIN_DRIVE]:
_, GM.Globals[GM.ADMIN_DRIVE] = buildGAPIServiceObject(API.DRIVE3, _getAdminEmail())
if GM.Globals[GM.ADMIN_DRIVE]:
try: try:
sharedDriveName = callGAPI(drive.drives(), 'get', sharedDriveName = callGAPI(GM.Globals[GM.ADMIN_DRIVE].drives(), 'get',
throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.NOT_FOUND], throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.NOT_FOUND],
useDomainAdminAccess=useDomainAdminAccess, useDomainAdminAccess=True,
driveId=sharedDriveId, fields='name')['name'] driveId=sharedDriveId, fields='name')['name']
except (GAPI.notFound, GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy): except (GAPI.notFound, GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy):
sharedDriveName = TEAM_DRIVE sharedDriveName = TEAM_DRIVE
else:
sharedDriveName = TEAM_DRIVE
GM.Globals[GM.MAP_SHAREDDRIVE_ID_TO_NAME][sharedDriveId] = sharedDriveName GM.Globals[GM.MAP_SHAREDDRIVE_ID_TO_NAME][sharedDriveId] = sharedDriveName
return sharedDriveName return sharedDriveName
@@ -52838,7 +52846,7 @@ def _getDriveFileNameFromId(drive, fileId, combineTitleId=True, useDomainAdminAc
if result: if result:
fileName = result['name'] fileName = result['name']
if (result['mimeType'] == MIMETYPE_GA_FOLDER) and result.get('driveId') and (result['name'] == TEAM_DRIVE): if (result['mimeType'] == MIMETYPE_GA_FOLDER) and result.get('driveId') and (result['name'] == TEAM_DRIVE):
fileName = _getSharedDriveNameFromId(drive, result['driveId']) fileName = _getSharedDriveNameFromId(result['driveId'])
if combineTitleId: if combineTitleId:
fileName += '('+fileId+')' fileName += '('+fileId+')'
return (fileName, _getEntityMimeType(result), result['mimeType']) return (fileName, _getEntityMimeType(result), result['mimeType'])
@@ -53920,7 +53928,7 @@ def getFilePaths(drive, fileTree, initialResult, filePathInfo, addParentsToTree=
fullpath=False, showDepth=False, folderPathOnly=False): fullpath=False, showDepth=False, folderPathOnly=False):
def _getParentName(result): def _getParentName(result):
if (result['mimeType'] == MIMETYPE_GA_FOLDER) and result.get('driveId') and (result['name'] == TEAM_DRIVE): if (result['mimeType'] == MIMETYPE_GA_FOLDER) and result.get('driveId') and (result['name'] == TEAM_DRIVE):
parentName = _getSharedDriveNameFromId(drive, result['driveId']) parentName = _getSharedDriveNameFromId(result['driveId'])
if parentName != TEAM_DRIVE: if parentName != TEAM_DRIVE:
return f'{SHARED_DRIVES}{filePathInfo["delimiter"]}{parentName}' return f'{SHARED_DRIVES}{filePathInfo["delimiter"]}{parentName}'
return result['name'] return result['name']
@@ -54619,9 +54627,9 @@ def showFileInfo(users):
driveId = result.get('driveId') driveId = result.get('driveId')
if driveId: if driveId:
if result['mimeType'] == MIMETYPE_GA_FOLDER and result['name'] == TEAM_DRIVE: if result['mimeType'] == MIMETYPE_GA_FOLDER and result['name'] == TEAM_DRIVE:
result['name'] = _getSharedDriveNameFromId(drive, driveId) result['name'] = _getSharedDriveNameFromId(driveId)
if DFF.showSharedDriveNames: if DFF.showSharedDriveNames:
result['driveName'] = _getSharedDriveNameFromId(drive, driveId) result['driveName'] = _getSharedDriveNameFromId(driveId)
if showNoParents: if showNoParents:
result.setdefault('parents', []) result.setdefault('parents', [])
if getPermissionsForSharedDrives and driveId and 'permissions' not in result: if getPermissionsForSharedDrives and driveId and 'permissions' not in result:
@@ -55309,7 +55317,7 @@ def extendFileTreeParents(drive, fileTree, fields):
result['parents'] = [ORPHANS] if result.get('ownedByMe', False) and 'sharedWithMeTime' not in result else [SHARED_WITHME] result['parents'] = [ORPHANS] if result.get('ownedByMe', False) and 'sharedWithMeTime' not in result else [SHARED_WITHME]
else: else:
if result['name'] == TEAM_DRIVE: if result['name'] == TEAM_DRIVE:
result['name'] = _getSharedDriveNameFromId(drive, result['driveId']) result['name'] = _getSharedDriveNameFromId(result['driveId'])
result['parents'] = [SHARED_DRIVES] if 'sharedWithMeTime' not in result else [SHARED_WITHME] result['parents'] = [SHARED_DRIVES] if 'sharedWithMeTime' not in result else [SHARED_WITHME]
fileTree[fileId]['info'] = result fileTree[fileId]['info'] = result
fileTree[fileId]['info']['noDisplay'] = True fileTree[fileId]['info']['noDisplay'] = True
@@ -56048,7 +56056,7 @@ def printFileList(users):
if not pmselect and 'permissions' in fileInfo: if not pmselect and 'permissions' in fileInfo:
fileInfo['permissions'] = DLP.GetFileMatchingPermission(fileInfo) fileInfo['permissions'] = DLP.GetFileMatchingPermission(fileInfo)
if DFF.showSharedDriveNames and driveId: if DFF.showSharedDriveNames and driveId:
fileInfo['driveName'] = _getSharedDriveNameFromId(drive, driveId) fileInfo['driveName'] = _getSharedDriveNameFromId(driveId)
if filepath: if filepath:
if not FJQC.formatJSON or not addPathsToJSON: if not FJQC.formatJSON or not addPathsToJSON:
addFilePathsToRow(drive, fileTree, fileInfo, filePathInfo, csvPF, row, addFilePathsToRow(drive, fileTree, fileInfo, filePathInfo, csvPF, row,
@@ -56103,7 +56111,7 @@ def printFileList(users):
else: else:
if not countsRowFilter: if not countsRowFilter:
csvPFco.UpdateMimeTypeCounts(flattenJSON(fileInfo, flattened=row, skipObjects=skipObjects, timeObjects=timeObjects, csvPFco.UpdateMimeTypeCounts(flattenJSON(fileInfo, flattened=row, skipObjects=skipObjects, timeObjects=timeObjects,
simpleLists=simpleLists, delimiter=delimiter), mimeTypeInfo) simpleLists=simpleLists, delimiter=delimiter), mimeTypeInfo, sizeField)
else: else:
mimeTypeInfo.setdefault(fileInfo['mimeType'], {'count': 0, 'size': 0}) mimeTypeInfo.setdefault(fileInfo['mimeType'], {'count': 0, 'size': 0})
mimeTypeInfo[fileInfo['mimeType']]['count'] += 1 mimeTypeInfo[fileInfo['mimeType']]['count'] += 1
@@ -56924,7 +56932,7 @@ def printShowFilePaths(users):
driveId = result.get('driveId') driveId = result.get('driveId')
if driveId: if driveId:
if result['mimeType'] == MIMETYPE_GA_FOLDER and result['name'] == TEAM_DRIVE: if result['mimeType'] == MIMETYPE_GA_FOLDER and result['name'] == TEAM_DRIVE:
result['name'] = _getSharedDriveNameFromId(drive, driveId) result['name'] = _getSharedDriveNameFromId(driveId)
if returnPathOnly: if returnPathOnly:
if fullpath: if fullpath:
writeStdout(f'{SHARED_DRIVES}/{result["name"]}\n') writeStdout(f'{SHARED_DRIVES}/{result["name"]}\n')
@@ -57014,7 +57022,7 @@ def printFileParentTree(users):
driveId = result.get('driveId') driveId = result.get('driveId')
if driveId: if driveId:
if result['mimeType'] == MIMETYPE_GA_FOLDER and result['name'] == TEAM_DRIVE: if result['mimeType'] == MIMETYPE_GA_FOLDER and result['name'] == TEAM_DRIVE:
result['name'] = _getSharedDriveNameFromId(drive, driveId) result['name'] = _getSharedDriveNameFromId(driveId)
result['isRoot'] = True result['isRoot'] = True
result['parents'] = [''] result['parents'] = ['']
fileList.append(result) fileList.append(result)
@@ -57212,10 +57220,7 @@ def printShowFileCounts(users):
if not drive: if not drive:
continue continue
sharedDriveId = fileIdEntity.get('shareddrive', {}).get('driveId', '') sharedDriveId = fileIdEntity.get('shareddrive', {}).get('driveId', '')
if sharedDriveId: sharedDriveName = _getSharedDriveNameFromId(sharedDriveId) if sharedDriveId else ''
sharedDriveName = _getSharedDriveNameFromId(drive, sharedDriveId)
else:
sharedDriveName = ''
mimeTypeInfo = {} mimeTypeInfo = {}
userLastModification = { userLastModification = {
'lastModifiedFileId': '', 'lastModifiedFileName': '', 'lastModifiedFileId': '', 'lastModifiedFileName': '',
@@ -57444,7 +57449,7 @@ def printDiskUsage(users):
includeOwner = False includeOwner = False
csvPF.RemoveTitles(['Owner', 'ownedByMe']) csvPF.RemoveTitles(['Owner', 'ownedByMe'])
if topFolder['name'] == TEAM_DRIVE and not topFolder.get('parents'): if topFolder['name'] == TEAM_DRIVE and not topFolder.get('parents'):
topFolder['name'] = _getSharedDriveNameFromId(drive, driveId) topFolder['name'] = _getSharedDriveNameFromId(driveId)
topFolder['path'] = f'{SHARED_DRIVES}{pathDelimiter}{topFolder["name"]}' topFolder['path'] = f'{SHARED_DRIVES}{pathDelimiter}{topFolder["name"]}'
else: else:
topFolder['path'] = topFolder['name'] topFolder['path'] = topFolder['name']
@@ -59509,7 +59514,7 @@ def _getCopyMoveParentInfo(drive, user, i, count, j, jcount, newParentId, statis
result['destParentType'] = DEST_PARENT_MYDRIVE_FOLDER result['destParentType'] = DEST_PARENT_MYDRIVE_FOLDER
else: else:
if result['name'] == TEAM_DRIVE and not result.get('parents', []): if result['name'] == TEAM_DRIVE and not result.get('parents', []):
result['name'] = _getSharedDriveNameFromId(drive, result['driveId']) result['name'] = _getSharedDriveNameFromId(result['driveId'])
result['destParentType'] = DEST_PARENT_SHAREDDRIVE_ROOT result['destParentType'] = DEST_PARENT_SHAREDDRIVE_ROOT
else: else:
result['destParentType'] = DEST_PARENT_SHAREDDRIVE_FOLDER result['destParentType'] = DEST_PARENT_SHAREDDRIVE_FOLDER
@@ -60055,7 +60060,7 @@ def copyDriveFile(users):
# Source at root of Shared Drive? # Source at root of Shared Drive?
sourceMimeType = source['mimeType'] sourceMimeType = source['mimeType']
if sourceMimeType == MIMETYPE_GA_FOLDER and source.get('driveId') and source['name'] == TEAM_DRIVE and not source.get('parents', []): if sourceMimeType == MIMETYPE_GA_FOLDER and source.get('driveId') and source['name'] == TEAM_DRIVE and not source.get('parents', []):
source['name'] = _getSharedDriveNameFromId(drive, source['driveId']) source['name'] = _getSharedDriveNameFromId(source['driveId'])
sourceName = source['name'] sourceName = source['name']
sourceNameId = f"{sourceName}({source['id']})" sourceNameId = f"{sourceName}({source['id']})"
copyMoveOptions['sourceDriveId'] = source.get('driveId') copyMoveOptions['sourceDriveId'] = source.get('driveId')
@@ -60823,7 +60828,7 @@ def moveDriveFile(users):
if sourceMimeType == MIMETYPE_GA_FOLDER and source['name'] in [MY_DRIVE, TEAM_DRIVE] and not source.get('parents', []): if sourceMimeType == MIMETYPE_GA_FOLDER and source['name'] in [MY_DRIVE, TEAM_DRIVE] and not source.get('parents', []):
copyMoveOptions['sourceIsMyDriveSharedDrive'] = True copyMoveOptions['sourceIsMyDriveSharedDrive'] = True
if source.get('driveId'): if source.get('driveId'):
source['name'] = _getSharedDriveNameFromId(drive, source['driveId']) source['name'] = _getSharedDriveNameFromId(source['driveId'])
sourceName = source['name'] sourceName = source['name']
sourceNameId = f"{sourceName}({source['id']})" sourceNameId = f"{sourceName}({source['id']})"
copyMoveOptions['sourceDriveId'] = source.get('driveId') copyMoveOptions['sourceDriveId'] = source.get('driveId')
@@ -63248,7 +63253,7 @@ def printEmptyDriveFolders(users):
fileIdEntity['shareddrive'] = {'driveId': sharedDriveId, 'corpora': 'drive', 'includeItemsFromAllDrives': True, 'supportsAllDrives': True} fileIdEntity['shareddrive'] = {'driveId': sharedDriveId, 'corpora': 'drive', 'includeItemsFromAllDrives': True, 'supportsAllDrives': True}
csvPF.AddTitles(['driveId']) csvPF.AddTitles(['driveId'])
csvPF.MoveTitlesToEnd(['name']) csvPF.MoveTitlesToEnd(['name'])
pathList = [f'{SHARED_DRIVES}/{_getSharedDriveNameFromId(drive, sharedDriveId)}'] pathList = [f'{SHARED_DRIVES}/{_getSharedDriveNameFromId(sharedDriveId)}']
else: else:
pathList = [fileEntryInfo['name']] pathList = [fileEntryInfo['name']]
mimeType = fileEntryInfo['mimeType'] mimeType = fileEntryInfo['mimeType']
@@ -63338,7 +63343,7 @@ def deleteEmptyDriveFolders(users):
if 'driveId' in fileEntryInfo: if 'driveId' in fileEntryInfo:
sharedDriveId = fileEntryInfo['driveId'] sharedDriveId = fileEntryInfo['driveId']
fileIdEntity['shareddrive'] = {'driveId': sharedDriveId, 'corpora': 'drive', 'includeItemsFromAllDrives': True, 'supportsAllDrives': True} fileIdEntity['shareddrive'] = {'driveId': sharedDriveId, 'corpora': 'drive', 'includeItemsFromAllDrives': True, 'supportsAllDrives': True}
pathList = [f'{SHARED_DRIVES}/{_getSharedDriveNameFromId(drive, sharedDriveId)}'] pathList = [f'{SHARED_DRIVES}/{_getSharedDriveNameFromId(sharedDriveId)}']
else: else:
pathList = [fileEntryInfo['name']] pathList = [fileEntryInfo['name']]
mimeType = fileEntryInfo['mimeType'] mimeType = fileEntryInfo['mimeType']
@@ -65754,7 +65759,7 @@ def doPrintShowSharedDrives():
def doPrintShowOrgunitSharedDrives(): def doPrintShowOrgunitSharedDrives():
def _getOrgUnitSharedDriveInfo(shareddrive): def _getOrgUnitSharedDriveInfo(shareddrive):
shareddrive['driveId'] = shareddrive['name'].rsplit(';')[1] shareddrive['driveId'] = shareddrive['name'].rsplit(';')[1]
shareddrive['driveName'] = _getSharedDriveNameFromId(drive, shareddrive['driveId'], useDomainAdminAccess=True) shareddrive['driveName'] = _getSharedDriveNameFromId(shareddrive['driveId'])
shareddrive['orgUnitPath'] = orgUnitPath shareddrive['orgUnitPath'] = orgUnitPath
def _showOrgUnitSharedDrive(shareddrive, j, jcount, FJQC): def _showOrgUnitSharedDrive(shareddrive, j, jcount, FJQC):
@@ -65773,8 +65778,8 @@ def doPrintShowOrgunitSharedDrives():
ci = buildGAPIObject(API.CLOUDIDENTITY_ORGUNITS_BETA) ci = buildGAPIObject(API.CLOUDIDENTITY_ORGUNITS_BETA)
cd = buildGAPIObject(API.DIRECTORY) cd = buildGAPIObject(API.DIRECTORY)
_, drive = buildGAPIServiceObject(API.DRIVE3, _getAdminEmail()) _, GM.Globals[GM.ADMIN_DRIVE] = buildGAPIServiceObject(API.DRIVE3, _getAdminEmail())
if not drive: if not GM.Globals[GM.ADMIN_DRIVE]:
return return
csvPF = CSVPrintFile(['name', 'type', 'member', 'memberUri', 'driveId', 'driveName', 'orgUnitPath']) if Act.csvFormat() else None csvPF = CSVPrintFile(['name', 'type', 'member', 'memberUri', 'driveId', 'driveName', 'orgUnitPath']) if Act.csvFormat() else None
FJQC = FormatJSONQuoteChar(csvPF) FJQC = FormatJSONQuoteChar(csvPF)
@@ -65853,13 +65858,13 @@ def copySyncSharedDriveACLs(users, useDomainAdminAccess=False):
if not drive: if not drive:
continue continue
if not srcFileIdEntity.get('shareddrivename'): if not srcFileIdEntity.get('shareddrivename'):
srcFileIdEntity['shareddrivename'] = _getSharedDriveNameFromId(drive, srcFileIdEntity['shareddrive']['driveId']) srcFileIdEntity['shareddrivename'] = _getSharedDriveNameFromId(srcFileIdEntity['shareddrive']['driveId'])
if tgtFileIdEntity.get('shareddrivename'): if tgtFileIdEntity.get('shareddrivename'):
if not _convertSharedDriveNameToId(drive, user, i, count, tgtFileIdEntity, useDomainAdminAccess): if not _convertSharedDriveNameToId(drive, user, i, count, tgtFileIdEntity, useDomainAdminAccess):
continue continue
tgtFileIdEntity['shareddrive']['corpora'] = 'drive' tgtFileIdEntity['shareddrive']['corpora'] = 'drive'
else: else:
tgtFileIdEntity['shareddrivename'] = _getSharedDriveNameFromId(drive, tgtFileIdEntity['shareddrive']['driveId']) tgtFileIdEntity['shareddrivename'] = _getSharedDriveNameFromId(tgtFileIdEntity['shareddrive']['driveId'])
statistics = _initStatistics() statistics = _initStatistics()
copyMoveOptions['sourceDriveId'] = srcFileIdEntity['shareddrive']['driveId'] copyMoveOptions['sourceDriveId'] = srcFileIdEntity['shareddrive']['driveId']
copyMoveOptions['destDriveId'] = tgtFileIdEntity['shareddrive']['driveId'] copyMoveOptions['destDriveId'] = tgtFileIdEntity['shareddrive']['driveId']
@@ -69692,7 +69697,7 @@ def _initMessageThreadParameters(entityType, doIt, maxToProcess):
'query': '', 'queryTimes': {}, 'query': '', 'queryTimes': {},
'entityType': entityType, 'messageEntity': None, 'doIt': doIt, 'quick': True, 'entityType': entityType, 'messageEntity': None, 'doIt': doIt, 'quick': True,
'labelMatchPattern': None, 'senderMatchPattern': None, 'labelMatchPattern': None, 'senderMatchPattern': None,
'maxToProcess': maxToProcess, 'maxItems': 0, 'maxToProcess': maxToProcess, 'maxItems': 0, 'maxMessagesPerThread': 0,
'maxToKeywords': [MESSAGES_MAX_TO_KEYWORDS[Act.Get()], 'maxtoprocess'], 'maxToKeywords': [MESSAGES_MAX_TO_KEYWORDS[Act.Get()], 'maxtoprocess'],
'listType': listType, 'fields': f'nextPageToken,{listType}(id)'} 'listType': listType, 'fields': f'nextPageToken,{listType}(id)'}
@@ -69734,6 +69739,8 @@ def _getMessageSelectParameters(myarg, parameters):
parameters['doIt'] = True parameters['doIt'] = True
elif myarg in parameters['maxToKeywords']: elif myarg in parameters['maxToKeywords']:
parameters['maxToProcess'] = getInteger(minVal=0) parameters['maxToProcess'] = getInteger(minVal=0)
elif myarg == 'maxmessagesperthread':
parameters['maxMessagesPerThread'] = getInteger(minVal=0)
else: else:
return False return False
return True return True
@@ -70873,8 +70880,6 @@ def printShowMessagesThreads(users, entityType):
return None return None
def _qualifyMessage(user, result): def _qualifyMessage(user, result):
if parameters['maxToProcess'] and parameters['messagesProcessed'] == parameters['maxToProcess']:
return (False, None)
if senderMatchPattern: if senderMatchPattern:
sender = _checkSenderMatchCount(result) sender = _checkSenderMatchCount(result)
if not sender: if not sender:
@@ -70902,7 +70907,9 @@ def printShowMessagesThreads(users, entityType):
except ValueError: except ValueError:
return headerValue return headerValue
def _showMessage(user, result, j, jcount): def _showMessage(user, result, j, jcount, checkMax=True):
if checkMax and parameters['maxToProcess'] and parameters['messagesProcessed'] == parameters['maxToProcess']:
return
status, messageLabels = _qualifyMessage(user, result) status, messageLabels = _qualifyMessage(user, result)
if not status: if not status:
return return
@@ -70938,6 +70945,7 @@ def printShowMessagesThreads(users, entityType):
if show_attachments or save_attachments or upload_attachments: if show_attachments or save_attachments or upload_attachments:
_showSaveAttachments(result['id'], result['payload'], attachmentNamePattern, j, jcount) _showSaveAttachments(result['id'], result['payload'], attachmentNamePattern, j, jcount)
Ind.Decrement() Ind.Decrement()
if checkMax:
parameters['messagesProcessed'] += 1 parameters['messagesProcessed'] += 1
def _getAttachments(messageId, payload, attachmentNamePattern, attachments): def _getAttachments(messageId, payload, attachmentNamePattern, attachments):
@@ -70960,7 +70968,9 @@ def printShowMessagesThreads(users, entityType):
else: else:
_getAttachments(messageId, part, attachmentNamePattern, attachments) _getAttachments(messageId, part, attachmentNamePattern, attachments)
def _printMessage(user, result): def _printMessage(user, result, checkMax=True):
if checkMax and parameters['maxToProcess'] and parameters['messagesProcessed'] == parameters['maxToProcess']:
return
status, messageLabels = _qualifyMessage(user, result) status, messageLabels = _qualifyMessage(user, result)
if not status: if not status:
return return
@@ -71014,6 +71024,7 @@ def printShowMessagesThreads(users, entityType):
row[f'Attachments{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}{i}{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}size'] = attachment[2] row[f'Attachments{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}{i}{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}size'] = attachment[2]
row[f'Attachments{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}{i}{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}charset'] = attachment[3] row[f'Attachments{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}{i}{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}charset'] = attachment[3]
csvPF.WriteRowTitles(row) csvPF.WriteRowTitles(row)
if checkMax:
parameters['messagesProcessed'] += 1 parameters['messagesProcessed'] += 1
def _countMessageLabels(user, result): def _countMessageLabels(user, result):
@@ -71044,6 +71055,8 @@ def printShowMessagesThreads(users, entityType):
messageThreadCounts['size'] += result['sizeEstimate'] messageThreadCounts['size'] += result['sizeEstimate']
def _showThread(user, result, j, jcount): def _showThread(user, result, j, jcount):
if parameters['maxToProcess'] and parameters['messagesProcessed'] == parameters['maxToProcess']:
return
if senderMatchPattern: if senderMatchPattern:
for message in result['messages']: for message in result['messages']:
if _checkSenderMatch(message): if _checkSenderMatch(message):
@@ -71059,19 +71072,29 @@ def printShowMessagesThreads(users, entityType):
k = 0 k = 0
for message in result['messages']: for message in result['messages']:
k += 1 k += 1
_showMessage(user, message, k, kcount) _showMessage(user, message, k, kcount, False)
if k == parameters['maxMessagesPerThread']:
break
Ind.Decrement() Ind.Decrement()
parameters['messagesProcessed'] += 1
def _printThread(user, result): def _printThread(user, result):
if parameters['maxToProcess'] and parameters['messagesProcessed'] == parameters['maxToProcess']:
return
if senderMatchPattern: if senderMatchPattern:
for message in result['messages']: for message in result['messages']:
if _checkSenderMatch(message): if _checkSenderMatch(message):
break break
else: else:
return return
k = 0
for message in result['messages']: for message in result['messages']:
_printMessage(user, message) k += 1
_printMessage(user, message, False)
if k == parameters['maxMessagesPerThread']:
break
messageThreadCounts['threads'] += 1 messageThreadCounts['threads'] += 1
parameters['messagesProcessed'] += 1
def _countThreadLabels(user, result): def _countThreadLabels(user, result):
for message in result['messages']: for message in result['messages']:
@@ -71086,8 +71109,12 @@ def printShowMessagesThreads(users, entityType):
else: else:
return return
else: else:
k = 0
for message in result['messages']: for message in result['messages']:
k += 1
messageThreadCounts['size'] += message['sizeEstimate'] messageThreadCounts['size'] += message['sizeEstimate']
if k == parameters['maxMessagesPerThread']:
break
messageThreadCounts['threads'] += 1 messageThreadCounts['threads'] += 1
_GMAIL_ERROR_REASON_TO_MESSAGE_MAP = {GAPI.NOT_FOUND: Msg.DOES_NOT_EXIST, GAPI.INVALID_MESSAGE_ID: Msg.INVALID_MESSAGE_ID} _GMAIL_ERROR_REASON_TO_MESSAGE_MAP = {GAPI.NOT_FOUND: Msg.DOES_NOT_EXIST, GAPI.INVALID_MESSAGE_ID: Msg.INVALID_MESSAGE_ID}
@@ -74669,14 +74696,14 @@ def createNotesACLs(users):
request['parent'] = name request['parent'] = name
try: try:
permissions = callGAPI(keep.notes().permissions(), 'batchCreate', permissions = callGAPI(keep.notes().permissions(), 'batchCreate',
throwReasons=GAPI.KEEP_THROW_REASONS, throwReasons=GAPI.KEEP_THROW_REASONS+[GAPI.FAILED_PRECONDITION],
parent=name, body=rbody) parent=name, body=rbody)
entityNumItemsActionPerformed(entityKVList, kcount, Ent.NOTE_ACL, j, jcount) entityNumItemsActionPerformed(entityKVList, kcount, Ent.NOTE_ACL, j, jcount)
if showDetails: if showDetails:
Ind.Increment() Ind.Increment()
_showNotePermissions(permissions['permissions']) _showNotePermissions(permissions['permissions'])
Ind.Decrement() Ind.Decrement()
except (GAPI.badRequest, GAPI.invalidArgument, GAPI.notFound) as e: except (GAPI.badRequest, GAPI.invalidArgument, GAPI.notFound, GAPI.failedPrecondition) as e:
entityActionFailedWarning(entityKVList, str(e), i, count) entityActionFailedWarning(entityKVList, str(e), i, count)
except GAPI.authError: except GAPI.authError:
userKeepServiceNotEnabledWarning(user, i, count) userKeepServiceNotEnabledWarning(user, i, count)

View File

@@ -23,8 +23,10 @@
# The following GM_XXX constants are arbitrary but must be unique # The following GM_XXX constants are arbitrary but must be unique
# Most errors print a message and bail out with a return code # Most errors print a message and bail out with a return code
# Some commands want to set a non-zero return code but not bail # Some commands want to set a non-zero return code but not bail
# GAM admin user # GAM admin user from oauth2.txt or oauth2service.json
ADMIN = 'admin' ADMIN = 'admn'
# Drive service for admin; used to look up Shared Drive Names
ADMIN_DRIVE = 'addr'
# Number/length of API call retries # Number/length of API call retries
API_CALLS_RETRY_DATA = 'rtry' API_CALLS_RETRY_DATA = 'rtry'
# GAM cache directory. If no_cache is True, this variable will be set to None # GAM cache directory. If no_cache is True, this variable will be set to None
@@ -215,6 +217,7 @@ REDIRECT_QUEUE_EOF = 'eof'
# #
Globals = { Globals = {
ADMIN: None, ADMIN: None,
ADMIN_DRIVE: None,
API_CALLS_RETRY_DATA: {}, API_CALLS_RETRY_DATA: {},
CACHE_DIR: None, CACHE_DIR: None,
CACHE_DISCOVERY_ONLY: True, CACHE_DISCOVERY_ONLY: True,