Initial commit of a new experimental modular GAM.

This commit is contained in:
Jay Lee
2026-07-03 08:33:14 -04:00
parent 2fbc3c5c35
commit 8a89a91414
129 changed files with 88262 additions and 82716 deletions

View File

@@ -0,0 +1,433 @@
"""Drive sub-package.
Re-exports all symbols from sub-modules for backward compatibility."""
from gam.cmd.drive.core import ( # noqa: F401
CONSOLIDATION_GROUPING_STRATEGY_CHOICE_MAP,
DFA_ADD_PARENT_IDS,
DFA_ADD_PARENT_NAMES,
DFA_CREATED_TIME,
DFA_IGNORE_DEFAULT_VISIBILITY,
DFA_KEEP_REVISION_FOREVER,
DFA_KWARGS,
DFA_LOCALFILENAME,
DFA_LOCALFILEPATH,
DFA_LOCALMIMETYPE,
DFA_MODIFIED_TIME,
DFA_OCRLANGUAGE,
DFA_PARENTID,
DFA_PARENTQUERY,
DFA_PRESERVE_FILE_TIMES,
DFA_REMOVE_PARENT_IDS,
DFA_REMOVE_PARENT_NAMES,
DFA_REPLACEFILENAME,
DFA_SEARCHARGS,
DFA_SHAREDDRIVE_PARENT,
DFA_SHAREDDRIVE_PARENTID,
DFA_SHAREDDRIVE_PARENTQUERY,
DFA_STRIPNAMEPREFIX,
DFA_TIMEFORMAT,
DFA_TIMESTAMP,
DFA_URL,
DFA_USE_CONTENT_AS_INDEXABLE_TEXT,
DRIVEFILE_PROPERTY_VISIBILITY_CHOICE_MAP,
DRIVE_ACTIVITY_ACTION_MAP,
DRIVE_BY_NAME_CHOICE_MAP,
DRIVE_FILE_CONTENT_RESTRICTIONS_CHOICE_MAP,
DRIVE_FILE_ITEM_DOWNLOAD_RESTRICTION_CHOICE_MAP,
DRIVE_LABEL_CHOICE_MAP,
LOCATION_ALL_DRIVES,
LOCATION_CHOICE_MAP,
LOCATION_MYDRIVE,
LOCATION_ONLY_SHARED_DRIVES,
LOCATION_ORPHANS,
LOCATION_OWNEDBY_ANY,
LOCATION_OWNEDBY_OTHERS,
LOCATION_SHARED_WITHME,
MIMETYPE_CHOICE_MAP,
MIMETYPE_TYPES,
MimeTypeCheck,
TITLE_QUERY_PATTERN,
_convertSharedDriveNameToId,
_driveFileParentSpecified,
_getDriveFileAddRemoveParentInfo,
_getDriveFileDownloadRestrictions,
_getDriveFileNameFromId,
_getDriveFileParentInfo,
_getFileIdFromURL,
_getSharedDriveNameFromId,
_mapDrive2QueryToDrive3,
_simpleFileIdEntityList,
_validateUserGetFileIDs,
_validateUserGetSharedDriveFileIDs,
_validateUserSharedDrive,
cleanFileIDsList,
doDriveSearch,
doSharedDriveSearch,
escapeDriveFileName,
getDriveFileAddRemoveParentAttribute,
getDriveFileAttribute,
getDriveFileCopyAttribute,
getDriveFileEntity,
getDriveFileEntitySharedDriveOnly,
getDriveFileParentAttribute,
getDriveFileProperty,
getEscapedDriveFileName,
getEscapedDriveFolderName,
getMediaBody,
getMimeType,
getSharedDriveEntity,
initDriveFileAttributes,
initDriveFileEntity,
setPreservedFileTimes,
validateMimeType,
)
from gam.cmd.drive.activity import ( # noqa: F401
DRIVESETTINGS_FIELDS_CHOICE_MAP,
DRIVESETTINGS_SCALAR_FIELDS,
DRIVESETTINGS_USAGE_BYTES_FIELDS,
_showSharedDriveThemeSettings,
doShowSharedDriveThemes,
printDriveActivity,
printShowDriveSettings,
showSharedDriveThemes,
)
from gam.cmd.drive.filepaths import ( # noqa: F401
DRIVEFILE_BASIC_PERMISSION_FIELDS,
DRIVEFILE_ORDERBY_CHOICE_MAP,
DRIVEFILE_PERMISSIONS_FOR_VIEW_CHOICES,
DRIVE_CAPABILITIES_SUBFIELDS_CHOICE_MAP,
DRIVE_CONTENT_RESTRICTIONS_SUBFIELDS_CHOICE_MAP,
DRIVE_DOWNLOAD_RESTRICTIONS_SUBFIELDS_CHOICE_MAP,
DRIVE_FIELDS_CHOICE_MAP,
DRIVE_LABELINFO_SUBFIELDS_CHOICE_MAP,
DRIVE_LIST_FIELDS,
DRIVE_OWNERS_SUBFIELDS_CHOICE_MAP,
DRIVE_PARENTS_SUBFIELDS_CHOICE_MAP,
DRIVE_PERMISSIONS_SUBFIELDS_CHOICE_MAP,
DRIVE_SHARINGUSER_SUBFIELDS_CHOICE_MAP,
DRIVE_SHORTCUTDETAILS_SUBFIELDS_CHOICE_MAP,
DRIVE_SUBFIELDS_CHOICE_MAP,
DRIVE_TIME_OBJECTS,
DriveFileFields,
FILEINFO_FIELDS_TITLES,
FILEPATH_FIELDS,
FILEPATH_FIELDS_TITLES,
SHOWLABELS_CHOICES,
_finalizeIncludeLabels,
_finalizeIncludePermissionsForView,
_formatFileDriveLabels,
_getDriveFieldSubField,
_getIncludeLabels,
_getIncludePermissionsForView,
_mapDriveInfo,
_mapDrivePermissionNames,
_mapDriveUser,
_setGetCheckFilePermissions,
_setGetPermissionsForMyDriveSharedDrives,
_setSkipObjects,
getFilePaths,
initFilePathInfo,
showFileInfo,
)
from gam.cmd.drive.revisions import ( # noqa: F401
DRIVE_REVISIONS_INDEXED_TITLES,
FILEREVISIONS_FIELDS_CHOICE_MAP,
FILEREVISIONS_TIME_OBJECTS,
REVISIONS_FIELDS_CHOICE_MAP,
_selectRevisionIds,
_selectRevisionResults,
_showRevision,
_stripMeInOwners,
_stripNotMeInOwners,
_updateAnyOwnerQuery,
deleteFileRevisions,
getRevisionsEntity,
printShowFileRevisions,
updateFileRevisions,
)
from gam.cmd.drive.filetree import ( # noqa: F401
CHECK_LOCATION_FIELDS_TITLES,
DRIVEFILE_ACL_PERMISSION_DETAILS_TYPES,
DRIVEFILE_ACL_PERMISSION_TYPES,
DRIVEFILE_ACL_ROLES_MAP,
DRIVE_INDEXED_TITLES,
DriveListParameters,
FILECOUNT_SUMMARY_CHOICE_MAP,
FILECOUNT_SUMMARY_NONE,
FILECOUNT_SUMMARY_ONLY,
FILECOUNT_SUMMARY_PLUS,
FILECOUNT_SUMMARY_USER,
FILELIST_FIELDS_TITLES,
OWNED_BY_ME_FIELDS_TITLES,
PermissionMatch,
SHOW_OWNED_BY_CHOICE_MAP,
SIZE_FIELD_CHOICE_MAP,
_getGettingEntity,
_validateACLAttributes,
_validateACLOwnerType,
_validatePermissionAttributes,
_validatePermissionOwnerType,
addFilePathsToInfo,
addFilePathsToRow,
buildFileTree,
extendFileTree,
extendFileTreeParents,
initFileTree,
noFileSelectFileIdEntity,
)
from gam.cmd.drive.filelist import ( # noqa: F401
FILECOMMENTS_AUTHOR_SUBFIELDS_CHOICE_MAP,
FILECOMMENTS_FIELDS_CHOICE_MAP,
FILECOMMENTS_INDEXED_TITLES,
FILECOMMENTS_REPLIES_SUBFIELDS_CHOICE_MAP,
FILECOMMENTS_SUBFIELDS_CHOICE_MAP,
FILECOMMENTS_TIME_OBJECTS,
_checkUpdateLastModifiction,
_getCommentFields,
_getLastModificationPath,
_initLastModification,
_showComment,
_showLastModification,
_stripCommentPhotoLinks,
_updateLastModificationRow,
printFileList,
printFileParentTree,
printShowFileComments,
printShowFilePaths,
)
from gam.cmd.drive.fileinfo import ( # noqa: F401
DISKUSAGE_SHOW_CHOICES,
FILESHARECOUNTS_CATEGORIES,
FILESHARECOUNTS_OWNER,
FILESHARECOUNTS_SHARED,
FILESHARECOUNTS_SHARED_EXTERNAL,
FILESHARECOUNTS_SHARED_INTERNAL,
FILESHARECOUNTS_TOTAL,
FILESHARECOUNTS_ZEROCOUNTS,
FILETREE_FIELDS_CHOICE_MAP,
FILETREE_FIELDS_PRINT_ORDER,
getCreationModificationTimes,
printDiskUsage,
printShowDrivelastModifications,
printShowFileCounts,
printShowFileShareCounts,
printShowFileTree,
writeReturnIdLink,
)
from gam.cmd.drive.files import ( # noqa: F401
FILE_SUBTOTAL_STATS,
FOLDER_SUBTOTAL_STATS,
SHORTCUT_CODE_MIMETYPE_MISMATCH,
SHORTCUT_CODE_NOT_A_SHORTCUT,
SHORTCUT_CODE_SHORTCUT_NOT_FOUND,
SHORTCUT_CODE_TARGET_NOT_FOUND,
SHORTCUT_CODE_VALID,
STAT_FILE_COPIED_MOVED,
STAT_FILE_DUPLICATE,
STAT_FILE_FAILED,
STAT_FILE_IN_SKIPIDS,
STAT_FILE_NOT_COPYABLE_MOVABLE,
STAT_FILE_PERMISSIONS_FAILED,
STAT_FILE_PROTECTEDRANGES_FAILED,
STAT_FILE_SHORTCUT_CREATED,
STAT_FILE_SHORTCUT_EXISTS,
STAT_FILE_TOTAL,
STAT_FOLDER_COPIED_MOVED,
STAT_FOLDER_DUPLICATE,
STAT_FOLDER_FAILED,
STAT_FOLDER_MERGED,
STAT_FOLDER_NOT_WRITABLE,
STAT_FOLDER_PERMISSIONS_FAILED,
STAT_FOLDER_SHORTCUT_CREATED,
STAT_FOLDER_SHORTCUT_EXISTS,
STAT_FOLDER_TOTAL,
STAT_LENGTH,
STAT_USER_NOT_ORGANIZER,
_incrStatistic,
_initStatistics,
addTimestampToFilename,
checkDriveFileShortcut,
createDriveFile,
createDriveFileShortcut,
createDriveFolderPath,
processFilenameReplacements,
updateDriveFile,
)
from gam.cmd.drive.copymove import ( # noqa: F401
COPY_NONINHERITED_PERMISSIONS_ALWAYS,
COPY_NONINHERITED_PERMISSIONS_CHOICES_MAP,
COPY_NONINHERITED_PERMISSIONS_NEVER,
COPY_NONINHERITED_PERMISSIONS_SYNC_ALL_FOLDERS,
COPY_NONINHERITED_PERMISSIONS_SYNC_UPDATED_FOLDERS,
COPY_OWNED_BY_CHOICE_MAP,
DELETE_DRIVEFILE_CHOICE_MAP,
DELETE_DRIVEFILE_FUNCTION_TO_ACTION_MAP,
DELETE_DRIVEFILE_FUNCTION_TO_CAPABILITY_MAP,
DEST_PARENT_MYDRIVE_FOLDER,
DEST_PARENT_MYDRIVE_ROOT,
DEST_PARENT_SHAREDDRIVE_FOLDER,
DEST_PARENT_SHAREDDRIVE_ROOT,
DUPLICATE_FILE_CHOICES,
DUPLICATE_FILE_DUPLICATE_NAME,
DUPLICATE_FILE_OVERWRITE_ALL,
DUPLICATE_FILE_OVERWRITE_OLDER,
DUPLICATE_FILE_SKIP,
DUPLICATE_FILE_UNIQUE_NAME,
DUPLICATE_FOLDER_CHOICES,
DUPLICATE_FOLDER_DUPLICATE_NAME,
DUPLICATE_FOLDER_MERGE,
DUPLICATE_FOLDER_SKIP,
DUPLICATE_FOLDER_UNIQUE_NAME,
UNIQUE_PREFIX_PATTERN,
_checkForDuplicateTargetFile,
_checkForExistingShortcut,
_copyPermissions,
_getCopyFolderNonInheritedPermissions,
_getCopyMoveParentInfo,
_getCopyMoveTargetInfo,
_getFilenameParts,
_getFilenamePrefix,
_getSheetProtectedRanges,
_getUniqueFilename,
_identicalSourceTarget,
_printStatistics,
_recursiveUpdateMovePermissions,
_targetFilenameExists,
_updateMoveFilePermissions,
_updateSheetProtectedRanges,
_updateSheetProtectedRangesACLchange,
_verifyUserIsOrganizer,
copyDriveFile,
getCopyMoveOptions,
initCopyMoveOptions,
moveDriveFile,
)
from gam.cmd.drive.transfer import ( # noqa: F401
DOCUMENT_FORMATS_MAP,
GOOGLEDOC_VALID_EXTENSIONS_MAP,
HTTP_ERROR_PATTERN,
MICROSOFT_FORMATS_LIST,
MIMETYPE_EXTENSION_MAP,
NON_DOWNLOADABLE_MIMETYPES,
OPENOFFICE_FORMATS_LIST,
SUGGESTIONS_VIEW_MODE_CHOICE_MAP,
TRANSFER_DRIVEFILE_ACL_ROLES_MAP,
claimOwnership,
collectOrphans,
deleteDriveFile,
getDriveFile,
getGoogleDocument,
getPermissionIdForEmail,
purgeDriveFile,
transferDrive,
transferOwnership,
trashDriveFile,
untrashDriveFile,
updateGoogleDocument,
validateUserGetPermissionId,
)
from gam.cmd.drive.permissions import ( # noqa: F401
DRIVELABELS_PERMISSION_ROLE_MAP,
DRIVELABELS_PROJECTION_CHOICE_MAP,
DRIVELABELS_TIME_OBJECTS,
_checkFileIdEntityDomainAccess,
_getDriveFileACLPrintKeysTimeObjects,
_showDriveFilePermission,
_showDriveFilePermissionJSON,
_showDriveFilePermissions,
_showDriveFilePermissionsJSON,
createDriveFileACL,
createDriveFilePermissions,
deleteDriveFileACLs,
deleteEmptyDriveFolders,
deletePermissions,
doCreateDriveFileACL,
doCreatePermissions,
doDeleteDriveFileACLs,
doDeletePermissions,
doInfoDriveFileACLs,
doPrintShowDriveFileACLs,
doUpdateDriveFileACLs,
emptyDriveTrash,
getDriveFilePermissionsFields,
infoDriveFileACLs,
printEmptyDriveFolders,
printShowDriveFileACLs,
updateDriveFileACLs,
)
from gam.cmd.drive.labels import ( # noqa: F401
DRIVELABEL_FIELD_TYPE_MAP,
_getDisplayDriveLabelsParameters,
_showDriveLabel,
_showDriveLabelPermission,
createDriveLabelPermissions,
deleteDriveLabelPermissions,
doCreateDriveLabelPermissions,
doDeleteDriveLabelPermissions,
doInfoDriveLabels,
doPrintShowDriveLabelPermissions,
doPrintShowDriveLabels,
infoDriveLabels,
normalizeDriveLabelID,
normalizeDriveLabelName,
printShowDriveLabelPermissions,
printShowDriveLabels,
processFileDriveLabels,
validateDriveLabelName,
)
from gam.cmd.drive.shareddrives import ( # noqa: F401
PRINT_ORGANIZER_TYPES,
SHAREDDRIVE_ACL_ROLES_MAP,
SHAREDDRIVE_API_GUI_ROLES_MAP,
SHAREDDRIVE_DOWNLOAD_RESTRICTIONS_MAP,
SHAREDDRIVE_FIELDS_CHOICE_MAP,
SHAREDDRIVE_LIST_FIELDS_CHOICE_MAP,
SHAREDDRIVE_RESTRICTIONS_MAP,
SHAREDDRIVE_ROLES_CAPABILITIES_MAP,
SHAREDDRIVE_TIME_OBJECTS,
SHOWWEBVIEWLINK_CHOICES,
SHOW_NO_PERMISSIONS_DRIVES_CHOICE_MAP,
_checkSharedDriveRestrictions,
_getSharedDriveRestrictions,
_getSharedDriveRole,
_getSharedDriveTheme,
_moveSharedDriveToOU,
_showSharedDrive,
copySyncSharedDriveACLs,
createSharedDrive,
deleteSharedDrive,
doCopySyncSharedDriveACLs,
doCreateSharedDrive,
doDeleteSharedDrive,
doHideUnhideSharedDrive,
doInfoSharedDrive,
doPrintSharedDriveOrganizers,
doPrintShowOrgunitSharedDrives,
doPrintShowOwnership,
doPrintShowSharedDriveACLs,
doPrintShowSharedDrives,
doUpdateSharedDrive,
hideUnhideSharedDrive,
infoSharedDrive,
printSharedDriveOrganizers,
printShowSharedDriveACLs,
printShowSharedDrives,
updateSharedDrive,
)
from gam.cmd.drive.looker import ( # noqa: F401
LOOKERSTUDIO_ADD_UPDATE_PERMISSION_ROLE_CHOICE_MAP,
LOOKERSTUDIO_ASSETS_ORDERBY_CHOICE_MAP,
LOOKERSTUDIO_ASSETS_TIME_OBJECTS,
LOOKERSTUDIO_ASSETTYPE_CHOICE_MAP,
LOOKERSTUDIO_DELETE_PERMISSION_ROLE_CHOICE_MAP,
LOOKERSTUDIO_PERMISSION_MODIFIER_MAP,
LOOKERSTUDIO_VIEW_PERMISSION_ROLE_CHOICE_MAP,
_getLookerStudioAssetByID,
_getLookerStudioAssets,
_showLookerStudioPermissions,
_validateUserGetLookerStudioAssetIds,
getLookerStudioAssetSelectionParameters,
initLookerStudioAssetSelectionParameters,
printShowLookerStudioAssets,
)

View File

@@ -0,0 +1,496 @@
"""Drive activity reporting and settings display.
Part of the drive sub-package, extracted from drive.py."""
"""GAM Google Drive file, permission, shared drive, and label management."""
import re
import json
import sys
from gamlib import glaction
from gamlib import glapi as API
from gamlib import glcfg as GC
from gamlib import glclargs
from gamlib import glentity
from gamlib import glgapi as GAPI
from gamlib import glglobals as GM
from gamlib import glindent
from gamlib import glmsgs as Msg
Act = glaction.GamAction()
Ent = glentity.GamEntity()
Ind = glindent.GamIndent()
Cmd = glclargs.GamCLArgs()
APPLICATION_VND_GOOGLE_APPS = 'application/vnd.google-apps.'
MIMETYPE_GA_DOCUMENT = f'{APPLICATION_VND_GOOGLE_APPS}document'
MIMETYPE_GA_DRAWING = f'{APPLICATION_VND_GOOGLE_APPS}drawing'
MIMETYPE_GA_FILE = f'{APPLICATION_VND_GOOGLE_APPS}file'
MIMETYPE_GA_FOLDER = f'{APPLICATION_VND_GOOGLE_APPS}folder'
MIMETYPE_GA_FORM = f'{APPLICATION_VND_GOOGLE_APPS}form'
MIMETYPE_GA_FUSIONTABLE = f'{APPLICATION_VND_GOOGLE_APPS}fusiontable'
MIMETYPE_GA_JAM = f'{APPLICATION_VND_GOOGLE_APPS}jam'
MIMETYPE_GA_MAP = f'{APPLICATION_VND_GOOGLE_APPS}map'
MIMETYPE_GA_PRESENTATION = f'{APPLICATION_VND_GOOGLE_APPS}presentation'
MIMETYPE_GA_SCRIPT = f'{APPLICATION_VND_GOOGLE_APPS}script'
MIMETYPE_GA_SCRIPT_JSON = f'{APPLICATION_VND_GOOGLE_APPS}script+json'
MIMETYPE_GA_SHORTCUT = f'{APPLICATION_VND_GOOGLE_APPS}shortcut'
MIMETYPE_GA_3P_SHORTCUT = f'{APPLICATION_VND_GOOGLE_APPS}drive-sdk'
MIMETYPE_GA_SITE = f'{APPLICATION_VND_GOOGLE_APPS}site'
MIMETYPE_GA_SPREADSHEET = f'{APPLICATION_VND_GOOGLE_APPS}spreadsheet'
ME_IN_OWNERS = "'me' in owners"
ME_IN_OWNERS_AND = ME_IN_OWNERS + " and "
NOT_ME_IN_OWNERS = "not " + ME_IN_OWNERS
NOT_ME_IN_OWNERS_AND = NOT_ME_IN_OWNERS + " and "
WITH_ANY_FILE_NAME = "name = '{0}'"
WITH_MY_FILE_NAME = ME_IN_OWNERS_AND + WITH_ANY_FILE_NAME
WITH_OTHER_FILE_NAME = NOT_ME_IN_OWNERS_AND + WITH_ANY_FILE_NAME
ROOT = 'root'
ORPHANS = 'Orphans'
SHARED_WITHME = 'SharedWithMe'
SHARED_DRIVES = 'SharedDrives'
def _getMain():
return sys.modules['gam']
def __getattr__(name):
"""Fall back to gam module for any undefined names."""
main = _getMain()
try:
return getattr(main, name)
except AttributeError:
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
def printDriveActivity(users):
def _getUserInfo(userId):
if userId.startswith('people/'):
userId = userId[7:]
entry = userInfo.get(userId)
if entry is None:
try:
result = _getMain().callGAPI(cd.users(), 'get',
throwReasons=GAPI.USER_GET_THROW_REASONS+[GAPI.INVALID_INPUT],
userKey=userId, fields='primaryEmail,name.fullName')
entry = (result['primaryEmail'], result['name']['fullName'])
except (GAPI.userNotFound, GAPI.domainNotFound, GAPI.domainCannotUseApis, GAPI.forbidden,
GAPI.badRequest, GAPI.backendError, GAPI.systemError, GAPI.invalidInput):
entry = (f'uid:{userId}', _getMain().UNKNOWN)
userInfo[userId] = entry
return entry
def _updateKnownUsers(structure):
if isinstance(structure, list):
for v in structure:
if isinstance(v, (dict, list)):
_updateKnownUsers(v)
elif isinstance(structure, dict):
for k, v in sorted(structure.items()):
if k != 'knownUser':
if isinstance(v, (dict, list)):
_updateKnownUsers(v)
else:
entry = _getUserInfo(v['personName'])
v['emailAddress'] = entry[0]
v['personName'] = entry[1]
break
cd = _getMain().buildGAPIObject(API.DIRECTORY)
startEndTime = _getMain().StartEndTime()
baseFileList = []
query = ''
activityFilter = ''
actions = set()
strategy = 'none'
negativeAction = stripCRsFromName = False
maxActivities = 0
_getMain().checkArgumentPresent(['v2'])
csvPF = _getMain().CSVPrintFile([f'user{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}name',
f'user{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}emailAddress',
f'target{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}id',
f'target{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}name',
f'target{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}mimeType',
'eventTime'],
'sortall')
FJQC = _getMain().FormatJSONQuoteChar(csvPF)
userInfo = {}
while Cmd.ArgumentsRemaining():
myarg = _getMain().getArgument()
if myarg == 'todrive':
csvPF.GetTodriveParameters()
elif myarg == 'fileid':
baseFileList.append({'id': getString(Cmd.OB_DRIVE_FILE_ID), 'mimeType': MIMETYPE_GA_DOCUMENT})
elif myarg == 'folderid':
baseFileList.append({'id': getString(Cmd.OB_DRIVE_FOLDER_ID), 'mimeType': MIMETYPE_GA_FOLDER})
elif myarg == 'drivefilename':
query = f"mimeType != '{MIMETYPE_GA_FOLDER}' and name = '{getEscapedDriveFileName()}'"
elif myarg == 'drivefoldername':
query = f"mimeType = '{MIMETYPE_GA_FOLDER}' and name = '{getEscapedDriveFileName()}'"
elif myarg == 'query':
query = _mapDrive2QueryToDrive3(_getMain().getString(Cmd.OB_QUERY))
elif myarg in {'start', 'starttime', 'end', 'endtime', 'yesterday', 'today', 'range', 'thismonth', 'previousmonths'}:
startEndTime.Get(myarg)
elif myarg in {'action', 'actions'}:
negativeAction = _getMain().checkArgumentPresent('not')
for action in _getMain()._getFieldsList():
if action in DRIVE_ACTIVITY_ACTION_MAP:
mappedAction = DRIVE_ACTIVITY_ACTION_MAP[action]
if mappedAction:
actions.add(mappedAction)
else:
_getMain().invalidChoiceExit(action, DRIVE_ACTIVITY_ACTION_MAP, True)
elif myarg in {'allevents', 'combinedevents', 'singleevents'}:
pass
elif myarg in {'consolidationstrategy', 'groupingstrategy'}:
strategy = _getMain().getChoice(CONSOLIDATION_GROUPING_STRATEGY_CHOICE_MAP, mapChoice=True)
elif myarg == 'idmapfile':
f, csvFile, _ = _getMain().openCSVFileReader(_getMain().getString(Cmd.OB_FILE_NAME))
for row in csvFile:
userInfo[row['id']] = (row['primaryEmail'], row.get('name.fullName', _getMain().UNKNOWN))
_getMain().closeFile(f)
elif myarg == 'stripcrsfromname':
stripCRsFromName = True
elif myarg == 'maxactivities':
maxActivities = _getMain().getInteger(minVal=0)
else:
FJQC.GetFormatJSONQuoteChar(myarg, True)
if not baseFileList and not query:
baseFileList = [{'id': ROOT, 'mimeType': MIMETYPE_GA_FOLDER}]
if startEndTime.startTime:
if activityFilter:
activityFilter += ' AND '
activityFilter += f'time >= "{startEndTime.startTime}"'
if startEndTime.endTime:
if activityFilter:
activityFilter += ' AND '
activityFilter += f'time <= "{startEndTime.endTime}"'
if actions:
if activityFilter:
activityFilter += ' AND '
activityFilter += f'{"-" if negativeAction else ""}detail.action_detail_case:({" ".join(actions)})'
i, count, users = _getMain().getEntityArgument(users)
for user in users:
i += 1
user, activity = _getMain().buildGAPIServiceObject(API.DRIVEACTIVITY, user, i, count)
if not activity:
continue
_, drive = _getMain().buildGAPIServiceObject(API.DRIVE3, user, i, count)
if not drive:
continue
fileList = baseFileList[:]
if query:
if GC.Values[GC.SHOW_GETTINGS]:
_getMain().printGettingAllEntityItemsForWhom(Ent.DRIVE_FILE_OR_FOLDER, user, i, count, query=query)
try:
fileList.extend(_getMain().callGAPIpages(drive.files(), 'list', 'files',
pageMessage=_getMain().getPageMessageForWhom(),
throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.INVALID_QUERY, GAPI.INVALID,
GAPI.BAD_REQUEST, GAPI.FILE_NOT_FOUND],
retryReasons=[GAPI.UNKNOWN_ERROR],
q=query, fields='nextPageToken,files(id,mimeType)', pageSize=GC.Values[GC.DRIVE_MAX_RESULTS]))
if not fileList:
_getMain().entityActionNotPerformedWarning([Ent.USER, user, Ent.DRIVE_FILE, None], _getMain().emptyQuery(query, Ent.DRIVE_FILE_OR_FOLDER), i, count)
continue
except (GAPI.invalidQuery, GAPI.invalid, GAPI.badRequest):
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.DRIVE_FILE, None], _getMain().invalidQuery(query), i, count)
break
except GAPI.fileNotFound:
_getMain().printGotEntityItemsForWhom(0)
continue
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
continue
for f_file in fileList:
fileId = f_file['id']
entityType = Ent.DRIVE_FOLDER_ID if f_file['mimeType'] == MIMETYPE_GA_FOLDER else Ent.DRIVE_FILE_ID
if entityType == Ent.DRIVE_FILE_ID:
drive_key = 'itemName'
else:
drive_key = 'ancestorName'
qualifier = f' for {Ent.Singular(entityType)}: {fileId}'
_getMain().printGettingAllEntityItemsForWhom(Ent.ACTIVITY, user, i, count, qualifier=qualifier)
pageMessage = _getMain().getPageMessageForWhom()
body = {
'consolidationStrategy': {strategy: {}},
'pageSize': GC.Values[GC.ACTIVITY_MAX_RESULTS],
'pageToken': None,
drive_key: f'items/{fileId}',
'filter': activityFilter}
numActivities = 0
try:
feed = _getMain().yieldGAPIpages(activity.activity(), 'query', 'activities',
pageMessage=pageMessage, maxItems=maxActivities,
throwReasons=GAPI.ACTIVITY_THROW_REASONS,
fields='nextPageToken,activities', body=body, pageArgsInBody=True)
for activities in feed:
for activityEvent in activities:
eventRow = {}
actors = activityEvent.get('actors', [])
if actors:
userId = actors[0].get('user', {}).get('knownUser', {}).get('personName', '')
if not userId:
userId = actors[0].get('impersonation', {}).get('impersonatedUser', {}).get('knownUser', {}).get('personName', '')
if userId:
entry = _getUserInfo(userId)
eventRow[f'user{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}name'] = entry[1]
eventRow[f'user{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}emailAddress'] = entry[0]
targets = activityEvent.get('targets', [])
if targets:
driveItem = targets[0].get('driveItem')
if driveItem:
eventRow[f'target{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}id'] = driveItem['name'][6:]
if stripCRsFromName:
driveItem['title'] = _stripControlCharsFromName(driveItem['title'])
eventRow[f'target{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}name'] = driveItem['title']
eventRow[f'target{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}mimeType'] = driveItem['mimeType']
else:
sharedDrive = targets[0].get('teamDrive')
if sharedDrive:
eventRow[f'target{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}id'] = sharedDrive['name'][11:]
if stripCRsFromName:
sharedDrive['title'] = _stripControlCharsFromName(sharedDrive['title'])
eventRow[f'target{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}name'] = sharedDrive['title']
if 'timestamp' in activityEvent:
eventRow['eventTime'] = formatLocalTime(activityEvent['timestamp'])
elif 'timeRange' in activityEvent:
timeRange = activityEvent['timeRange']
eventRow['eventTime'] = f'{formatLocalTime(timeRange["startTime"])}-{formatLocalTime(timeRange["endTime"])}'
_updateKnownUsers(activityEvent)
if not FJQC.formatJSON:
activityEvent.pop('timestamp', None)
activityEvent.pop('timeRange', None)
_getMain().flattenJSON(activityEvent, flattened=eventRow)
csvPF.WriteRowTitles(eventRow)
else:
checkRow = eventRow.copy()
_getMain().flattenJSON(activityEvent, flattened=checkRow)
if csvPF.CheckRowTitles(checkRow):
eventRow['JSON'] = json.dumps(_getMain().cleanJSON(activityEvent), ensure_ascii=False, sort_keys=True)
csvPF.WriteRowNoFilter(eventRow)
numActivities += 1
if maxActivities and numActivities >= maxActivities:
break
except GAPI.badRequest as e:
_getMain().entityActionFailedWarning([Ent.USER, user, entityType, fileId], str(e), i, count)
continue
except GAPI.serviceNotAvailable as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
continue
csvPF.writeCSVfile('Drive Activity')
DRIVESETTINGS_FIELDS_CHOICE_MAP = {
'appinstalled': 'appInstalled',
'exportformats': 'exportFormats',
'foldercolorpalette': 'folderColorPalette',
'importformats': 'importFormats',
'largestchangeid': 'largestChangeId',
'limit': 'limit',
'maximportsizes': 'maxImportSizes',
'maxuploadsize': 'maxUploadSize',
'name': 'name',
'permissionid': 'permissionId',
'rootfolderid': 'rootFolderId',
'drivethemes': 'driveThemes',
'shareddrivethemes': 'driveThemes',
'teamdrivethemes': 'driveThemes',
'usage': 'usage',
'usageindrive': 'usageInDrive',
'usageindrivetrash': 'usageInDriveTrash',
}
DRIVESETTINGS_SCALAR_FIELDS = [
'name',
'appInstalled',
'largestChangeId',
'limit',
'maxUploadSize',
'permissionId',
'rootFolderId',
'usage',
'usageInDrive',
'usageInDriveTrash',
]
DRIVESETTINGS_USAGE_BYTES_FIELDS = {
'usage': 'usageBytes',
'usageInDrive': 'usageInDriveBytes',
'usageInDriveTrash': 'usageInDriveTrashBytes',
}
def _showSharedDriveThemeSettings(themes):
Ind.Increment()
for theme in themes:
_getMain().printKeyValueList(['id', theme['id']])
Ind.Increment()
_getMain().printKeyValueList(['backgroundImageLink', theme['backgroundImageLink']])
_getMain().printKeyValueList(['colorRgb', theme['colorRgb']])
Ind.Decrement()
Ind.Decrement()
# gam <UserTypeEntity> print drivesettings [todrive <ToDriveAttribute>*]
# [allfields|<DriveSettingsFieldName>*|(fields <DriveSettingsFieldNameList>)]
# [delimiter <Character>] [showusagebytes]
# gam <UserTypeEntity> show drivesettings
# [allfields|<DriveSettingsFieldName>*|(fields <DriveSettingsFieldNameList>)]
# [delimiter <Character>] [showusagebytes]
def printShowDriveSettings(users):
def _showFormats(title):
if title in fieldsList and title in feed:
_getMain().printKeyValueList([title, None])
Ind.Increment()
for item, value in sorted(feed[title].items()):
_getMain().printKeyValueList([item, delimiter.join(value)])
Ind.Decrement()
def _showSetting(title):
if title in fieldsList and title in feed:
if not isinstance(feed[title], list):
_getMain().printKeyValueList([title, feed[title]])
else:
_getMain().printKeyValueList([title, delimiter.join(feed[title])])
def _addFormats(row, title):
if title in fieldsList and title in feed:
jcount = len(feed[title])
row[title] = jcount
j = 0
for item, value in sorted(feed[title].items()):
row[f'{title}{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}{j:02d}{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}{item}'] = delimiter.join(value)
j += 1
def _addSetting(row, title):
if title in fieldsList and title in feed:
if not isinstance(feed[title], list):
row[title] = feed[title]
else:
row[title] = delimiter.join(feed[title])
csvPF = _getMain().CSVPrintFile(['email'], ['email']+DRIVESETTINGS_SCALAR_FIELDS) if Act.csvFormat() else None
fieldsList = []
delimiter = GC.Values[GC.CSV_OUTPUT_FIELD_DELIMITER]
showUsageBytes = False
while Cmd.ArgumentsRemaining():
myarg = _getMain().getArgument()
if csvPF and myarg == 'todrive':
csvPF.GetTodriveParameters()
elif myarg == 'delimiter':
delimiter = _getMain().getCharacter()
elif myarg == 'allfields':
fieldsList.extend(DRIVESETTINGS_FIELDS_CHOICE_MAP.values())
elif _getMain().getFieldsList(myarg, DRIVESETTINGS_FIELDS_CHOICE_MAP, fieldsList):
pass
elif myarg == 'showusagebytes':
showUsageBytes = True
else:
_getMain().unknownArgumentExit()
if not fieldsList:
fieldsList = DRIVESETTINGS_SCALAR_FIELDS[:]
i, count, users = _getMain().getEntityArgument(users)
for user in users:
i += 1
user, drive = _getMain().buildGAPIServiceObject(API.DRIVE3, user, i, count)
if not drive:
continue
if csvPF:
_getMain().printGettingEntityItemForWhom(Ent.DRIVE_SETTINGS, user, i, count)
try:
feed = _getMain().callGAPI(drive.about(), 'get',
throwReasons=GAPI.DRIVE_USER_THROW_REASONS,
fields='*')
feed['name'] = feed['user']['displayName']
feed['maxUploadSize'] = formatFileSize(int(feed['maxUploadSize']))
feed['permissionId'] = feed['user']['permissionId']
if 'limit' in feed['storageQuota']:
feed['limit'] = formatFileSize(int(feed['storageQuota']['limit']))
else:
feed['limit'] = 'UNLIMITED'
for setting in ['usage', 'usageInDrive', 'usageInDriveTrash']:
uval = int(feed['storageQuota'].get(setting, '0'))
feed[setting] = _getMain().formatFileSize(uval)
if showUsageBytes:
feed[DRIVESETTINGS_USAGE_BYTES_FIELDS[setting]] = uval
if 'rootFolderId' in fieldsList:
feed['rootFolderId'] = _getMain().callGAPI(drive.files(), 'get',
throwReasons=GAPI.DRIVE_USER_THROW_REASONS,
fileId=ROOT, fields='id')['id']
if 'largestChangeId' in fieldsList:
feed['largestChangeId'] = _getMain().callGAPI(drive.changes(), 'getStartPageToken',
throwReasons=GAPI.DRIVE_USER_THROW_REASONS,
fields='startPageToken')['startPageToken']
if not csvPF:
_getMain().entityPerformActionNumItems([Ent.USER, user], 1, Ent.DRIVE_SETTINGS, i, count)
Ind.Increment()
for setting in DRIVESETTINGS_SCALAR_FIELDS:
_showSetting(setting)
if showUsageBytes:
for title, setting in DRIVESETTINGS_USAGE_BYTES_FIELDS.items():
if title in fieldsList and setting in feed:
_getMain().printKeyValueList([setting, feed[setting]])
_showSetting('folderColorPalette')
_showFormats('exportFormats')
_showFormats('importFormats')
if 'maxImportSizes' in fieldsList and 'maxImportSizes' in fieldsList:
_getMain().printKeyValueList(['maxImportSizes', None])
Ind.Increment()
for setting, value in feed['maxImportSizes'].items():
_getMain().printKeyValueList([setting, _getMain().formatFileSize(int(value))])
Ind.Decrement()
if 'driveThemes' in fieldsList and 'driveThemes' in feed:
_getMain().printKeyValueList(['driveThemes', None])
_showSharedDriveThemeSettings(feed['driveThemes'])
Ind.Decrement()
else:
row = {'email': user}
for setting in DRIVESETTINGS_SCALAR_FIELDS:
_addSetting(row, setting)
if showUsageBytes:
for title, setting in DRIVESETTINGS_USAGE_BYTES_FIELDS.items():
if title in fieldsList and setting in feed:
row[setting] = feed[setting]
_addSetting(row, 'folderColorPalette')
_addFormats(row, 'exportFormats')
_addFormats(row, 'importFormats')
if 'maxImportSizes' in fieldsList and 'maxImportSizes' in fieldsList:
jcount = len(feed['maxImportSizes'])
row['maxImportSizes'] = jcount
j = 0
for setting, value in feed['maxImportSizes'].items():
row[f'maxImportSizes{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}{j}{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}{setting}'] = _getMain().formatFileSize(int(value))
j += 1
if 'driveThemes' in fieldsList and 'driveThemes' in feed:
jcount = len(feed['driveThemes'])
row['driveThemes'] = jcount
j = 0
for setting in feed['driveThemes']:
row[f'driveThemes{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}{j:02d}{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}id'] = setting['id']
row[f'driveThemes{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}{j:02d}{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}backgroundImageLink'] = setting['backgroundImageLink']
row[f'driveThemes{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}{j:02d}{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}colorRgb'] = setting['colorRgb']
j += 1
csvPF.WriteRowTitles(row)
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
if csvPF:
csvPF.writeCSVfile('User Drive Settings')
# gam <UserTypeEntity> show shareddrivethemes
def showSharedDriveThemes(users):
_getMain().checkForExtraneousArguments()
i, count, users = _getMain().getEntityArgument(users)
for user in users:
i += 1
user, drive = _getMain().buildGAPIServiceObject(API.DRIVE3, user, i, count)
if not drive:
continue
try:
themes = _getMain().callGAPIitems(drive.about(), 'get', 'driveThemes',
throwReasons=GAPI.DRIVE_USER_THROW_REASONS,
fields='driveThemes')
jcount = len(themes)
_getMain().entityPerformActionNumItems([Ent.USER, user], jcount, Ent.SHAREDDRIVE_THEME, i, count)
_showSharedDriveThemeSettings(themes)
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
def doShowSharedDriveThemes():
showSharedDriveThemes([_getMain()._getAdminEmail()])

View File

@@ -0,0 +1,56 @@
"""Copymove sub-package.
Re-exports all symbols from sub-modules for backward compatibility."""
from gam.cmd.drive.copymove.copymove_util import ( # noqa: F401
COPY_NONINHERITED_PERMISSIONS_ALWAYS,
COPY_NONINHERITED_PERMISSIONS_CHOICES_MAP,
COPY_NONINHERITED_PERMISSIONS_NEVER,
COPY_NONINHERITED_PERMISSIONS_SYNC_ALL_FOLDERS,
COPY_NONINHERITED_PERMISSIONS_SYNC_UPDATED_FOLDERS,
COPY_OWNED_BY_CHOICE_MAP,
DEST_PARENT_MYDRIVE_FOLDER,
DEST_PARENT_MYDRIVE_ROOT,
DEST_PARENT_SHAREDDRIVE_FOLDER,
DEST_PARENT_SHAREDDRIVE_ROOT,
DUPLICATE_FILE_CHOICES,
DUPLICATE_FILE_DUPLICATE_NAME,
DUPLICATE_FILE_OVERWRITE_ALL,
DUPLICATE_FILE_OVERWRITE_OLDER,
DUPLICATE_FILE_SKIP,
DUPLICATE_FILE_UNIQUE_NAME,
DUPLICATE_FOLDER_CHOICES,
DUPLICATE_FOLDER_DUPLICATE_NAME,
DUPLICATE_FOLDER_MERGE,
DUPLICATE_FOLDER_SKIP,
DUPLICATE_FOLDER_UNIQUE_NAME,
UNIQUE_PREFIX_PATTERN,
_checkForDuplicateTargetFile,
_checkForExistingShortcut,
_copyPermissions,
_getCopyFolderNonInheritedPermissions,
_getCopyMoveParentInfo,
_getCopyMoveTargetInfo,
_getFilenameParts,
_getFilenamePrefix,
_getSheetProtectedRanges,
_getUniqueFilename,
_identicalSourceTarget,
_printStatistics,
_targetFilenameExists,
_updateSheetProtectedRanges,
_updateSheetProtectedRangesACLchange,
_verifyUserIsOrganizer,
copyDriveFile,
copyReturnItemMap,
getCopyMoveOptions,
initCopyMoveOptions,
)
from gam.cmd.drive.copymove.copymove_move import ( # noqa: F401
DELETE_DRIVEFILE_CHOICE_MAP,
DELETE_DRIVEFILE_FUNCTION_TO_ACTION_MAP,
DELETE_DRIVEFILE_FUNCTION_TO_CAPABILITY_MAP,
_recursiveUpdateMovePermissions,
_updateMoveFilePermissions,
moveDriveFile,
)

View File

@@ -0,0 +1,834 @@
"""Move Drive file operations and permission updates.
Part of the copymove sub-package."""
"""File copy/move operations with permission handling.
Part of the drive sub-package, extracted from drive.py."""
"""GAM Google Drive file, permission, shared drive, and label management."""
import re
import sys
from gamlib import glaction
from gamlib import glapi as API
from gamlib import glcfg as GC
from gamlib import glclargs
from gamlib import glentity
from gamlib import glgapi as GAPI
from gamlib import glglobals as GM
from gamlib import glindent
from gamlib import glmsgs as Msg
Act = glaction.GamAction()
Ent = glentity.GamEntity()
Ind = glindent.GamIndent()
Cmd = glclargs.GamCLArgs()
APPLICATION_VND_GOOGLE_APPS = 'application/vnd.google-apps.'
MIMETYPE_GA_DOCUMENT = f'{APPLICATION_VND_GOOGLE_APPS}document'
MIMETYPE_GA_DRAWING = f'{APPLICATION_VND_GOOGLE_APPS}drawing'
MIMETYPE_GA_FILE = f'{APPLICATION_VND_GOOGLE_APPS}file'
MIMETYPE_GA_FOLDER = f'{APPLICATION_VND_GOOGLE_APPS}folder'
MIMETYPE_GA_FORM = f'{APPLICATION_VND_GOOGLE_APPS}form'
MIMETYPE_GA_FUSIONTABLE = f'{APPLICATION_VND_GOOGLE_APPS}fusiontable'
MIMETYPE_GA_JAM = f'{APPLICATION_VND_GOOGLE_APPS}jam'
MIMETYPE_GA_MAP = f'{APPLICATION_VND_GOOGLE_APPS}map'
MIMETYPE_GA_PRESENTATION = f'{APPLICATION_VND_GOOGLE_APPS}presentation'
MIMETYPE_GA_SCRIPT = f'{APPLICATION_VND_GOOGLE_APPS}script'
MIMETYPE_GA_SCRIPT_JSON = f'{APPLICATION_VND_GOOGLE_APPS}script+json'
MIMETYPE_GA_SHORTCUT = f'{APPLICATION_VND_GOOGLE_APPS}shortcut'
MIMETYPE_GA_3P_SHORTCUT = f'{APPLICATION_VND_GOOGLE_APPS}drive-sdk'
MIMETYPE_GA_SITE = f'{APPLICATION_VND_GOOGLE_APPS}site'
MIMETYPE_GA_SPREADSHEET = f'{APPLICATION_VND_GOOGLE_APPS}spreadsheet'
ME_IN_OWNERS = "'me' in owners"
ME_IN_OWNERS_AND = ME_IN_OWNERS + " and "
NOT_ME_IN_OWNERS = "not " + ME_IN_OWNERS
NOT_ME_IN_OWNERS_AND = NOT_ME_IN_OWNERS + " and "
WITH_ANY_FILE_NAME = "name = '{0}'"
WITH_MY_FILE_NAME = ME_IN_OWNERS_AND + WITH_ANY_FILE_NAME
WITH_OTHER_FILE_NAME = NOT_ME_IN_OWNERS_AND + WITH_ANY_FILE_NAME
ROOT = 'root'
ORPHANS = 'Orphans'
SHARED_WITHME = 'SharedWithMe'
SHARED_DRIVES = 'SharedDrives'
def _getMain():
return sys.modules['gam']
def __getattr__(name):
"""Fall back to gam module for any undefined names."""
main = _getMain()
try:
return getattr(main, name)
except AttributeError:
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
MY_DRIVE = 'My Drive'
TEAM_DRIVE = 'Drive'
def _updateMoveFilePermissions(drive, user, i, count,
entityType, fileId, fileTitle,
statistics, stat, copyMoveOptions):
def getPermissions(fid):
permissions = {}
try:
result = _getMain().callGAPIpages(drive.permissions(), 'list', 'permissions',
throwReasons=GAPI.DRIVE3_GET_ACL_REASONS,
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
fileId=fid,
fields='nextPageToken,permissions(allowFileDiscovery,domain,emailAddress,expirationTime,id,role,type,deleted,view,pendingOwner,permissionDetails)',
useDomainAdminAccess=copyMoveOptions['useDomainAdminAccess'], supportsAllDrives=True)
for permission in result:
permission.pop('teamDrivePermissionDetails', None)
if not permission.pop('permissionDetails', [{'inherited': False}])[0]['inherited']:
permissions[permission['id']] = permission
return permissions
except (GAPI.fileNotFound, GAPI.forbidden, GAPI.internalError,
GAPI.insufficientAdministratorPrivileges, GAPI.insufficientFilePermissions,
GAPI.unknownError, GAPI.invalid):
pass
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
_incrStatistic(statistics, stat)
return None
def permissionKVList(user, entityType, title, permission):
permstr = f"noninherited/{permission['role']}/{permission['type']}"
if permission['type'] in {'group', 'user'}:
permstr += f"/{permission['emailAddress']}"
elif permission['type'] == 'domain':
permstr += f"/{permission['domain']}"
return [Ent.USER, user, entityType, title, Ent.PERMISSION, permstr]
def isPermissionDeletable(kvList, permission):
if not copyMoveOptions['moveFilePermissions']:
notMovedMessage = 'movefilepermissions false'
elif permission.pop('deleted', False):
notMovedMessage = f"{permission['type']} {permission['emailAddress']} deleted"
elif copyMoveOptions['excludePermissionsFromDomains'] or copyMoveOptions['includePermissionsFromDomains']:
domain = ''
if permission['type'] in {'group', 'user'}:
atLoc = permission.get('emailAddress', '').find('@')
if atLoc > 0:
domain = permission['emailAddress'][atLoc+1:].lower()
elif permission['type'] == 'domain':
domain = permission.get('domain', '').lower()
if domain and domain in copyMoveOptions['excludePermissionsFromDomains']:
notMovedMessage = f'domain {domain} excluded'
elif domain and copyMoveOptions['includePermissionsFromDomains'] and domain not in copyMoveOptions['includePermissionsFromDomains']:
notMovedMessage = f'domain {domain} not included'
else:
return False
else:
return False
deleteSourcePerms[permission['id']] = permission.copy()
if copyMoveOptions['showPermissionMessages']:
_getMain().entityActionNotPerformedWarning(kvList, notMovedMessage, 0, 0)
return True
def mapPermissionsDomains(kvList, permission):
if 'emailAddress' in permission:
sourceEmail = permission['emailAddress'].lower()
destEmail = copyMoveOptions['mapPermissionsEmails'].get(sourceEmail, None)
if destEmail:
deleteSourcePerms[permission['id']] = permission.copy()
if copyMoveOptions['showPermissionMessages']:
notMovedMessage = f"email {sourceEmail} mapped to {destEmail}"
_getMain().entityActionNotPerformedWarning(kvList, notMovedMessage, 0, 0)
permission['emailAddress'] = destEmail
addSourcePerms[permission['id']] = permission
return True
email, domain = sourceEmail.split('@', 1)
if domain in copyMoveOptions['mapPermissionsDomains']:
destEmail = f"{email}@{copyMoveOptions['mapPermissionsDomains'][domain]}"
deleteSourcePerms[permission['id']] = permission.copy()
if copyMoveOptions['showPermissionMessages']:
notMovedMessage = f"email {sourceEmail} mapped to {destEmail}"
_getMain().entityActionNotPerformedWarning(kvList, notMovedMessage, 0, 0)
permission['emailAddress'] = destEmail
addSourcePerms[permission['id']] = permission
return True
elif 'domain' in permission:
domain = permission['domain'].lower()
if domain in copyMoveOptions['mapPermissionsDomains']:
deleteSourcePerms[permission['id']] = permission.copy()
if copyMoveOptions['showPermissionMessages']:
notMovedMessage = f"domain {domain} mapped to {copyMoveOptions['mapPermissionsDomains'][domain]}"
_getMain().entityActionNotPerformedWarning(kvList, notMovedMessage, 0, 0)
permission['domain'] = copyMoveOptions['mapPermissionsDomains'][domain]
addSourcePerms[permission['id']] = permission
return True
return False
sourcePerms = getPermissions(fileId)
if sourcePerms is None:
return
deleteSourcePerms = {}
addSourcePerms = {}
for permissionId, permission in sourcePerms.items():
kvList = permissionKVList(user, entityType, fileTitle, permission)
if permission.get('permissionDetails', {}).get('inherited', False):
continue
if (not isPermissionDeletable(kvList, permission) and
(copyMoveOptions['mapPermissionsDomains'] or copyMoveOptions['mapPermissionsEmails'])):
mapPermissionsDomains(kvList, permission)
action = Act.Get()
kcount = len(deleteSourcePerms)
if kcount > 0:
Act.Set(Act.DELETE)
k = 0
for permissionId, permission in deleteSourcePerms.items():
k += 1
kvList = permissionKVList(user, entityType, fileTitle, permission)
try:
_getMain().callGAPI(drive.permissions(), 'delete',
throwReasons=GAPI.DRIVE_ACCESS_THROW_REASONS+GAPI.DRIVE3_DELETE_ACL_THROW_REASONS,
useDomainAdminAccess=copyMoveOptions['useDomainAdminAccess'],
fileId=fileId, permissionId=permissionId, supportsAllDrives=True)
if copyMoveOptions['showPermissionMessages']:
_getMain().entityActionPerformed(kvList, k, kcount)
except (GAPI.fileNotFound, GAPI.forbidden, GAPI.internalError, GAPI.insufficientFilePermissions, GAPI.unknownError, GAPI.invalid,
GAPI.badRequest, GAPI.notFound, GAPI.permissionNotFound, GAPI.cannotRemoveOwner,
GAPI.cannotModifyInheritedTeamDrivePermission, GAPI.cannotModifyInheritedPermission,
GAPI.insufficientAdministratorPrivileges, GAPI.sharingRateLimitExceeded, GAPI.cannotDeletePermission,
GAPI.fileNeverWritable) as e:
_getMain().entityActionFailedWarning(kvList, str(e), k, kcount)
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
_incrStatistic(statistics, stat)
Act.Set(action)
return
kcount = len(addSourcePerms)
if kcount > 0:
Act.Set(Act.CREATE)
k = 0
for permissionId, permission in addSourcePerms.items():
k += 1
kvList = permissionKVList(user, entityType, fileTitle, permission)
permission.pop('id')
if copyMoveOptions['destDriveId']:
permission.pop('pendingOwner', None)
sendNotificationEmail = False
while True:
try:
_getMain().callGAPI(drive.permissions(), 'create',
throwReasons=GAPI.DRIVE_ACCESS_THROW_REASONS+GAPI.DRIVE3_CREATE_ACL_THROW_REASONS,
# retryReasons=[GAPI.INVALID_SHARING_REQUEST],
fileId=fileId, sendNotificationEmail=sendNotificationEmail, emailMessage=None,
body=permission, fields='', useDomainAdminAccess=copyMoveOptions['useDomainAdminAccess'], supportsAllDrives=True)
if copyMoveOptions['showPermissionMessages']:
_getMain().entityActionPerformed(kvList, k, kcount)
break
except (GAPI.badRequest, GAPI.invalid, GAPI.fileNotFound, GAPI.forbidden, GAPI.internalError,
GAPI.insufficientFilePermissions, GAPI.unknownError, GAPI.ownershipChangeAcrossDomainNotPermitted,
GAPI.teamDriveDomainUsersOnlyRestriction, GAPI.teamDriveTeamMembersOnlyRestriction,
GAPI.targetUserRoleLimitedByLicenseRestriction, GAPI.insufficientAdministratorPrivileges, GAPI.sharingRateLimitExceeded,
GAPI.publishOutNotPermitted, GAPI.shareInNotPermitted, GAPI.shareOutNotPermitted, GAPI.shareOutNotPermittedToUser,
GAPI.cannotShareTeamDriveTopFolderWithAnyoneOrDomains, GAPI.cannotShareTeamDriveWithNonGoogleAccounts,
GAPI.ownerOnTeamDriveItemNotSupported,
GAPI.organizerOnNonTeamDriveNotSupported, GAPI.organizerOnNonTeamDriveItemNotSupported,
GAPI.fileOrganizerNotYetEnabledForThisTeamDrive,
GAPI.fileOrganizerOnFoldersInSharedDriveOnly,
GAPI.fileOrganizerOnNonTeamDriveNotSupported,
GAPI.cannotModifyInheritedPermission,
GAPI.teamDrivesFolderSharingNotSupported, GAPI.invalidLinkVisibility, GAPI.abusiveContentRestriction) as e:
_getMain().entityActionFailedWarning(kvList, str(e), k, kcount)
break
except GAPI.invalidSharingRequest as e:
if not copyMoveOptions['sendEmailIfRequired'] or sendNotificationEmail or 'You are trying to invite' not in str(e):
_getMain().entityActionFailedWarning(kvList, str(e), k, kcount)
break
sendNotificationEmail = True
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
_incrStatistic(statistics, stat)
Act.Set(action)
return
Act.Set(action)
def _recursiveUpdateMovePermissions(drive, user, i, count,
fileId, fileTitle,
statistics, copyMoveOptions, sourceSearchArgs):
_updateMoveFilePermissions(drive, user, i, count,
Ent.DRIVE_FOLDER, fileId, fileTitle,
statistics, STAT_FOLDER_PERMISSIONS_FAILED, copyMoveOptions)
sourceChildren = _getMain().callGAPIpages(drive.files(), 'list', 'files',
throwReasons=GAPI.DRIVE_USER_THROW_REASONS,
retryReasons=[GAPI.UNKNOWN_ERROR],
q=_getMain().WITH_PARENTS.format(fileId),
orderBy='folder desc,name,modifiedTime desc',
fields='nextPageToken,files(id,name,mimeType)',
pageSize=GC.Values[GC.DRIVE_MAX_RESULTS], **sourceSearchArgs)
kcount = len(sourceChildren)
if kcount > 0:
Ind.Increment()
k = 0
for child in sourceChildren:
k += 1
childId = child['id']
childName = child['name']
if child['mimeType'] == MIMETYPE_GA_FOLDER:
_recursiveUpdateMovePermissions(drive, user, i, count,
childId, childName,
statistics, copyMoveOptions, sourceSearchArgs)
else:
_updateMoveFilePermissions(drive, user, i, count,
Ent.DRIVE_FILE, childId, childName,
statistics, STAT_FILE_PERMISSIONS_FAILED, copyMoveOptions)
Ind.Decrement()
# gam <UserTypeEntity> move drivefile <DriveFileEntity> [newfilename <DriveFileName>]
# [summary [<Boolean>]] [showpermissionsmessages [<Boolean>]]
# [<DriveFileParentAttribute>]
# [mergewithparent|mergewithparentretain [<Boolean>]]
# [createshortcutsfornonmovablefiles [<Boolean>]]
# [duplicatefiles overwriteolder|overwriteall|duplicatename|uniquename|skip]
# [duplicatefolders merge|duplicatename|uniquename|skip]
# [copyfolderpermissions [<Boolean>]]
# [copymergewithparentfolderpermissions [<Boolean>]]
# [copymergedtopfolderpermissions [<Boolean>]]
# [copytopfolderpermissions [<Boolean>]]
# [copytopfolderiheritedpermissions [<Boolean>]]
# [copytopfoldernoniheritedpermissions never|always|syncallfolders|syncupdatedfolders]
# [copymergedsubfolderpermissions [<Boolean>]]
# [copysubfolderpermissions [<Boolean>]]
# [copysubfolderinheritedpermissions [<Boolean>]]
# [copysubfoldernoniheritedpermissions never|always|syncallfolders|syncupdatedfolders]
# [copypermissionroles <DriveFileACLRoleList>]
# [copypermissiontypes <DriveFileACLTypeList>]
# [synctopfoldernoniheritedpermissions [<Boolean>]] [syncsubfoldernoninheritedpermissions [<Boolean>]]
# [movefilepermissions [<Boolean>]]
# [excludepermissionsfromdomains|includepermissionsfromdomains <DomainNameList>]
# (mappermissionsemail <EmailAddress> <EmailAddress>)* [mappermissionsemailfile <CSVFileInput> endcsv]
# (mappermissionsdomain <DomainName> <DomainName>)*
# [retainsourcefolders [<Boolean>]]
# [sendemailifrequired [<Boolean>]]
# [verifyorganizer [<Boolean>]]
def moveDriveFile(users):
def _cloneFolderMove(drive, user, i, count, j, jcount,
source, targetChildren, newFolderName, newParentId, newParentName, mergeParentModifiedTime,
statistics, copyMoveOptions, atTop):
folderId = source.pop('id')
folderName = source['name']
sourceMimeType = source['mimeType']
folderNameId = f'{folderName}({folderId})'
kvList = [Ent.USER, user, Ent.DRIVE_FOLDER, folderNameId]
newParentNameId = f'{newParentName}({newParentId})'
# Merge top parent folder
if atTop and (copyMoveOptions['mergeWithParent'] or copyMoveOptions['mergeWithParentRetain']):
action = Act.Get()
Act.Set(Act.MOVE_MERGE)
_getMain().entityPerformActionModifierItemValueList(kvList, Act.MODIFIER_CONTENTS_WITH, [Ent.DRIVE_FOLDER, newParentNameId], j, jcount)
Act.Set(action)
_incrStatistic(statistics, STAT_FOLDER_MERGED)
if (copyMoveOptions['copyFolderPermissions'] and
copyMoveOptions['copyMergeWithParentFolderPermissions'] and
copyMoveOptions['destParentType'] != DEST_PARENT_MYDRIVE_ROOT):
copyFolderNonInheritedPermissions =\
_getCopyFolderNonInheritedPermissions(copyMoveOptions,
'copyTopFolderNonInheritedPermissions',
source['modifiedTime'], mergeParentModifiedTime)
_copyPermissions(drive, user, i, count, j, jcount,
Ent.DRIVE_FOLDER, folderId, folderName, newParentId, newFolderName,
statistics, STAT_FOLDER_PERMISSIONS_FAILED,
copyMoveOptions, True,
'copyTopFolderInheritedPermissions',
copyFolderNonInheritedPermissions,
False)
source.pop('oldparents', None)
return (newParentId, newParentName, True, True)
# Merge parent folders
if atTop and copyMoveOptions['sourceIsMyDriveSharedDrive']:
pass
elif copyMoveOptions['duplicateFolders'] == DUPLICATE_FOLDER_MERGE:
newFolderNameLower = newFolderName.lower()
for target in targetChildren:
if not target.get('processed', False) and newFolderNameLower == target['name'].lower() and sourceMimeType == target['mimeType']:
target['processed'] = True
if target['capabilities']['canAddChildren']:
newFolderId = target['id']
action = Act.Get()
Act.Set(Act.MOVE_MERGE)
_getMain().entityModifierItemValueListActionPerformed(kvList, Act.MODIFIER_CONTENTS_WITH, [Ent.DRIVE_FOLDER, f'{newFolderName}({newFolderId})'], j, jcount)
Act.Set(action)
_incrStatistic(statistics, STAT_FOLDER_MERGED)
if (copyMoveOptions['copyFolderPermissions'] and
copyMoveOptions[['copyMergedSubFolderPermissions', 'copyMergedTopFolderPermissions'][atTop]] and
(not atTop or copyMoveOptions['destParentType'] != DEST_PARENT_MYDRIVE_ROOT)):
copyFolderNonInheritedPermissions =\
_getCopyFolderNonInheritedPermissions(copyMoveOptions,
['copySubFolderNonInheritedPermissions', 'copyTopFolderNonInheritedPermissions'][atTop],
source['modifiedTime'], target['modifiedTime'])
_copyPermissions(drive, user, i, count, j, jcount,
Ent.DRIVE_FOLDER, folderId, folderName, newFolderId, newFolderName,
statistics, STAT_FOLDER_PERMISSIONS_FAILED,
copyMoveOptions, atTop,
['copySubFolderInheritedPermissions', 'copyTopFolderInheritedPermissions'][atTop],
copyFolderNonInheritedPermissions,
False)
return (newFolderId, newFolderName, True, True)
_getMain().entityActionFailedWarning(kvList+[Ent.DRIVE_FOLDER, newParentNameId], Msg.NOT_WRITABLE, j, jcount)
_incrStatistic(statistics, STAT_FOLDER_NOT_WRITABLE)
copyMoveOptions['retainSourceFolders'] = True
return (None, None, False, False)
elif copyMoveOptions['duplicateFolders'] == DUPLICATE_FOLDER_UNIQUE_NAME:
newFolderName = _getUniqueFilename(newFolderName, sourceMimeType, targetChildren)
elif copyMoveOptions['duplicateFolders'] == DUPLICATE_FOLDER_SKIP:
targetChild = _targetFilenameExists(newFolderName, sourceMimeType, targetChildren)
if targetChild is not None:
_getMain().entityModifierItemValueListActionNotPerformedWarning(kvList, Act.MODIFIER_TO,
[Ent.DRIVE_FOLDER, newParentNameId, Ent.DRIVE_FOLDER, f"{newFolderName}({targetChild['id']})"],
Msg.DUPLICATE, j, jcount)
_incrStatistic(statistics, STAT_FOLDER_DUPLICATE)
copyMoveOptions['retainSourceFolders'] = True
return (None, None, False, False)
# Update parents on: not retain and MD->MD, SD->MD, SD->SD
if atTop and copyMoveOptions['sourceIsMyDriveSharedDrive']:
pass
elif (not copyMoveOptions['retainSourceFolders'] and (copyMoveOptions['sourceDriveId'] or not copyMoveOptions['destDriveId'])):
if newFolderName != folderName:
body = {'name': newFolderName}
else:
body = {}
removeParents = ','.join([parentId for parentId in source.pop('oldparents', []) if parentId not in source['parents']])
try:
_getMain().callGAPI(drive.files(), 'update',
throwReasons=GAPI.DRIVE_ACCESS_THROW_REASONS+[GAPI.BAD_REQUEST,
GAPI.INSUFFICIENT_PARENT_PERMISSIONS,
GAPI.FILE_OWNER_NOT_MEMBER_OF_TEAMDRIVE,
GAPI.FILE_OWNER_NOT_MEMBER_OF_WRITER_DOMAIN,
GAPI.FILE_WRITER_TEAMDRIVE_MOVE_IN_DISABLED,
GAPI.TARGET_USER_ROLE_LIMITED_BY_LICENSE_RESTRICTION,
GAPI.CANNOT_MOVE_TRASHED_ITEM_INTO_TEAMDRIVE,
GAPI.CANNOT_MOVE_TRASHED_ITEM_OUT_OF_TEAMDRIVE,
GAPI.CROSS_DOMAIN_MOVE_RESTRICTION,
GAPI.STORAGE_QUOTA_EXCEEDED],
fileId=folderId,
addParents=newParentId, removeParents=removeParents,
body=body, fields=None, supportsAllDrives=True)
_getMain().entityModifierItemValueListActionPerformed(kvList, Act.MODIFIER_TO,
[Ent.DRIVE_FOLDER, newParentNameId, Ent.DRIVE_FOLDER, f'{newFolderName}({folderId})'],
j, jcount)
_incrStatistic(statistics, STAT_FILE_COPIED_MOVED)
return (None, None, False, True)
except (GAPI.badRequest, GAPI.insufficientParentPermissions, GAPI.fileOwnerNotMemberOfTeamDrive, GAPI.fileOwnerNotMemberOfWriterDomain,
GAPI.fileWriterTeamDriveMoveInDisabled, GAPI.targetUserRoleLimitedByLicenseRestriction,
GAPI.cannotMoveTrashedItemIntoTeamDrive, GAPI.cannotMoveTrashedItemOutOfTeamDrive,
GAPI.crossDomainMoveRestriction, GAPI.storageQuotaExceeded) as e:
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.DRIVE_FOLDER, folderName], str(e), j, jcount)
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
_incrStatistic(statistics, STAT_FILE_FAILED)
copyMoveOptions['retainSourceFolders'] = True
return (None, None, False, False)
# Create new parent on: retain or MD->SD
source.pop('oldparents', None)
body = source.copy()
body.pop('capabilities', None)
if copyMoveOptions['sourceDriveId'] or copyMoveOptions['destDriveId']:
body.pop('copyRequiresWriterPermission', None)
body.pop('writersCanShare', None)
body.pop('trashed', None)
if not copyMoveOptions['destDriveId']:
body.pop('driveId', None)
body['name'] = newFolderName
try:
result = _getMain().callGAPI(drive.files(), 'create',
throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.FORBIDDEN, GAPI.INSUFFICIENT_PERMISSIONS, GAPI.INSUFFICIENT_PARENT_PERMISSIONS,
GAPI.INTERNAL_ERROR, GAPI.STORAGE_QUOTA_EXCEEDED,
GAPI.TEAMDRIVE_FILE_LIMIT_EXCEEDED, GAPI.TEAMDRIVE_HIERARCHY_TOO_DEEP,
GAPI.BAD_REQUEST, GAPI.TARGET_USER_ROLE_LIMITED_BY_LICENSE_RESTRICTION],
body=body, fields='id', supportsAllDrives=True)
newFolderId = result['id']
action = Act.Get()
Act.Set(Act.RECREATE)
_getMain().entityModifierItemValueListActionPerformed(kvList, Act.MODIFIER_IN,
[Ent.DRIVE_FOLDER, newParentNameId, Ent.DRIVE_FOLDER, f'{newFolderName}({newFolderId})'],
j, jcount)
Act.Set(action)
_incrStatistic(statistics, STAT_FOLDER_COPIED_MOVED)
if (copyMoveOptions['copyFolderPermissions'] and
copyMoveOptions[['copySubFolderPermissions', 'copyTopFolderPermissions'][atTop]]):
_copyPermissions(drive, user, i, count, j, jcount,
Ent.DRIVE_FOLDER, folderId, folderName, newFolderId, newFolderName,
statistics, STAT_FOLDER_PERMISSIONS_FAILED,
copyMoveOptions, False,
['copySubFolderInheritedPermissions', 'copyTopFolderInheritedPermissions'][atTop],
['copySubFolderNonInheritedPermissions', 'copyTopFolderNonInheritedPermissions'][atTop],
True)
return (newFolderId, newFolderName, False, True)
except (GAPI.forbidden, GAPI.insufficientFilePermissions, GAPI.insufficientParentPermissions,
GAPI.internalError, GAPI.storageQuotaExceeded, GAPI.teamDriveFileLimitExceeded, GAPI.teamDriveHierarchyTooDeep,
GAPI.badRequest, GAPI.targetUserRoleLimitedByLicenseRestriction) as e:
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.DRIVE_FOLDER, newFolderName], str(e), j, jcount)
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
_incrStatistic(statistics, STAT_FOLDER_FAILED)
copyMoveOptions['retainSourceFolders'] = True
return (None, None, False, False)
def _makeMoveShortcut(drive, user, k, kcount, entityType, childId, childName, newParentId, newParentName):
kvList = [Ent.USER, user, entityType, f'{childName}({childId})']
if entityType == Ent.DRIVE_FILE:
targetEntityType = Ent.DRIVE_FILE_SHORTCUT
statShortcutCreated = STAT_FILE_SHORTCUT_CREATED
statShortcutExists = STAT_FILE_SHORTCUT_EXISTS
else:
targetEntityType = Ent.DRIVE_FOLDER_SHORTCUT
statShortcutCreated = STAT_FOLDER_SHORTCUT_CREATED
statShortcutExists = STAT_FOLDER_SHORTCUT_EXISTS
newParentNameId = f'{newParentName}({newParentId})'
action = Act.Get()
existingShortcut = _checkForExistingShortcut(drive, childId, childName, newParentId)
if existingShortcut:
Act.Set(Act.CREATE_SHORTCUT)
_getMain().entityModifierItemValueListActionPerformed(kvList, Act.MODIFIER_PREVIOUSLY_IN,
[Ent.DRIVE_FOLDER, newParentNameId, targetEntityType, f"{childName}({existingShortcut})"],
k, kcount)
Act.Set(action)
_incrStatistic(statistics, statShortcutExists)
return
body = {'name': childName, 'mimeType': MIMETYPE_GA_SHORTCUT,
'parents': [newParentId], 'shortcutDetails': {'targetId': childId}}
try:
result = _getMain().callGAPI(drive.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_FILE_LIMIT_EXCEEDED, GAPI.TEAMDRIVE_HIERARCHY_TOO_DEEP, GAPI.SHORTCUT_TARGET_INVALID,
GAPI.TARGET_USER_ROLE_LIMITED_BY_LICENSE_RESTRICTION, GAPI.SHARE_OUT_WARNING],
body=body, fields='id', supportsAllDrives=True)
Act.Set(Act.CREATE_SHORTCUT)
_getMain().entityModifierItemValueListActionPerformed(kvList, Act.MODIFIER_IN,
[Ent.DRIVE_FOLDER, newParentNameId, targetEntityType, f"{childName}({result['id']})"],
k, kcount)
Act.Set(action)
_incrStatistic(statistics, statShortcutCreated)
except (GAPI.forbidden, GAPI.insufficientFilePermissions, GAPI.insufficientParentPermissions,
GAPI.invalid, GAPI.badRequest, GAPI.fileNotFound, GAPI.unknownError,
GAPI.storageQuotaExceeded, GAPI.teamDrivesSharingRestrictionNotAllowed,
GAPI.teamDriveFileLimitExceeded, GAPI.teamDriveHierarchyTooDeep, GAPI.shortcutTargetInvalid,
GAPI.targetUserRoleLimitedByLicenseRestriction, GAPI.shareOutWarning) as e:
_getMain().entityActionFailedWarning(kvList+[Ent.DRIVE_FILE_SHORTCUT, childName], str(e), k, kcount)
_incrStatistic(statistics, STAT_FILE_FAILED)
def _moveFile(drive, user, i, count, k, kcount, entityType, childId, childName, newChildName, newParentId, newParentName, removeParents, body):
kvList = [Ent.USER, user, entityType, f'{childName}({childId})']
newParentNameId = f'{newParentName}({newParentId})'
if updateMovePermissions:
_updateMoveFilePermissions(drive, user, i, count,
entityType, childId, childName,
statistics, STAT_FILE_PERMISSIONS_FAILED, copyMoveOptions)
try:
_getMain().callGAPI(drive.files(), 'update',
throwReasons=GAPI.DRIVE_ACCESS_THROW_REASONS+[GAPI.BAD_REQUEST,
GAPI.INSUFFICIENT_PARENT_PERMISSIONS,
GAPI.FILE_OWNER_NOT_MEMBER_OF_TEAMDRIVE,
GAPI.FILE_OWNER_NOT_MEMBER_OF_WRITER_DOMAIN,
GAPI.FILE_WRITER_TEAMDRIVE_MOVE_IN_DISABLED,
GAPI.SHARE_OUT_NOT_PERMITTED,
GAPI.TARGET_USER_ROLE_LIMITED_BY_LICENSE_RESTRICTION,
GAPI.CANNOT_MOVE_TRASHED_ITEM_INTO_TEAMDRIVE,
GAPI.CANNOT_MOVE_TRASHED_ITEM_OUT_OF_TEAMDRIVE,
GAPI.TEAMDRIVES_SHORTCUT_FILE_NOT_SUPPORTED,
GAPI.CROSS_DOMAIN_MOVE_RESTRICTION,
GAPI.STORAGE_QUOTA_EXCEEDED],
fileId=childId, addParents=newParentId, removeParents=removeParents,
body=body, fields='', supportsAllDrives=True)
_getMain().entityModifierItemValueListActionPerformed(kvList, Act.MODIFIER_TO,
[Ent.DRIVE_FOLDER, newParentNameId, entityType, f'{newChildName}({childId})'],
k, kcount)
_incrStatistic(statistics, STAT_FILE_COPIED_MOVED)
return
except (GAPI.fileNotFound, GAPI.forbidden, GAPI.internalError, GAPI.unknownError, GAPI.badRequest,
GAPI.shareOutNotPermitted, GAPI.targetUserRoleLimitedByLicenseRestriction,
GAPI.cannotMoveTrashedItemIntoTeamDrive, GAPI.cannotMoveTrashedItemOutOfTeamDrive,
GAPI.teamDrivesShortcutFileNotSupported, GAPI.storageQuotaExceeded) as e:
_getMain().entityActionFailedWarning(kvList, str(e), k, kcount)
_incrStatistic(statistics, STAT_FILE_FAILED)
except (GAPI.insufficientFilePermissions, GAPI.insufficientParentPermissions,
GAPI.fileOwnerNotMemberOfTeamDrive, GAPI.fileOwnerNotMemberOfWriterDomain,
GAPI.fileWriterTeamDriveMoveInDisabled,
GAPI.crossDomainMoveRestriction) as e:
if not copyMoveOptions['createShortcutsForNonmovableFiles']:
_getMain().entityActionFailedWarning(kvList, str(e), k, kcount)
_incrStatistic(statistics, STAT_FILE_FAILED)
else:
_makeMoveShortcut(drive, user, k, kcount, entityType, childId, childName, newParentId, newParentName)
copyMoveOptions['retainSourceFolders'] = True
def _recursiveFolderMove(drive, user, i, count, j, jcount,
source, targetChildren, newFolderName, newParentId, newParentName, mergeParentModifiedTime, atTop):
folderId = source['id']
newFolderId, newFolderName, existingTargetFolder, status = _cloneFolderMove(drive, user, i, count, j, jcount,
source, targetChildren, newFolderName,
newParentId, newParentName, mergeParentModifiedTime,
statistics, copyMoveOptions, atTop)
if not status:
return
if newFolderId is None:
if updateMovePermissions:
_recursiveUpdateMovePermissions(drive, user, i, count,
folderId, source['name'],
statistics, copyMoveOptions, sourceSearchArgs)
return
movedFiles[newFolderId] = 1
sourceChildren = _getMain().callGAPIpages(drive.files(), 'list', 'files',
throwReasons=GAPI.DRIVE_USER_THROW_REASONS,
retryReasons=[GAPI.UNKNOWN_ERROR],
q=_getMain().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)',
pageSize=GC.Values[GC.DRIVE_MAX_RESULTS], **sourceSearchArgs)
kcount = len(sourceChildren)
if kcount > 0:
if existingTargetFolder:
subTargetChildren = _getMain().callGAPIpages(drive.files(), 'list', 'files',
throwReasons=GAPI.DRIVE_USER_THROW_REASONS,
retryReasons=[GAPI.UNKNOWN_ERROR],
q=_getMain().ANY_NON_TRASHED_WITH_PARENTS.format(newFolderId),
orderBy='folder desc,name,modifiedTime desc',
fields='nextPageToken,files(id,name,capabilities,mimeType,modifiedTime)',
**parentParms[DFA_SEARCHARGS])
else:
subTargetChildren = []
Ind.Increment()
k = 0
for child in sourceChildren:
k += 1
childId = child['id']
childName = child['name']
childNameId = f'{childName}({childId})'
if movedFiles.get(childId):
continue
trashed = child.pop('trashed', False)
if (childId == newFolderId) or (copyMoveOptions['destDriveId'] and trashed):
_getMain().entityActionNotPerformedWarning([Ent.USER, user, _getMain()._getEntityMimeType(child), childNameId],
[Msg.NOT_MOVABLE, Msg.NOT_MOVABLE_IN_TRASH][trashed], k, kcount)
_incrStatistic(statistics, STAT_FILE_NOT_COPYABLE_MOVABLE)
continue
childParents = child.pop('parents', [])
child['parents'] = [newFolderId]
if child['mimeType'] == MIMETYPE_GA_FOLDER:
child['oldparents'] = childParents
_recursiveFolderMove(drive, user, i, count, k, kcount,
child, subTargetChildren, childName, newFolderId, newFolderName, child['modifiedTime'], False)
else:
if existingTargetFolder and _checkForDuplicateTargetFile(drive, user, k, kcount, child, childName, subTargetChildren, copyMoveOptions, statistics):
copyMoveOptions['retainSourceFolders'] = True
continue
if childName != child['name']:
body = {'name': child['name']}
else:
body = {}
removeParents = ','.join(childParents)
_moveFile(drive, user, i, count, k, kcount, Ent.DRIVE_FILE, childId, childName, childName, newFolderId, newFolderName, removeParents, body)
Ind.Decrement()
sourceName = source['name']
kvList = [Ent.USER, user, Ent.DRIVE_FOLDER, f"{sourceName}({folderId})"]
if (atTop and (copyMoveOptions['mergeWithParentRetain'] or copyMoveOptions['sourceIsMyDriveSharedDrive'])) or copyMoveOptions['retainSourceFolders']:
Act.Set(Act.RETAIN)
_getMain().entityActionPerformed(kvList, i, count)
else:
Act.Set(Act.DELETE)
try:
_getMain().callGAPI(drive.files(), 'delete',
throwReasons=GAPI.DRIVE_ACCESS_THROW_REASONS+[GAPI.FILE_NEVER_WRITABLE],
fileId=folderId, supportsAllDrives=True)
_getMain().entityActionPerformed(kvList, i, count)
except (GAPI.fileNotFound, GAPI.forbidden, GAPI.internalError, GAPI.insufficientFilePermissions, GAPI.unknownError,
GAPI.fileNeverWritable) as e:
_getMain().entityActionFailedWarning(kvList, str(e), j, jcount)
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
Act.Set(Act.MOVE)
return
def anyFolderPermissionOperations():
for field in ['copyMergeWithParentFolderPermissions',
'copyMergedTopFolderPermissions', 'copyMergedSubFolderPermissions',
'copyTopFolderPermissions', 'copySubFolderPermissions']:
if copyMoveOptions[field]:
return True
return False
fileIdEntity = getDriveFileEntity()
parentBody = {}
parentParms = initDriveFileAttributes()
copyMoveOptions = initCopyMoveOptions(False)
newParentsSpecified = False
movedFiles = {}
verifyOrganizer = True
while Cmd.ArgumentsRemaining():
myarg = _getMain().getArgument()
if getCopyMoveOptions(myarg, copyMoveOptions):
pass
elif getDriveFileParentAttribute(myarg, parentParms):
newParentsSpecified = True
elif myarg == 'verifyorganizer':
verifyOrganizer = _getMain().getBoolean()
else:
_getMain().unknownArgumentExit()
updateMovePermissions = ((not copyMoveOptions['moveFilePermissions']) or
copyMoveOptions['excludePermissionsFromDomains'] or copyMoveOptions['includePermissionsFromDomains'] or
copyMoveOptions['mapPermissionsDomains'] or copyMoveOptions['mapPermissionsEmails'])
i, count, users = _getMain().getEntityArgument(users)
for user in users:
i += 1
user, drive, jcount = _validateUserGetFileIDs(user, i, count, fileIdEntity, entityType=Ent.DRIVE_FILE_OR_FOLDER)
if jcount == 0:
continue
if not _getDriveFileParentInfo(drive, user, i, count, parentBody, parentParms):
continue
statistics = _initStatistics()
Ind.Increment()
j = 0
for fileId in fileIdEntity['list']:
j += 1
try:
source = _getMain().callGAPI(drive.files(), 'get',
throwReasons=GAPI.DRIVE_GET_THROW_REASONS,
fileId=fileId,
fields='id,name,parents,appProperties,capabilities,contentHints,copyRequiresWriterPermission,'\
'description,mimeType,modifiedTime,properties,starred,driveId,trashed,viewedByMeTime,writersCanShare',
supportsAllDrives=True)
# Source at root of My Drive or Shared Drive?
sourceMimeType = source['mimeType']
if sourceMimeType == MIMETYPE_GA_FOLDER and source['name'] in [MY_DRIVE, TEAM_DRIVE] and not source.get('parents', []):
copyMoveOptions['sourceIsMyDriveSharedDrive'] = True
if source.get('driveId'):
source['name'] = _getSharedDriveNameFromId(drive, source['driveId'])
sourceName = source['name']
sourceNameId = f"{sourceName}({source['id']})"
copyMoveOptions['sourceDriveId'] = source.get('driveId')
kvList = [Ent.USER, user, _getMain()._getEntityMimeType(source), sourceNameId]
if fileId in movedFiles:
_getMain().entityActionNotPerformedWarning(kvList, Msg.DUPLICATE, j, jcount)
_incrStatistic(statistics, STAT_FILE_DUPLICATE)
continue
if copyMoveOptions['sourceDriveId']:
# If moving from a Shared Drive, user has to be an organizer
if verifyOrganizer and not _verifyUserIsOrganizer(drive, user, i, count, copyMoveOptions['sourceDriveId']):
_incrStatistic(statistics, STAT_USER_NOT_ORGANIZER)
continue
if source['trashed']:
_getMain().entityActionNotPerformedWarning(kvList, Msg.NOT_MOVABLE_IN_TRASH, j, jcount)
_incrStatistic(statistics, STAT_FILE_NOT_COPYABLE_MOVABLE)
continue
sourceSearchArgs = {'driveId': copyMoveOptions['sourceDriveId'], 'corpora': 'drive', 'includeItemsFromAllDrives': True, 'supportsAllDrives': True}
else:
sourceSearchArgs = {}
sourceParents = source.pop('parents', [])
if newParentsSpecified:
newParents = parentBody['parents']
numNewParents = len(newParents)
if numNewParents > 1:
_getMain().entityActionNotPerformedWarning(kvList, Msg.MULTIPLE_PARENTS_SPECIFIED.format(numNewParents), j, jcount)
_incrStatistic(statistics, STAT_FILE_FAILED)
continue
else:
newParents = sourceParents if sourceParents else [ROOT]
source['parents'] = newParents
newParentId = newParents[0]
dest = _getCopyMoveParentInfo(drive, user, i, count, j, jcount, newParentId, statistics)
if dest is None:
continue
newParentName = dest['name']
newParentNameId = f'{newParentName}({newParentId})'
copyMoveOptions['destDriveId'] = dest['driveId']
copyMoveOptions['destParentType'] = dest['destParentType']
if copyMoveOptions['destDriveId'] and not parentParms[DFA_SEARCHARGS]:
parentParms[DFA_SEARCHARGS] = {'driveId': copyMoveOptions['destDriveId'], 'corpora': 'drive',
'includeItemsFromAllDrives': True, 'supportsAllDrives': True}
if copyMoveOptions['newFilename']:
destName = copyMoveOptions['newFilename']
elif (sourceMimeType == MIMETYPE_GA_FOLDER) and (copyMoveOptions['mergeWithParent'] or copyMoveOptions['mergeWithParentRetain']):
destName = dest['name']
else:
destName = sourceName
targetChildren = _getCopyMoveTargetInfo(drive, user, i, count, j, jcount, source, destName, newParentId, statistics, parentParms)
if targetChildren is None:
continue
if copyMoveOptions['destDriveId']:
# If moving to a Shared Drive, user has to be an organizer
if verifyOrganizer and not _verifyUserIsOrganizer(drive, user, i, count, copyMoveOptions['destDriveId']):
_incrStatistic(statistics, STAT_USER_NOT_ORGANIZER)
continue
# 3rd party shortcuts can't be moved to Shared Drives
if sourceMimeType.startswith(MIMETYPE_GA_3P_SHORTCUT):
_getMain().entityActionNotPerformedWarning([Ent.USER, user, Ent.DRIVE_3PSHORTCUT, sourceNameId], Msg.NOT_MOVABLE, j, jcount)
_incrStatistic(statistics, STAT_FILE_NOT_COPYABLE_MOVABLE)
continue
# Move folder
if sourceMimeType == MIMETYPE_GA_FOLDER:
if fileId == newParentId:
_getMain().entityActionNotPerformedWarning(kvList, Msg.NOT_MOVABLE_INTO_ITSELF, j, jcount)
_incrStatistic(statistics, STAT_FOLDER_FAILED)
continue
if copyMoveOptions['duplicateFolders'] == DUPLICATE_FOLDER_MERGE:
if _identicalSourceTarget(fileId, targetChildren):
_getMain().entityActionNotPerformedWarning(kvList, Msg.NOT_MOVABLE_SAME_NAME_CURRENT_FOLDER_MERGE, j, jcount)
_incrStatistic(statistics, STAT_FOLDER_FAILED)
continue
elif copyMoveOptions['duplicateFolders'] == DUPLICATE_FOLDER_UNIQUE_NAME:
destName = _getUniqueFilename(destName, sourceMimeType, targetChildren)
elif copyMoveOptions['duplicateFolders'] == DUPLICATE_FOLDER_SKIP:
targetChild = _targetFilenameExists(destName, sourceMimeType, targetChildren)
if targetChild is not None:
_getMain().entityModifierItemValueListActionNotPerformedWarning(kvList, Act.MODIFIER_TO,
[Ent.DRIVE_FOLDER, newParentNameId, Ent.DRIVE_FOLDER, f"{destName}({targetChild['id']})"],
Msg.DUPLICATE, j, jcount)
_incrStatistic(statistics, STAT_FOLDER_DUPLICATE)
continue
if ((not copyMoveOptions['sourceDriveId'] and copyMoveOptions['destDriveId']) or
(copyMoveOptions['mergeWithParent'] or copyMoveOptions['mergeWithParentRetain'] or copyMoveOptions['retainSourceFolders']) or
(copyMoveOptions['duplicateFolders'] == DUPLICATE_FOLDER_MERGE and _targetFilenameExists(destName, sourceMimeType, targetChildren) is not None) or
anyFolderPermissionOperations()):
source['oldparents'] = sourceParents
_recursiveFolderMove(drive, user, i, count, j, jcount,
source, targetChildren, destName, newParentId, newParentName, dest['modifiedTime'], True)
continue
entityType = Ent.DRIVE_FOLDER
# Move file
else:
if copyMoveOptions['duplicateFiles'] in [DUPLICATE_FILE_OVERWRITE_ALL, DUPLICATE_FILE_OVERWRITE_OLDER] and _identicalSourceTarget(fileId, targetChildren):
_getMain().entityActionNotPerformedWarning(kvList, Msg.NOT_MOVABLE_SAME_NAME_CURRENT_FOLDER_OVERWRITE, j, jcount)
_incrStatistic(statistics, STAT_FILE_FAILED)
continue
if _checkForDuplicateTargetFile(drive, user, j, jcount, source, destName, targetChildren, copyMoveOptions, statistics):
continue
destName = source['name'] # duplicatefiles uniquename may cause rename
entityType = Ent.DRIVE_FILE
# Simple move file/folder
if destName != sourceName:
body = {'name': destName}
else:
body = {}
# All parents removed from top level moved item as non-path parents can't be determined
removeParents = ','.join(sourceParents)
_moveFile(drive, user, i, count, j, jcount, entityType, fileId, sourceName, destName, newParentId, newParentName, removeParents, body)
except (GAPI.fileNotFound, GAPI.forbidden, GAPI.internalError, GAPI.insufficientFilePermissions, GAPI.unknownError,
GAPI.invalid, GAPI.badRequest, GAPI.cannotCopyFile,
GAPI.fileOwnerNotMemberOfTeamDrive, GAPI.fileOwnerNotMemberOfWriterDomain,
GAPI.fileWriterTeamDriveMoveInDisabled,
GAPI.cannotMoveTrashedItemIntoTeamDrive, GAPI.cannotMoveTrashedItemOutOfTeamDrive,
GAPI.teamDrivesShortcutFileNotSupported, GAPI.crossDomainMoveRestriction) as e:
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.DRIVE_FILE_OR_FOLDER_ID, fileId], str(e), j, jcount)
_incrStatistic(statistics, STAT_FILE_FAILED)
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
_incrStatistic(statistics, STAT_FILE_FAILED)
break
Ind.Decrement()
if copyMoveOptions['summary']:
_printStatistics(user, statistics, i, count, False)
DELETE_DRIVEFILE_CHOICE_MAP = {'purge': 'delete', 'trash': 'trash', 'untrash': 'untrash'}
DELETE_DRIVEFILE_FUNCTION_TO_ACTION_MAP = {'delete': Act.PURGE, 'trash': Act.TRASH, 'untrash': Act.UNTRASH}
DELETE_DRIVEFILE_FUNCTION_TO_CAPABILITY_MAP = {'delete': 'canDelete', 'trash': 'canTrash', 'untrash': 'canUntrash'}
# gam <UserTypeEntity> delete drivefile <DriveFileEntity> [purge|trash|untrash] [shortcutandtarget [<Boolean>]]

File diff suppressed because it is too large Load Diff

1147
src/gam/cmd/drive/core.py Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,838 @@
"""File path resolution, Drive info mapping, field handling.
Part of the drive sub-package, extracted from drive.py."""
"""GAM Google Drive file, permission, shared drive, and label management."""
import re
import json
import sys
from gamlib import glaction
from gamlib import glapi as API
from gamlib import glcfg as GC
from gamlib import glclargs
from gamlib import glentity
from gamlib import glgapi as GAPI
from gamlib import glglobals as GM
from gamlib import glindent
from gamlib import glmsgs as Msg
Act = glaction.GamAction()
Ent = glentity.GamEntity()
Ind = glindent.GamIndent()
Cmd = glclargs.GamCLArgs()
APPLICATION_VND_GOOGLE_APPS = 'application/vnd.google-apps.'
MIMETYPE_GA_DOCUMENT = f'{APPLICATION_VND_GOOGLE_APPS}document'
MIMETYPE_GA_DRAWING = f'{APPLICATION_VND_GOOGLE_APPS}drawing'
MIMETYPE_GA_FILE = f'{APPLICATION_VND_GOOGLE_APPS}file'
MIMETYPE_GA_FOLDER = f'{APPLICATION_VND_GOOGLE_APPS}folder'
MIMETYPE_GA_FORM = f'{APPLICATION_VND_GOOGLE_APPS}form'
MIMETYPE_GA_FUSIONTABLE = f'{APPLICATION_VND_GOOGLE_APPS}fusiontable'
MIMETYPE_GA_JAM = f'{APPLICATION_VND_GOOGLE_APPS}jam'
MIMETYPE_GA_MAP = f'{APPLICATION_VND_GOOGLE_APPS}map'
MIMETYPE_GA_PRESENTATION = f'{APPLICATION_VND_GOOGLE_APPS}presentation'
MIMETYPE_GA_SCRIPT = f'{APPLICATION_VND_GOOGLE_APPS}script'
MIMETYPE_GA_SCRIPT_JSON = f'{APPLICATION_VND_GOOGLE_APPS}script+json'
MIMETYPE_GA_SHORTCUT = f'{APPLICATION_VND_GOOGLE_APPS}shortcut'
MIMETYPE_GA_3P_SHORTCUT = f'{APPLICATION_VND_GOOGLE_APPS}drive-sdk'
MIMETYPE_GA_SITE = f'{APPLICATION_VND_GOOGLE_APPS}site'
MIMETYPE_GA_SPREADSHEET = f'{APPLICATION_VND_GOOGLE_APPS}spreadsheet'
ME_IN_OWNERS = "'me' in owners"
ME_IN_OWNERS_AND = ME_IN_OWNERS + " and "
NOT_ME_IN_OWNERS = "not " + ME_IN_OWNERS
NOT_ME_IN_OWNERS_AND = NOT_ME_IN_OWNERS + " and "
WITH_ANY_FILE_NAME = "name = '{0}'"
WITH_MY_FILE_NAME = ME_IN_OWNERS_AND + WITH_ANY_FILE_NAME
WITH_OTHER_FILE_NAME = NOT_ME_IN_OWNERS_AND + WITH_ANY_FILE_NAME
ROOT = 'root'
ORPHANS = 'Orphans'
SHARED_WITHME = 'SharedWithMe'
SHARED_DRIVES = 'SharedDrives'
def _getMain():
return sys.modules['gam']
def __getattr__(name):
"""Fall back to gam module for any undefined names."""
main = _getMain()
try:
return getattr(main, name)
except AttributeError:
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
from gam.cmd.drive.core import DRIVE_LABEL_CHOICE_MAP # cross-module ref
def initFilePathInfo(delimiter):
return {'ids': {}, 'allPaths': {}, 'localPaths': None, 'delimiter': delimiter}
def getFilePaths(drive, fileTree, initialResult, filePathInfo, addParentsToTree=False,
fullpath=False, showDepth=False, folderPathOnly=False, parentPathOnly=False):
def _getParentName(result):
if (result['mimeType'] == MIMETYPE_GA_FOLDER) and result.get('driveId') and (result['name'] == _getMain().TEAM_DRIVE):
parentName = _getSharedDriveNameFromId(drive, result['driveId'])
if parentName != _getMain().TEAM_DRIVE:
return f'{SHARED_DRIVES}{filePathInfo["delimiter"]}{parentName}'
return result['name']
def _followParent(paths, parentId):
result = None
paths.setdefault(parentId, {})
if fileTree:
parentEntry = fileTree.get(parentId)
if not parentEntry:
if not addParentsToTree:
return
parentEntry = fileTree[parentId] = {'info': {'id': parentId, 'name': parentId, 'mimeType': MIMETYPE_GA_FOLDER}, 'children': []}
if parentEntry['info']['name'] == parentEntry['info']['id'] and parentEntry['info']['id'] not in {ORPHANS, SHARED_WITHME, SHARED_DRIVES}:
try:
result = _getMain().callGAPI(drive.files(), 'get',
throwReasons=GAPI.DRIVE_GET_THROW_REASONS,
fileId=parentId, fields='name,parents,mimeType,driveId', supportsAllDrives=True)
parentEntry['info']['name'] = _getParentName(result)
parentEntry['info']['parents'] = result.get('parents', [])
except (GAPI.fileNotFound, GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy):
pass
filePathInfo['ids'][parentId] = parentEntry['info']['name']
parents = parentEntry['info'].get('parents', [])
else:
try:
result = _getMain().callGAPI(drive.files(), 'get',
throwReasons=GAPI.DRIVE_GET_THROW_REASONS,
fileId=parentId, fields='name,parents,mimeType,driveId', supportsAllDrives=True)
filePathInfo['ids'][parentId] = _getParentName(result)
parents = result.get('parents', [])
except (GAPI.fileNotFound, GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy):
return
for lparentId in parents:
if lparentId not in filePathInfo['allPaths']:
_followParent(paths[parentId], lparentId)
filePathInfo['allPaths'][lparentId] = paths[parentId][lparentId]
else:
paths[parentId][lparentId] = filePathInfo['allPaths'][lparentId]
def _makeFilePaths(localPaths, fplist, filePaths, name, maxDepth):
for k, v in localPaths.items():
fplist.append(filePathInfo['ids'].get(k, ''))
if not v:
fp = fplist[:]
if showDepth:
depth = len(fp)
if depth > maxDepth:
maxDepth = depth-1
fp.reverse()
if not parentPathOnly:
if initialMimeType == MIMETYPE_GA_FOLDER or not folderPathOnly:
fp.append(name)
filePaths.append(filePathInfo['delimiter'].join(fp))
else:
maxDepth = _makeFilePaths(v, fplist, filePaths, name, maxDepth)
fplist.pop()
return maxDepth
filePaths = []
parents = initialResult.get('parents', [])
initialMimeType = initialResult['mimeType']
if parents:
filePathInfo['localPaths'] = {}
for parentId in parents:
if parentId not in filePathInfo['allPaths']:
_followParent(filePathInfo['allPaths'], parentId)
filePathInfo['localPaths'][parentId] = filePathInfo['allPaths'][parentId]
fplist = []
maxDepth = _makeFilePaths(filePathInfo['localPaths'], fplist, filePaths, initialResult['name'], -1)
else:
if (fullpath and initialMimeType == MIMETYPE_GA_FOLDER and
((initialResult['name'] == _getMain().MY_DRIVE) or
(initialResult.get('driveId') and initialResult['name'].startswith(SHARED_DRIVES)))):
filePaths.append(initialResult['name'])
maxDepth = 0
return (_getMain()._getEntityMimeType(initialResult), filePaths, maxDepth)
DRIVEFILE_ORDERBY_CHOICE_MAP = {
'createddate': 'createdTime',
'createdtime': 'createdTime',
'folder': 'folder',
'lastviewedbyme': 'viewedByMeTime',
'lastviewedbymedate': 'viewedByMeTime',
'lastviewedbymetime': 'viewedByMeTime',
'lastviewedbyuser': 'viewedByMeTime',
'modifiedbyme': 'modifiedByMeTime',
'modifiedbymedate': 'modifiedByMeTime',
'modifiedbymetime': 'modifiedByMeTime',
'modifiedbyuser': 'modifiedByMeTime',
'modifieddate': 'modifiedTime',
'modifiedtime': 'modifiedTime',
'name': 'name',
'namenatural': 'name_natural',
'quotabytesused': 'quotaBytesUsed',
'quotaused': 'quotaBytesUsed',
'recency': 'recency',
'sharedwithmedate': 'sharedWithMeTime',
'sharedwithmetime': 'sharedWithMeTime',
'starred': 'starred',
'title': 'name',
'titlenatural': 'name_natural',
'viewedbymedate': 'viewedByMeTime',
'viewedbymetime': 'viewedByMeTime',
}
def _mapDriveUser(field):
if 'me' in field:
field['isAuthenticatedUser'] = field.pop('me')
if 'photoLink' in field:
field['picture'] = {'url': field.pop('photoLink')}
def _mapDrivePermissionNames(permission):
emailAddress = permission.get('emailAddress')
if emailAddress:
_, permission['domain'] = _getMain().splitEmailAddress(emailAddress)
def _mapDriveInfo(f_file, parentsSubFields, showParentsIdsAsList):
if 'parents' in f_file:
parents = f_file.pop('parents')
if showParentsIdsAsList:
f_file['parentsIds'] = parents
elif len(parents) != 1 or parents[0] != ORPHANS:
f_file['parents'] = []
for parentId in parents:
parent = {}
if parentsSubFields['id']:
parent['id'] = parentId
if parentsSubFields['isRoot']:
parent['isRoot'] = parentId == parentsSubFields['rootFolderId']
f_file['parents'].append(parent)
appProperties = f_file.pop('appProperties', [])
properties = f_file.pop('properties', [])
if appProperties:
f_file.setdefault('properties', [])
for key, value in sorted(appProperties.items()):
f_file['properties'].append({'key': key, 'value': value, 'visibility': 'PRIVATE'})
if properties:
f_file.setdefault('properties', [])
for key, value in sorted(properties.items()):
f_file['properties'].append({'key': key, 'value': value, 'visibility': 'PUBLIC'})
for permission in f_file.get('permissions', []):
emailAddress = permission.get('emailAddress')
if emailAddress:
_, permission['domain'] = _getMain().splitEmailAddress(emailAddress)
DRIVEFILE_BASIC_PERMISSION_FIELDS = [
'displayName', 'id', 'emailAddress', 'domain', 'role', 'type',
'allowFileDiscovery', 'expirationTime', 'deleted', 'inheritedPermissionsDisabled',
'permissionDetails' #permissionDetails must be last
]
DRIVE_FIELDS_CHOICE_MAP = {
'alternatelink': 'webViewLink',
'appdatacontents': 'spaces',
'appproperties': 'appProperties',
'basicpermissions': ['permissions.displayName', 'permissions.id', 'permissions.emailAddress', 'permissions.domain',
'permissions.role', 'permissions.type', 'permissions.allowFileDiscovery',
'permissions.expirationTime', 'permissions.deleted', 'permissions.inheritedPermissionsDisabled'],
'cancomment': 'capabilities.canComment',
'canreadrevisions': 'capabilities.canReadRevisions',
'capabilities': 'capabilities',
'contenthints': 'contentHints',
'contentrestrictions': 'contentRestrictions',
'copyable': 'capabilities.canCopy',
'copyrequireswriterpermission': 'copyRequiresWriterPermission',
'createddate': 'createdTime',
'createdtime': 'createdTime',
'description': 'description',
'downloadrestrictions': 'downloadRestrictions',
'driveid': 'driveId',
'drivename': 'driveId',
'editable': 'capabilities.canEdit',
'explicitlytrashed': 'explicitlyTrashed',
'exportlinks': 'exportLinks',
'fileextension': 'fileExtension',
'filesize': 'size',
'foldercolorrgb': 'folderColorRgb',
'fullfileextension': 'fullFileExtension',
'hasaugmentedpermissions': 'hasAugmentedPermissions',
'hasthumbnail': 'hasThumbnail',
'headrevisionid': 'headRevisionId',
'iconlink': 'iconLink',
'id': 'id',
'imagemediametadata': 'imageMediaMetadata',
'inheritedpermissionsdisabled': 'inheritedPermissionsDisabled',
'isappauthorized': 'isAppAuthorized',
'labelinfo': 'labelInfo',
'labels': ['modifiedByMe', 'copyRequiresWriterPermission', 'starred', 'trashed', 'viewedByMe'],
'lastmodifyinguser': 'lastModifyingUser',
'lastmodifyingusername': 'lastModifyingUser.displayName',
'lastviewedbyme': 'viewedByMeTime',
'lastviewedbymedate': 'viewedByMeTime',
'lastviewedbymetime': 'viewedByMeTime',
'lastviewedbyuser': 'viewedByMeTime',
'linksharemetadata': 'linkShareMetadata',
'md5': 'md5Checksum',
'md5checksum': 'md5Checksum',
'md5sum': 'md5Checksum',
'mime': 'mimeType',
'mimetype': 'mimeType',
'modifiedbymedate': 'modifiedByMeTime',
'modifiedbymetime': 'modifiedByMeTime',
'modifiedbyuser': 'modifiedByMeTime',
'modifieddate': 'modifiedTime',
'modifiedtime': 'modifiedTime',
'name': 'name',
'originalfilename': 'originalFilename',
'ownedbyme': 'ownedByMe',
'ownernames': 'owners.displayName',
'owners': 'owners',
'parents': 'parents',
'permissiondetails': 'permissions.permissionDetails',
'permissionids': 'permissionIds',
'permissions': 'permissions',
'properties': 'properties',
'quotabytesused': 'quotaBytesUsed',
'quotaused': 'quotaBytesUsed',
'resourcekey': 'resourceKey',
'shareable': 'capabilities.canShare',
'shared': 'shared',
'shareddriveid': 'driveId',
'shareddrivename': 'driveId',
'sharedwithmedate': 'sharedWithMeTime',
'sharedwithmetime': 'sharedWithMeTime',
'sharinguser': 'sharingUser',
'shortcutdetails': 'shortcutDetails',
'sha1checksum': 'sha1Checksum',
'sha256checksum': 'sha256Checksum',
'size': 'size',
'spaces': 'spaces',
'teamdriveid': 'driveId',
'teamdrivename': 'driveId',
'thumbnaillink': 'thumbnailLink',
'thumbnailversion': 'thumbnailVersion',
'title': 'name',
'trasheddate': 'trashedTime',
'trashedtime': 'trashedTime',
'trashinguser': 'trashingUser',
'userpermission': 'ownedByMe,capabilities.canEdit,capabilities.canComment',
'version': 'version',
'videomediametadata': 'videoMediaMetadata',
'viewedbymedate': 'viewedByMeTime',
'viewedbymetime': 'viewedByMeTime',
'viewerscancopycontent': 'copyRequiresWriterPermission',
'webcontentlink': 'webContentLink',
'webviewlink': 'webViewLink',
'writerscanshare': 'writersCanShare',
}
DRIVE_CAPABILITIES_SUBFIELDS_CHOICE_MAP = {
'canacceptownership': 'canAcceptOwnership',
'canaddchildren': 'canAddChildren',
'canaddfolderfromanotherdrive': 'canAddFolderFromAnotherDrive',
'canaddmydriveparent': 'canAddMyDriveParent',
'canchangecopyrequireswriterpermission': 'canChangeCopyRequiresWriterPermission',
'canchangecopyrequireswriterpermissionrestriction': 'canChangeCopyRequiresWriterPermissionRestriction',
'canchangedownloadrestriction': 'canChangeDownloadRestriction',
'canchangedomainusersonlyrestriction': 'canChangeDomainUsersOnlyRestriction',
'canchangedrivebackground': 'canChangeDriveBackground',
'canchangedrivemembersonlyrestriction': 'canChangeDriveMembersOnlyRestriction',
'canchangesecurityupdateenabled': 'canChangeSecurityUpdateEnabled',
'canchangesharingfoldersrequiresorganizerpermissionrestriction': 'canChangeSharingFoldersRequiresOrganizerPermissionRestriction',
'canchangeviewerscancopycontent': 'canChangeViewersCanCopyContent',
'cancomment': 'canComment',
'cancopy': 'canCopy',
'candelete': 'canDelete',
'candeletechildren': 'canDeleteChildren',
'candeletedrive': 'canDeleteDrive',
'candisableinheritedpermissions': 'canDisableInheritedPermissions',
'candownload': 'canDownload',
'canedit': 'canEdit',
'canenableinheritedpermissions': 'canEnableInheritedPermissions',
'canlistchildren': 'canListChildren',
'canmanagemembers': 'canManageMembers',
'canmodifycontent': 'canModifyContent',
'canmodifycontentrestriction': 'canModifyContentRestriction',
'canmodifyeditorcontentrestriction': 'canModifyEditorContentRestriction',
'canmodifylabels': 'canModifyLabels',
'canmodifyownercontentrestriction': 'canModifyOwnerContentRestriction',
'canmovechildrenoutofdrive': 'canMoveChildrenOutOfDrive',
'canmovechildrenoutofteamdrive': 'canMoveChildrenOutOfDrive',
'canmovechildrenwithindrive': 'canMoveChildrenWithinDrive',
'canmovechildrenwithinteamdrive': 'canMoveChildrenWithinDrive',
'canmoveitemintodrive': 'canMoveItemIntoDrive',
'canmoveitemintoteamdrive': 'canMoveItemIntoDrive',
'canmoveitemoutofdrive': 'canMoveItemOutOfDrive',
'canmoveitemoutofteamdrive': 'canMoveItemOutOfDrive',
'canmoveitemwithindrive': 'canMoveItemWithinDrive',
'canmoveitemwithinteamdrive': 'canMoveItemWithinDrive',
'canmoveteamdriveitem': 'canMoveTeamDriveItem',
'canreaddrive': 'canReadDrive',
'canreadlabels': 'canReadLabels',
'canreadrevisions': 'canReadRevisions',
'canreadteamdrive': 'canReadDrive',
'canremovechildren': 'canRemoveChildren',
'canremovecontentrestriction': 'canRemoveContentRestriction',
'canremovemydriveparent': 'canRemoveMyDriveParent',
'canrename': 'canRename',
'canrenamedrive': 'canRenameDrive',
'canresetdriverestrictions': 'canResetDriveRestrictions',
'canshare': 'canShare',
'cantrash': 'canTrash',
'cantrashchildren': 'canTrashChildren',
'canuntrash': 'canUntrash',
}
DRIVE_CONTENT_RESTRICTIONS_SUBFIELDS_CHOICE_MAP = {
'ownerrestricted': 'ownerRestricted',
'readonly': 'readOnly',
'reason': 'reason',
'restrictinguser': 'restrictingUser',
'restrictiontime': 'restrictionTime',
'type': 'type',
}
DRIVE_DOWNLOAD_RESTRICTIONS_SUBFIELDS_CHOICE_MAP = {
'itemdownloadrestriction': 'itemDownloadRestriction',
'effectivedownloadrestrictionwithcontext': 'effectiveDownloadRestrictionWithContext',
}
DRIVE_LABELINFO_SUBFIELDS_CHOICE_MAP = {
'id': 'labels(id)',
'fields': 'labels(fields)',
'revisionid': 'labels(revisionId)',
}
DRIVE_OWNERS_SUBFIELDS_CHOICE_MAP = {
'displayname': 'displayName',
'emailaddress': 'emailAddress',
'isauthenticateduser': 'me',
'me': 'me',
'permissionid': 'permissionId',
'photolink': 'photoLink',
'picture': 'photoLink',
}
DRIVE_PARENTS_SUBFIELDS_CHOICE_MAP = {
'id': 'id',
'isroot': 'isRoot',
}
DRIVE_PERMISSIONS_SUBFIELDS_CHOICE_MAP = {
'additionalroles': 'role',
'allowfilediscovery': 'allowFileDiscovery',
'deleted': 'deleted',
'displayname': 'displayName',
'domain': 'domain',
'emailaddress': 'emailAddress',
'expirationdate': 'expirationTime',
'expirationtime': 'expirationTime',
'id': 'id',
'inheritedpermissionsdisabled': 'inheritedPermissionsDisabled',
'name': 'displayName',
'pendingowner': 'pendingOwner',
'permissiondetails': 'permissionDetails',
'photolink': 'photoLink',
'role': 'role',
'shareddrivepermissiondetails': 'permissionDetails',
'teamdrivepermissiondetails': 'permissionDetails',
'type': 'type',
'view': 'view',
'withlink': 'allowFileDiscovery',
}
DRIVE_SHARINGUSER_SUBFIELDS_CHOICE_MAP = {
'displayname': 'displayName',
'emailaddress': 'emailAddress',
'isauthenticateduser': 'me',
'me': 'me',
'name': 'displayName',
'permissionid': 'permissionId',
'photolink': 'photoLink',
'picture': 'photoLink',
}
DRIVE_SHORTCUTDETAILS_SUBFIELDS_CHOICE_MAP = {
'targetid': 'targetId',
'targetmimetype': 'targetMimeType',
'targetresourcekey': 'targetResourceKey',
}
DRIVE_SUBFIELDS_CHOICE_MAP = {
'capabilities': DRIVE_CAPABILITIES_SUBFIELDS_CHOICE_MAP,
'contentrestrictions': DRIVE_CONTENT_RESTRICTIONS_SUBFIELDS_CHOICE_MAP,
'downloadrestrictions': DRIVE_DOWNLOAD_RESTRICTIONS_SUBFIELDS_CHOICE_MAP,
'labelinfo': DRIVE_LABELINFO_SUBFIELDS_CHOICE_MAP,
'labels': DRIVE_LABEL_CHOICE_MAP,
'lastmodifyinguser': DRIVE_SHARINGUSER_SUBFIELDS_CHOICE_MAP,
'owners': DRIVE_OWNERS_SUBFIELDS_CHOICE_MAP,
'parents': DRIVE_PARENTS_SUBFIELDS_CHOICE_MAP,
'permissions': DRIVE_PERMISSIONS_SUBFIELDS_CHOICE_MAP,
'sharinguser': DRIVE_SHARINGUSER_SUBFIELDS_CHOICE_MAP,
'trashinguser': DRIVE_SHARINGUSER_SUBFIELDS_CHOICE_MAP,
'shortcutdetails': DRIVE_SHORTCUTDETAILS_SUBFIELDS_CHOICE_MAP,
}
DRIVE_LIST_FIELDS = {'owners', 'parents', 'permissions', 'permissionIds', 'spaces'}
FILEINFO_FIELDS_TITLES = ['name', 'mimeType']
FILEPATH_FIELDS_TITLES = ['name', 'id', 'mimeType', 'ownedByMe', 'parents', 'sharedWithMeTime', 'driveId']
FILEPATH_FIELDS = ','.join(FILEPATH_FIELDS_TITLES)
DRIVE_TIME_OBJECTS = {'createdTime', 'viewedByMeTime', 'modifiedByMeTime', 'modifiedTime', 'restrictionTime', 'sharedWithMeTime', 'trashedTime'}
def _getIncludeLabels(includeLabels):
labelIds = _getMain().getEntityList(Cmd.OB_CLASSIFICATION_LABEL_ID, shlexSplit=True)
for labelId in labelIds:
includeLabels.add(normalizeDriveLabelID(labelId))
def _finalizeIncludeLabels(includeLabels):
if includeLabels:
return ','.join(includeLabels)
return None
DRIVEFILE_PERMISSIONS_FOR_VIEW_CHOICES = ['published']
def _getIncludePermissionsForView(includePermissionsForView):
ipfwList = _getMain().getEntityList(Cmd.OB_STRING_LIST)
for ipfw in ipfwList:
if ipfw in DRIVEFILE_PERMISSIONS_FOR_VIEW_CHOICES:
includePermissionsForView.add(ipfw)
else:
_getMain().invalidChoiceExit(ipfw, DRIVEFILE_PERMISSIONS_FOR_VIEW_CHOICES, True)
def _finalizeIncludePermissionsForView(includePermissionsForView):
if includePermissionsForView:
return ','.join(includePermissionsForView)
return None
def _getDriveFieldSubField(field, fieldsList, parentsSubFields):
field, subField = field.split('.', 1)
if field in DRIVE_SUBFIELDS_CHOICE_MAP:
if field == 'parents':
fieldsList.append(DRIVE_FIELDS_CHOICE_MAP[field])
parentsSubFields[DRIVE_SUBFIELDS_CHOICE_MAP[field][subField]] = True
elif subField in DRIVE_SUBFIELDS_CHOICE_MAP[field]:
if field != 'labels':
if not isinstance(DRIVE_SUBFIELDS_CHOICE_MAP[field][subField], list):
fieldsList.append(f'{DRIVE_FIELDS_CHOICE_MAP[field]}.{DRIVE_SUBFIELDS_CHOICE_MAP[field][subField]}')
else:
for subSubField in DRIVE_SUBFIELDS_CHOICE_MAP[field][subField]:
fieldsList.append(f'{DRIVE_FIELDS_CHOICE_MAP[field]}.{subSubField}')
else:
fieldsList.append(DRIVE_SUBFIELDS_CHOICE_MAP[field][subField])
else:
_getMain().invalidChoiceExit(subField, list(DRIVE_SUBFIELDS_CHOICE_MAP[field]), True)
else:
_getMain().invalidChoiceExit(field, list(DRIVE_SUBFIELDS_CHOICE_MAP), True)
class DriveFileFields():
def __init__(self):
self.showSharedDriveNames = False
self.allFields = False
self.OBY = _getMain().OrderBy(DRIVEFILE_ORDERBY_CHOICE_MAP)
self.fieldsList = []
self.includeLabels = set()
self.includePermissionsForView = set()
self.parentsSubFields = {'id': False, 'isRoot': False, 'rootFolderId': None}
def SetAllParentsSubFields(self):
self.parentsSubFields['id'] = self.parentsSubFields['isRoot'] = True
def ProcessArgument(self, myarg):
if myarg == 'allfields':
self.fieldsList = []
self.allFields = True
elif myarg in DRIVE_LABEL_CHOICE_MAP:
_getMain().addFieldToFieldsList(myarg, DRIVE_LABEL_CHOICE_MAP, self.fieldsList)
elif myarg in DRIVE_FIELDS_CHOICE_MAP:
_getMain().addFieldToFieldsList(myarg, DRIVE_FIELDS_CHOICE_MAP, self.fieldsList)
if myarg == 'parents':
self.SetAllParentsSubFields()
elif myarg in {'drivename', 'shareddrivename', 'teamdrivename'}:
self.showSharedDriveNames = True
elif myarg == 'fields':
for field in _getMain()._getFieldsList():
if field in DRIVE_LABEL_CHOICE_MAP:
_getMain().addFieldToFieldsList(field, DRIVE_LABEL_CHOICE_MAP, self.fieldsList)
elif field.find('.') == -1:
if field in DRIVE_FIELDS_CHOICE_MAP:
_getMain().addFieldToFieldsList(field, DRIVE_FIELDS_CHOICE_MAP, self.fieldsList)
if field == 'parents':
self.SetAllParentsSubFields()
elif field in {'drivename', 'shareddrivename', 'teamdrivename'}:
self.showSharedDriveNames = True
else:
_getMain().invalidChoiceExit(field, list(DRIVE_FIELDS_CHOICE_MAP)+list(DRIVE_LABEL_CHOICE_MAP), True)
else:
_getDriveFieldSubField(field, self.fieldsList, self.parentsSubFields)
elif myarg == 'includelabels':
_getIncludeLabels(self.includeLabels)
elif myarg == 'includepermissionsforview':
_getIncludePermissionsForView(self.includePermissionsForView)
elif myarg.find('.') != -1:
_getDriveFieldSubField(myarg, self.fieldsList, self.parentsSubFields)
elif myarg == 'orderby':
self.OBY.GetChoice()
elif myarg in {'showdrivename', 'showshareddrivename', 'showteamdrivename'}:
self.showSharedDriveNames = True
else:
return False
return True
@property
def orderBy(self):
return self.OBY.orderBy
def _setSkipObjects(skipObjects, skipTitles, fieldsList):
for field in skipTitles:
if field != 'parents':
if field not in fieldsList:
skipObjects.add(field)
fieldsList.append(field)
else:
for xfield in fieldsList:
if xfield.startswith('parents'):
break
else:
skipObjects.add(field)
fieldsList.append('parents')
def _setGetPermissionsForMyDriveSharedDrives(fieldsList, permissionsFieldsList):
getPermissionDetailsForMyDrive = getPermissionsForSharedDrives = False
permissionsFields = None
for field in fieldsList:
if field.startswith('permissions'):
if field.find('.') != -1:
field, subField = field.split('.', 1)
permissionsFieldsList.append(subField)
else:
permissionsFieldsList.append('*')
if permissionsFieldsList:
if 'permissionDetails' in permissionsFieldsList:
getPermissionDetailsForMyDrive = True
getPermissionsForSharedDrives = True
permissionsFields = _getMain().getItemFieldsFromFieldsList('permissions', permissionsFieldsList, True)
return (getPermissionDetailsForMyDrive, getPermissionsForSharedDrives, permissionsFields)
# Do file permissions have to be gotten by API call?
def _setGetCheckFilePermissions(fileinfo, getPermissionDetailsForMyDrive, getPermissionsForSharedDrives, driveId, DLP):
filePerms = fileinfo.get('permissions')
if getPermissionsForSharedDrives and driveId and not filePerms:
return True
if getPermissionDetailsForMyDrive:
return True
if DLP.PM.checkDetails:
if not filePerms:
return True
permDetails = filePerms[0].get('permissionDetails')
if (not permDetails) or 'inherited' not in permDetails[0]:
return True
return False
SHOWLABELS_CHOICES = {'details', 'ids'}
def _formatFileDriveLabels(showLabels, labels, result, printMode, delimiter):
if showLabels == 'details':
result['labels'] = labels
else:
if printMode:
result['labels'] = len(labels)
result['labelsIds'] = delimiter.join([label['id'] for label in labels])
# gam <UserTypeEntity> info drivefile <DriveFileEntity>
# [returnidonly]
# [filepath|fullpath] [folderpathonly|parentpathonly [<Boolean>]] [pathdelimiter <Character>]
# [allfields|<DriveFieldName>*|(fields <DriveFieldNameList>)] [formatjson]
# (orderby <DriveFileOrderByFieldName> [ascending|descending])*
# [showdrivename] [showshareddrivepermissions]
# [(showlabels details|ids)|(includelabels <DriveLabelIDList>)]
# [includepermissionsforview published]
# [showparentsidsaslist] [followshortcuts [<Boolean>]]
# [stripcrsfromname] [formatjson]
# gam <UserTypeEntity> show fileinfo <DriveFileEntity>
# [returnidonly]
# [filepath|fullpath] [folderpathonly|parentpathonly [<Boolean>]] [pathdelimiter <Character>]
# [allfields|<DriveFieldName>*|(fields <DriveFieldNameList>)] [formatjson]
# (orderby <DriveFileOrderByFieldName> [ascending|descending])*
# [showdrivename] [showshareddrivepermissions]
# [(showlabels details|ids)|(includelabels <DriveLabelIDList>)]
# [includepermissionsforview published]
# [showparentsidsaslist] [followshortcuts [<Boolean>]]
# [stripcrsfromname] [formatjson]
def showFileInfo(users):
def _setSelectionFields():
_setSkipObjects(skipObjects, FILEINFO_FIELDS_TITLES, DFF.fieldsList)
if filepath:
_setSkipObjects(skipObjects, FILEPATH_FIELDS_TITLES, DFF.fieldsList)
if getPermissionsForSharedDrives or DFF.showSharedDriveNames:
_setSkipObjects(skipObjects, ['driveId'], DFF.fieldsList)
if followShortcuts:
_setSkipObjects(skipObjects, ['mimeType', 'shortcutDetails'], DFF.fieldsList)
getPermissionsForSharedDrives = filepath = fullpath = folderPathOnly = parentPathOnly = followShortcuts = \
returnIdOnly = showParentsIdsAsList = showNoParents = stripCRsFromName = False
pathDelimiter = '/'
showLabels = None
permissionsFieldsList = []
simpleLists = []
skipObjects = set()
fileIdEntity = getDriveFileEntity()
DFF = DriveFileFields()
FJQC = _getMain().FormatJSONQuoteChar()
while Cmd.ArgumentsRemaining():
myarg = _getMain().getArgument()
if myarg == 'filepath':
filepath = True
elif myarg == 'fullpath':
filepath = fullpath = True
elif myarg == 'folderpathonly':
folderPathOnly = _getMain().getBoolean()
elif myarg == 'parentpathonly':
parentPathOnly = _getMain().getBoolean()
elif myarg == 'pathdelimiter':
pathDelimiter = _getMain().getCharacter()
elif myarg == 'showparentsidsaslist':
showParentsIdsAsList = True
simpleLists.append('parentsIds')
elif myarg == 'stripcrsfromname':
stripCRsFromName = True
elif myarg == 'showlabels':
showLabels = _getMain().getChoice(SHOWLABELS_CHOICES)
elif myarg == 'showshareddrivepermissions':
permissionsFieldsList = DRIVEFILE_BASIC_PERMISSION_FIELDS.copy()
elif myarg == 'returnidonly':
returnIdOnly = True
elif myarg == 'followshortcuts':
followShortcuts = _getMain().getBoolean()
elif DFF.ProcessArgument(myarg):
pass
else:
FJQC.GetFormatJSON(myarg)
_, getPermissionsForSharedDrives, permissionsFields = _setGetPermissionsForMyDriveSharedDrives(DFF.fieldsList, permissionsFieldsList)
if DFF.fieldsList:
_setSelectionFields()
if followShortcuts:
DFF.fieldsList.extend(['mimeType', 'shortcutDetails'])
fields = _getMain().getFieldsFromFieldsList(DFF.fieldsList)
showNoParents = 'parents' in DFF.fieldsList
else:
fields = '*'
DFF.SetAllParentsSubFields()
skipObjects = skipObjects.union(DEFAULT_SKIP_OBJECTS)
showNoParents = True
includeLabels = _finalizeIncludeLabels(DFF.includeLabels)
includePermissionsForView = _finalizeIncludePermissionsForView(DFF.includePermissionsForView)
pathFields = FILEPATH_FIELDS
i, count, users = _getMain().getEntityArgument(users)
for user in users:
i += 1
if returnIdOnly:
GC.Values[GC.SHOW_GETTINGS] = False
user, drive, jcount = _validateUserGetFileIDs(user, i, count, fileIdEntity,
entityType=[Ent.DRIVE_FILE_OR_FOLDER, None][FJQC.formatJSON or returnIdOnly],
orderBy=DFF.orderBy)
if jcount == 0:
continue
if returnIdOnly:
for fileId in fileIdEntity['list']:
_getMain().writeStdout(f'{fileId}\n')
continue
if not showParentsIdsAsList and DFF.parentsSubFields['isRoot']:
try:
DFF.parentsSubFields['rootFolderId'] = _getMain().callGAPI(drive.files(), 'get',
throwReasons=GAPI.DRIVE_USER_THROW_REASONS,
fileId=ROOT, fields='id')['id']
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
continue
if filepath:
filePathInfo = initFilePathInfo(pathDelimiter)
if fullpath:
fileTree, status = initFileTree(drive, fileIdEntity.get('shareddrive'), None, [], True, user, i, count)
if not status:
continue
else:
fileTree = None
Ind.Increment()
j = 0
for fileId in fileIdEntity['list']:
j += 1
try:
result = _getMain().callGAPI(drive.files(), 'get',
throwReasons=GAPI.DRIVE_GET_THROW_REASONS+[GAPI.INVALID],
fileId=fileId, includeLabels=includeLabels, includePermissionsForView=includePermissionsForView,
fields=fields, supportsAllDrives=True)
if followShortcuts and result['mimeType'] == MIMETYPE_GA_SHORTCUT:
fileId = result['shortcutDetails']['targetId']
result = _getMain().callGAPI(drive.files(), 'get',
throwReasons=GAPI.DRIVE_GET_THROW_REASONS+[GAPI.INVALID],
fileId=fileId, includeLabels=includeLabels, includePermissionsForView=includePermissionsForView,
fields=fields, supportsAllDrives=True)
if stripCRsFromName:
result['name'] = _stripControlCharsFromName(result['name'])
driveId = result.get('driveId')
if driveId:
if result['mimeType'] == MIMETYPE_GA_FOLDER and result['name'] == _getMain().TEAM_DRIVE:
result['name'] = _getSharedDriveNameFromId(drive, driveId)
if DFF.showSharedDriveNames:
result['driveName'] = _getSharedDriveNameFromId(drive, driveId)
if showNoParents:
result.setdefault('parents', [])
if getPermissionsForSharedDrives and driveId and 'permissions' not in result:
try:
result['permissions'] = _getMain().callGAPIpages(drive.permissions(), 'list', 'permissions',
throwReasons=GAPI.DRIVE3_GET_ACL_REASONS,
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
fileId=fileId, fields=permissionsFields, supportsAllDrives=True)
for permission in result['permissions']:
permission.pop('teamDrivePermissionDetails', None)
except (GAPI.insufficientAdministratorPrivileges, GAPI.insufficientFilePermissions) as e:
if fields != '*':
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.DRIVE_FILE_OR_FOLDER_ID, fileId], str(e), j, jcount)
continue
if showLabels is not None:
labels = _getMain().callGAPIitems(drive.files(), 'listLabels', 'labels',
throwReasons=GAPI.DRIVE_GET_THROW_REASONS,
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS+[GAPI.UNKNOWN_ERROR],
fileId=fileId)
_formatFileDriveLabels(showLabels, labels, result, False, ' ')
if not FJQC.formatJSON:
_getMain().printEntity([_getMain()._getEntityMimeType(result), f'{result["name"]} ({fileId})'], j, jcount)
Ind.Increment()
if filepath:
if fullpath:
extendFileTree(fileTree, [result], None, False)
extendFileTreeParents(drive, fileTree, pathFields)
if not FJQC.formatJSON:
_, paths, _ = getFilePaths(drive, fileTree, result, filePathInfo, addParentsToTree=True,
fullpath=fullpath, folderPathOnly=folderPathOnly, parentPathOnly=parentPathOnly)
kcount = len(paths)
_getMain().printKeyValueList(['paths', kcount])
Ind.Increment()
for path in sorted(paths):
_getMain().printKeyValueList(['path', path])
Ind.Decrement()
else:
addFilePathsToInfo(drive, fileTree, result, filePathInfo,
addParentsToTree=True, folderPathOnly=folderPathOnly, parentPathOnly=parentPathOnly)
if fullpath:
# Save simple parents list as mappings turn it into a list of dicts
fpparents = result['parents'][:]
_mapDriveInfo(result, DFF.parentsSubFields, showParentsIdsAsList)
if not FJQC.formatJSON:
_getMain().showJSON(None, result, skipObjects=skipObjects, timeObjects=DRIVE_TIME_OBJECTS, simpleLists=simpleLists,
dictObjectsKey={'owners': 'displayName', 'fields': 'id', 'labels': 'id', 'user': 'emailAddress', 'parents': 'id',
'permissions': 'displayName', 'permissionDetails': 'inherited'})
Ind.Decrement()
else:
_getMain().printLine(json.dumps(_getMain().cleanJSON(result, skipObjects=skipObjects, timeObjects=DRIVE_TIME_OBJECTS), ensure_ascii=False, sort_keys=True))
if fullpath:
# Restore simple parents list
fileTree[fileId]['info']['parents'] = fpparents[:]
except (GAPI.fileNotFound, GAPI.forbidden, GAPI.internalError,
GAPI.insufficientFilePermissions, GAPI.unknownError, GAPI.invalid) as e:
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.DRIVE_FILE_OR_FOLDER_ID, fileId], str(e), j, jcount)
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
break
Ind.Decrement()

915
src/gam/cmd/drive/files.py Normal file
View File

@@ -0,0 +1,915 @@
"""File creation, update, shortcuts.
Part of the drive sub-package, extracted from drive.py."""
"""GAM Google Drive file, permission, shared drive, and label management."""
import re
import sys
from gamlib import glaction
from gamlib import glapi as API
from gamlib import glcfg as GC
from gamlib import glclargs
from gamlib import glentity
from gamlib import glgapi as GAPI
from gamlib import glglobals as GM
from gamlib import glindent
from gamlib import glmsgs as Msg
Act = glaction.GamAction()
Ent = glentity.GamEntity()
Ind = glindent.GamIndent()
Cmd = glclargs.GamCLArgs()
APPLICATION_VND_GOOGLE_APPS = 'application/vnd.google-apps.'
MIMETYPE_GA_DOCUMENT = f'{APPLICATION_VND_GOOGLE_APPS}document'
MIMETYPE_GA_DRAWING = f'{APPLICATION_VND_GOOGLE_APPS}drawing'
MIMETYPE_GA_FILE = f'{APPLICATION_VND_GOOGLE_APPS}file'
MIMETYPE_GA_FOLDER = f'{APPLICATION_VND_GOOGLE_APPS}folder'
MIMETYPE_GA_FORM = f'{APPLICATION_VND_GOOGLE_APPS}form'
MIMETYPE_GA_FUSIONTABLE = f'{APPLICATION_VND_GOOGLE_APPS}fusiontable'
MIMETYPE_GA_JAM = f'{APPLICATION_VND_GOOGLE_APPS}jam'
MIMETYPE_GA_MAP = f'{APPLICATION_VND_GOOGLE_APPS}map'
MIMETYPE_GA_PRESENTATION = f'{APPLICATION_VND_GOOGLE_APPS}presentation'
MIMETYPE_GA_SCRIPT = f'{APPLICATION_VND_GOOGLE_APPS}script'
MIMETYPE_GA_SCRIPT_JSON = f'{APPLICATION_VND_GOOGLE_APPS}script+json'
MIMETYPE_GA_SHORTCUT = f'{APPLICATION_VND_GOOGLE_APPS}shortcut'
MIMETYPE_GA_3P_SHORTCUT = f'{APPLICATION_VND_GOOGLE_APPS}drive-sdk'
MIMETYPE_GA_SITE = f'{APPLICATION_VND_GOOGLE_APPS}site'
MIMETYPE_GA_SPREADSHEET = f'{APPLICATION_VND_GOOGLE_APPS}spreadsheet'
ME_IN_OWNERS = "'me' in owners"
ME_IN_OWNERS_AND = ME_IN_OWNERS + " and "
NOT_ME_IN_OWNERS = "not " + ME_IN_OWNERS
NOT_ME_IN_OWNERS_AND = NOT_ME_IN_OWNERS + " and "
WITH_ANY_FILE_NAME = "name = '{0}'"
WITH_MY_FILE_NAME = ME_IN_OWNERS_AND + WITH_ANY_FILE_NAME
WITH_OTHER_FILE_NAME = NOT_ME_IN_OWNERS_AND + WITH_ANY_FILE_NAME
ROOT = 'root'
ORPHANS = 'Orphans'
SHARED_WITHME = 'SharedWithMe'
SHARED_DRIVES = 'SharedDrives'
def _getMain():
return sys.modules['gam']
def __getattr__(name):
"""Fall back to gam module for any undefined names."""
main = _getMain()
try:
return getattr(main, name)
except AttributeError:
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
def processFilenameReplacements(name, replacements):
for replacement in replacements:
name = re.sub(replacement[0], replacement[1], name)
return name
def addTimestampToFilename(parameters, body):
tdtime = arrow.now(GC.Values[GC.TIMEZONE])
body['name'] += ' - '
if not parameters[DFA_TIMEFORMAT]:
body['name'] += _getMain().ISOformatTimeStamp(tdtime)
else:
body['name'] += tdtime.strftime(parameters[DFA_TIMEFORMAT])
createReturnItemMap = {
'returnidonly': 'id',
'returnlinkonly': 'webViewLink',
'returneditlinkonly': 'editLink'
}
# gam <UserTypeEntity> create drivefile
# [(localfile <FileName>|-)|(url <URL>)]
# [(drivefilename|newfilename <DriveFileName>) | (replacefilename <REMatchPattern> <RESubstitution>)*]
# [stripnameprefix <String>]
# [timestamp <Boolean>]] [timeformat <DateTimeFormat>]
# <DriveFileCreateAttribute>* [noduplicate]
# [(csv [todrive <ToDriveAttribute>*] (addcsvdata <FieldName> <String>)*)) |
# (returnidonly|returnlinkonly|returneditlinkonly|showdetails)]
def createDriveFile(users):
csvPF = media_body = None
addCSVData = {}
returnIdLink = None
noDuplicate = showDetails = False
body = {}
newName = None
assignLocalName = True
parameters = initDriveFileAttributes()
while Cmd.ArgumentsRemaining():
myarg = _getMain().getArgument()
if myarg in {'drivefilename', 'newfilename'}:
newName = _getMain().getString(Cmd.OB_DRIVE_FILE_NAME)
assignLocalName = False
elif myarg in createReturnItemMap:
returnIdLink = createReturnItemMap[myarg]
showDetails = False
elif myarg == 'showdetails':
returnIdLink = None
showDetails = True
elif myarg == 'noduplicate':
noDuplicate = True
elif myarg == 'csv':
csvPF = _getMain().CSVPrintFile()
elif csvPF and myarg == 'todrive':
csvPF.GetTodriveParameters()
elif csvPF and myarg == 'addcsvdata':
_getMain().getAddCSVData(addCSVData)
else:
getDriveFileAttribute(myarg, body, parameters, False)
if assignLocalName and parameters[DFA_LOCALFILENAME] and parameters[DFA_LOCALFILENAME] != '-':
newName = parameters[DFA_LOCALFILENAME]
if newName:
if parameters[DFA_STRIPNAMEPREFIX] and newName.startswith(parameters[DFA_STRIPNAMEPREFIX]):
newName = newName[len(parameters[DFA_STRIPNAMEPREFIX]):]
if parameters[DFA_REPLACEFILENAME]:
body['name'] = processFilenameReplacements(newName, parameters[DFA_REPLACEFILENAME])
else:
body['name'] = newName
else:
body['name'] = 'Untitled'
if parameters[DFA_TIMESTAMP]:
addTimestampToFilename(parameters, body)
if parameters[DFA_LOCALFILEPATH]:
if parameters[DFA_LOCALFILEPATH] != '-' and parameters[DFA_PRESERVE_FILE_TIMES]:
setPreservedFileTimes(body, parameters, False)
if body.get('mimeType') == MIMETYPE_GA_SCRIPT_JSON:
parameters[DFA_LOCALMIMETYPE] = body['mimeType']
media_body = getMediaBody(parameters)
elif parameters[DFA_URL]:
media_body = getMediaBody(parameters)
body['mimeType'] = parameters[DFA_LOCALMIMETYPE]
if csvPF:
csvPF.SetTitles(['User', 'name', 'id'])
if showDetails:
csvPF.AddTitles(['parentId', 'mimeType'])
if addCSVData:
csvPF.AddTitles(sorted(addCSVData.keys()))
Act.Set(Act.CREATE)
i, count, users = _getMain().getEntityArgument(users)
for user in users:
i += 1
user, drive = _getMain().buildGAPIServiceObject(API.DRIVE3, user, i, count)
if not drive:
continue
if not _getDriveFileParentInfo(drive, user, i, count, body, parameters):
continue
entityType = _getMain()._getEntityMimeType(body) if 'mimeType' in body else Ent.DRIVE_FILE
try:
if noDuplicate:
# Check for existing file/folder, do not duplicate
files = _getMain().callGAPIitems(drive.files(), 'list', 'files',
throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.INVALID_QUERY, GAPI.INVALID],
q=f"name = '{escapeDriveFileName(body['name'])}'and '{body['parents'][0]}' in parents and trashed = false",
fields='files(id)', **parameters['searchargs'])
if files:
_getMain().entityActionNotPerformedWarning([Ent.USER, user, entityType, body['name'], Ent.DRIVE_PARENT_FOLDER_ID, body['parents'][0]],
f"{Msg.DUPLICATE} IDs {','.join([file['id'] for file in files])}", i, count)
continue
result = _getMain().callGAPI(drive.files(), 'create',
throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.FORBIDDEN, GAPI.INSUFFICIENT_PERMISSIONS, GAPI.INSUFFICIENT_PARENT_PERMISSIONS,
GAPI.PERMISSION_DENIED, GAPI.INVALID, GAPI.BAD_REQUEST, GAPI.CANNOT_ADD_PARENT,
GAPI.FILE_NOT_FOUND, GAPI.UNKNOWN_ERROR, GAPI.INTERNAL_ERROR,
GAPI.STORAGE_QUOTA_EXCEEDED, GAPI.TEAMDRIVES_SHARING_RESTRICTION_NOT_ALLOWED,
GAPI.TEAMDRIVE_FILE_LIMIT_EXCEEDED, GAPI.TEAMDRIVE_HIERARCHY_TOO_DEEP,
GAPI.UPLOAD_TOO_LARGE, GAPI.TEAMDRIVES_SHORTCUT_FILE_NOT_SUPPORTED],
ocrLanguage=parameters[DFA_OCRLANGUAGE],
ignoreDefaultVisibility=parameters[DFA_IGNORE_DEFAULT_VISIBILITY],
keepRevisionForever=parameters[DFA_KEEP_REVISION_FOREVER],
useContentAsIndexableText=parameters[DFA_USE_CONTENT_AS_INDEXABLE_TEXT],
media_body=media_body, body=body, fields='id,name,mimeType,parents,webViewLink', supportsAllDrives=True)
parentId = result['parents'][0] if 'parents' in result and result['parents'] else _getMain().UNKNOWN
if returnIdLink:
writeReturnIdLink(returnIdLink, parameters[DFA_LOCALMIMETYPE], result)
elif not csvPF:
kvList = [Ent.USER, user]
if not showDetails:
titleInfo = f'{result["name"]}({result["id"]})'
if parameters[DFA_LOCALFILENAME]:
kvList.extend([Ent.DRIVE_FILE, titleInfo])
else:
kvList.extend([_getMain()._getEntityMimeType(result), titleInfo])
else:
if result['mimeType'] != MIMETYPE_GA_FOLDER:
kvList.extend([Ent.DRIVE_FILE, result['name'], Ent.DRIVE_FILE_ID, result['id']])
else:
kvList.extend([Ent.DRIVE_FOLDER, result['name'], Ent.DRIVE_FOLDER_ID, result['id']])
kvList.extend([Ent.DRIVE_PARENT_FOLDER_ID, parentId, Ent.MIMETYPE, result['mimeType']])
if media_body:
_getMain().entityModifierNewValueActionPerformed(kvList, Act.MODIFIER_WITH_CONTENT_FROM, parameters[DFA_LOCALFILENAME] or parameters[DFA_URL], i, count)
else:
_getMain().entityActionPerformed(kvList, i, count)
else:
row = {'User': user, 'name': result['name'], 'id': result['id']}
if showDetails:
row.update({'parentId': parentId, 'mimeType': result['mimeType']})
if addCSVData:
row.update(addCSVData)
csvPF.WriteRow(row)
except (GAPI.forbidden, GAPI.insufficientFilePermissions, GAPI.insufficientParentPermissions,
GAPI.invalidQuery, GAPI.permissionDenied, GAPI.invalid, GAPI.badRequest, GAPI.cannotAddParent,
GAPI.fileNotFound, GAPI.unknownError, GAPI.storageQuotaExceeded, GAPI.teamDrivesSharingRestrictionNotAllowed,
GAPI.teamDriveFileLimitExceeded, GAPI.teamDriveHierarchyTooDeep,
GAPI.uploadTooLarge, GAPI.teamDrivesShortcutFileNotSupported) as e:
_getMain().entityActionFailedWarning([Ent.USER, user, entityType, body['name']], str(e), i, count)
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
if csvPF:
csvPF.writeCSVfile('Files')
# gam <UserTypeEntity> create drivefolderpath
# [pathdelimiter <Character>]
# ((fullpath <DriveFolderPath) |
# (path <DriveFolderPath [<DriveFileParentAttribute>]) |
# (list <DriveFolderNameList>) [<DriveFileParentAttribute>]))
# [(csv [todrive <ToDriveAttribute>*] (addcsvdata <FieldName> <String>)*) |
# returnidonly]
def createDriveFolderPath(users):
csvPF = None
addCSVData = {}
fullPath = parentSpecified = returnIdOnly = False
parentBody = {}
parentParms = initDriveFileAttributes()
driveFolderNameList = []
pathDelimiter = '/'
while Cmd.ArgumentsRemaining():
myarg = _getMain().getArgument()
if myarg == 'pathdelimiter':
pathDelimiter = _getMain().getCharacter()
elif myarg == 'fullpath':
folderPathLocation = Cmd.Location()
driveFolderNameList = _getMain().getString(Cmd.OB_DRIVE_FOLDER_PATH).lstrip(pathDelimiter).strip(' ').split(pathDelimiter)
if len(driveFolderNameList) > 0:
if driveFolderNameList[0].lower() == _getMain().MY_DRIVE.lower():
parentParms[DFA_PARENTID] = ROOT
driveFolderNameList = driveFolderNameList[1:]
elif driveFolderNameList[0].lower() == SHARED_DRIVES.lower() and len(driveFolderNameList) > 1:
parentParms[DFA_SHAREDDRIVE_PARENT] = driveFolderNameList[1]
driveFolderNameList = driveFolderNameList[2:]
else:
_getMain().usageErrorExit(Msg.FULL_PATH_MUST_START_WITH_DRIVE.format(_getMain().MY_DRIVE, f'{SHARED_DRIVES}{pathDelimiter}<SharedDriveName>'))
fullPath = True
elif myarg == 'path':
folderPathLocation = Cmd.Location()
driveFolderNameList = _getMain().getString(Cmd.OB_DRIVE_FOLDER_PATH).strip(' ').split(pathDelimiter)
elif myarg == 'list':
folderPathLocation = Cmd.Location()
driveFolderNameList = _getMain().shlexSplitList(_getMain().getString(Cmd.OB_DRIVE_FOLDER_NAME_LIST), dataDelimiter=',')
elif getDriveFileParentAttribute(myarg, parentParms):
parentSpecified = True
elif myarg == 'returnidonly':
returnIdOnly = True
elif myarg == 'csv':
csvPF = _getMain().CSVPrintFile(['User', 'name', 'id', 'status', 'pathIndex', 'pathLength'], 'sortall')
elif csvPF and myarg == 'todrive':
csvPF.GetTodriveParameters()
elif csvPF and myarg == 'addcsvdata':
_getMain().getAddCSVData(addCSVData)
else:
_getMain().unknownArgumentExit()
if not driveFolderNameList:
Cmd.SetLocation(folderPathLocation)
_getMain().emptyArgumentExit('fullpath|path|list')
if fullPath and parentSpecified:
_getMain().usageErrorExit(Msg.ARE_MUTUALLY_EXCLUSIVE.format('fullpath', '<DriveFileParentAttribute>'))
for folderName in driveFolderNameList:
if not folderName.strip():
Cmd.SetLocation(folderPathLocation)
_getMain().usageErrorExit(Msg.ALL_FOLDER_NAMES_MUST_BE_NON_BLANK.format(driveFolderNameList))
if csvPF:
if addCSVData:
csvPF.AddTitles(sorted(addCSVData.keys()))
i, count, users = _getMain().getEntityArgument(users)
for user in users:
i += 1
user, drive = _getMain().buildGAPIServiceObject(API.DRIVE3, user, i, count)
if not drive:
continue
if not _getDriveFileParentInfo(drive, user, i, count, parentBody, parentParms,
defaultToRoot=True, entityType=Ent.DRIVE_FOLDER):
continue
parentId = parentBody['parents'][0]
if parentParms.get('searchargs', {}).get('corpora', ''):
query = _getMain().ANY_NON_TRASHED_FOLDER_NAME_WITH_PARENTS
else:
query = _getMain().MY_NON_TRASHED_FOLDER_NAME_WITH_PARENTS
errors = False
createOnly = False
if not returnIdOnly and not csvPF:
_getMain().entityPerformAction([Ent.USER, user, Ent.DRIVE_FOLDER_PATH, ''], i, count)
jcount = len(driveFolderNameList)
Ind.Increment()
j = 0
for folderName in driveFolderNameList:
j += 1
try:
folderFound = False
if not createOnly:
op = 'Find Folder'
result = _getMain().callGAPIpages(drive.files(), 'list', 'files',
throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.BAD_REQUEST, GAPI.INSUFFICIENT_PERMISSIONS],
retryReasons=[GAPI.UNKNOWN_ERROR],
q=query.format(escapeDriveFileName(folderName), parentId),
supportsAllDrives=True, includeItemsFromAllDrives=True,
fields='nextPageToken,files(id,name)')
if result:
if len(result) > 1:
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.DRIVE_FOLDER, folderName], f'{op}: {len(result)} Folders with same name')
errors = True
break
parentId = result[0]['id']
parentName = result[0]['name']
folderFound = True
Act.Set(Act.EXISTS)
if not folderFound:
op = 'Create Folder'
result = _getMain().callGAPI(drive.files(), 'create',
throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.FORBIDDEN, GAPI.INSUFFICIENT_PERMISSIONS, GAPI.INSUFFICIENT_PARENT_PERMISSIONS,
GAPI.UNKNOWN_ERROR, GAPI.BAD_REQUEST,
GAPI.STORAGE_QUOTA_EXCEEDED, GAPI.TEAMDRIVE_FILE_LIMIT_EXCEEDED, GAPI.TEAMDRIVE_HIERARCHY_TOO_DEEP],
body={'parents': [parentId], 'name': folderName, 'mimeType': MIMETYPE_GA_FOLDER}, fields='id,name', supportsAllDrives=True)
parentId = result['id']
parentName = result['name']
createOnly = True
Act.Set(Act.CREATE)
except (GAPI.forbidden, GAPI.insufficientPermissions, GAPI.insufficientParentPermissions,
GAPI.unknownError, GAPI.badRequest, GAPI.storageQuotaExceeded, GAPI.teamDriveFileLimitExceeded, GAPI.teamDriveHierarchyTooDeep) as e:
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.DRIVE_FOLDER, folderName], f'{op}: {str(e)}', j, jcount)
errors = True
break
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
errors = True
break
if not returnIdOnly:
if not csvPF:
_getMain().entityActionPerformed([Ent.USER, user, Ent.DRIVE_FOLDER_NAME, f'{parentName}({parentId})'],
j, jcount)
else:
row = {'User': user, 'name': parentName, 'id': parentId, 'status': Act.Performed(), 'pathIndex': j, 'pathLength': jcount}
if addCSVData:
row.update(addCSVData)
csvPF.WriteRow(row)
if returnIdOnly and not errors:
_getMain().writeStdout(f'{parentId}\n')
Ind.Decrement()
if csvPF:
csvPF.writeCSVfile('Folders')
# gam <UserTypeEntity> create drivefileshortcut <DriveFileEntity> [shortcutname <String>]
# [<DriveFileParentAttribute>|convertparents]
# [(csv [todrive <ToDriveAttribute>*]) | returnidonly]
def createDriveFileShortcut(users):
csvPF = baseShortcutName = None
convertParents = newParentsSpecified = returnIdOnly = False
fileIdEntity = getDriveFileEntity()
parentBody = {}
parentParms = initDriveFileAttributes()
while Cmd.ArgumentsRemaining():
myarg = _getMain().getArgument()
if myarg == 'shortcutname':
baseShortcutName = _getMain().getString(Cmd.OB_DRIVE_FILE_NAME)
elif myarg == 'returnidonly':
returnIdOnly = True
elif myarg == 'csv':
csvPF = _getMain().CSVPrintFile(['User', 'name', 'id', 'targetName', 'targetId'], 'sortall')
elif csvPF and myarg == 'todrive':
csvPF.GetTodriveParameters()
elif getDriveFileParentAttribute(myarg, parentParms):
if convertParents:
_getMain().usageErrorExit(Msg.ARE_MUTUALLY_EXCLUSIVE.format(myarg, 'convertparents'))
newParentsSpecified = True
elif myarg == 'convertparents':
if newParentsSpecified:
_getMain().usageErrorExit(Msg.ARE_MUTUALLY_EXCLUSIVE.format(myarg, '<DriveFileParentAttribute>'))
convertParents = True
else:
_getMain().unknownArgumentExit()
if fileIdEntity['query']:
fileIdEntity['query'] = fileIdEntity['query']+_getMain().AND_NOT_SHORTCUT
elif fileIdEntity['shareddrivefilequery']:
fileIdEntity['shareddrivefilequery'] = fileIdEntity['shareddrivefilequery']+_getMain().AND_NOT_SHORTCUT
i, count, users = _getMain().getEntityArgument(users)
for user in users:
i += 1
user, drive, jcount = _validateUserGetFileIDs(user, i, count, fileIdEntity)
if not returnIdOnly and not csvPF:
_getMain().entityPerformActionSubItemModifierNumItems([Ent.USER, user], Ent.DRIVE_FILE_SHORTCUT,
Act.MODIFIER_FOR, jcount, Ent.DRIVE_FILE_OR_FOLDER, i, count)
if jcount == 0:
continue
if not _getDriveFileParentInfo(drive, user, i, count, parentBody, parentParms):
continue
if newParentsSpecified:
newParents = parentBody['parents']
numNewParents = len(newParents)
elif not convertParents:
try:
rootFolderId = _getMain().callGAPI(drive.files(), 'get',
throwReasons=GAPI.DRIVE_USER_THROW_REASONS,
fileId=ROOT, fields='id')['id']
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
continue
newParents = [rootFolderId]
numNewParents = 1
Ind.Increment()
j = 0
for fileId in fileIdEntity['list']:
j += 1
Act.Set(Act.CREATE)
try:
target = _getMain().callGAPI(drive.files(), 'get',
throwReasons=GAPI.DRIVE_GET_THROW_REASONS,
fileId=fileId, fields='mimeType,name,parents', supportsAllDrives=True)
except (GAPI.forbidden, GAPI.insufficientFilePermissions, GAPI.invalid, GAPI.badRequest,
GAPI.fileNotFound, GAPI.unknownError, GAPI.teamDrivesSharingRestrictionNotAllowed) as e:
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.DRIVE_FILE_OR_FOLDER, fileId], str(e), j, jcount)
continue
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
break
targetName = target['name']
if baseShortcutName:
shortcutName = baseShortcutName.replace('#filename#', targetName)
else:
shortcutName = targetName
targetEntityType = _getMain()._getEntityMimeType(target)
if convertParents:
newParents = target.get('parents', [])[:-1]
numNewParents = len(newParents)
if numNewParents <= 1:
_getMain().entityActionNotPerformedWarning([Ent.USER, user, targetEntityType, targetName, Ent.DRIVE_FILE_SHORTCUT, None],
Msg.NO_PARENTS_TO_CONVERT_TO_SHORTCUTS, j, jcount)
continue
removeParents = []
body = {'name': shortcutName, 'mimeType': MIMETYPE_GA_SHORTCUT, 'parents': None, 'shortcutDetails': {'targetId': fileId}}
if not returnIdOnly and not csvPF:
_getMain().entityPerformActionNumItems([Ent.USER, user, targetEntityType, targetName], numNewParents, Ent.DRIVE_FILE_SHORTCUT, j, jcount)
try:
existingShortcuts = _getMain().callGAPI(drive.files(), 'list',
throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.INVALID_QUERY, GAPI.INVALID],
retryReasons=[GAPI.UNKNOWN_ERROR],
supportsAllDrives=True, includeItemsFromAllDrives=True,
q=f"shortcutDetails.targetId = '{fileId}' and trashed = False", fields='files(id,name,parents)')['files']
except (GAPI.invalidQuery, GAPI.invalid, GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy):
existingShortcuts = []
Ind.Increment()
k = 0
for parentId in newParents:
k += 1
duplicateShortcut = False
for shortcut in existingShortcuts:
if parentId in shortcut['parents'] and shortcutName == shortcut['name']:
_getMain().entityActionNotPerformedWarning([Ent.USER, user, targetEntityType, targetName, Ent.DRIVE_FILE_SHORTCUT, f'{shortcut["name"]}({shortcut["id"]})'],
Msg.DUPLICATE, k, numNewParents)
duplicateShortcut = True
break
if duplicateShortcut:
continue
body['parents'] = [parentId]
try:
result = _getMain().callGAPI(drive.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_FILE_LIMIT_EXCEEDED, GAPI.TEAMDRIVE_HIERARCHY_TOO_DEEP,
GAPI.SHORTCUT_TARGET_INVALID],
body=body, fields='id,name', supportsAllDrives=True)
removeParents.append(parentId)
if returnIdOnly:
_getMain().writeStdout(f'{result["id"]}\n')
elif not csvPF:
_getMain().entityActionPerformed([Ent.USER, user, targetEntityType, targetName, Ent.DRIVE_FILE_SHORTCUT, f'{result["name"]}({result["id"]})'],
k, numNewParents)
else:
csvPF.WriteRow({'User': user, 'name': result['name'], 'id': result['id'], 'targetName': targetName, 'targetId': fileId})
except (GAPI.forbidden, GAPI.insufficientFilePermissions, GAPI.insufficientParentPermissions, GAPI.invalid, GAPI.badRequest,
GAPI.fileNotFound, GAPI.unknownError, GAPI.storageQuotaExceeded, GAPI.teamDrivesSharingRestrictionNotAllowed,
GAPI.teamDriveFileLimitExceeded, GAPI.teamDriveHierarchyTooDeep, GAPI.shortcutTargetInvalid) as e:
_getMain().entityActionFailedWarning([Ent.USER, user, targetEntityType, targetName, Ent.DRIVE_FILE_SHORTCUT, body['name']], str(e), k, numNewParents)
Ind.Decrement()
if convertParents and removeParents:
if not returnIdOnly and not csvPF:
lcount = len(removeParents)
Act.Set(Act.DELETE)
_getMain().entityPerformActionNumItems([Ent.USER, user, targetEntityType, targetName], lcount, Ent.DRIVE_PARENT_FOLDER_REFERENCE, j, jcount)
try:
_getMain().callGAPI(drive.files(), 'update',
throwReasons=GAPI.DRIVE_ACCESS_THROW_REASONS+[GAPI.BAD_REQUEST],
fileId=fileId,
removeParents=','.join(removeParents), body={}, fields='id', supportsAllDrives=True)
if not returnIdOnly and not csvPF:
Ind.Increment()
for l, parent in enumerate(removeParents):
_getMain().entityActionPerformed([Ent.USER, user, targetEntityType, targetName, Ent.DRIVE_PARENT_FOLDER_REFERENCE, parent], l+1, lcount)
Ind.Decrement()
except (GAPI.forbidden, GAPI.insufficientFilePermissions, GAPI.invalid, GAPI.badRequest,
GAPI.fileNotFound, GAPI.unknownError, GAPI.teamDrivesSharingRestrictionNotAllowed) as e:
_getMain().entityActionFailedWarning([Ent.USER, user, targetEntityType, targetName, Ent.DRIVE_PARENT_FOLDER_REFERENCE, str(l)], str(e), j, jcount)
Ind.Decrement()
if csvPF:
csvPF.writeCSVfile('Shortcuts')
SHORTCUT_CODE_VALID = 0
SHORTCUT_CODE_SHORTCUT_NOT_FOUND = 1
SHORTCUT_CODE_NOT_A_SHORTCUT = 2
SHORTCUT_CODE_TARGET_NOT_FOUND = 3
SHORTCUT_CODE_MIMETYPE_MISMATCH = 4
# gam <UserTypeEntity> check drivefileshortcut <DriveFileEntity>
# [csv [todrive <ToDriveAttribute>*]]
def checkDriveFileShortcut(users):
csvPF = None
fileIdEntity = getDriveFileEntity()
while Cmd.ArgumentsRemaining():
myarg = _getMain().getArgument()
if myarg == 'csv':
csvPF = _getMain().CSVPrintFile(['User', 'name', 'id', 'owner', 'parentId', 'shortcutDetails.targetId', 'shortcutDetails.targetMimeType',
'targetName', 'targetId', 'targetMimeType', 'code'], 'sortall')
elif csvPF and myarg == 'todrive':
csvPF.GetTodriveParameters()
else:
_getMain().unknownArgumentExit()
scfields = 'id,name,mimeType,owners(emailAddress),parents,shortcutDetails'
trfields = 'id,name,mimeType'
i, count, users = _getMain().getEntityArgument(users)
for user in users:
i += 1
user, drive, jcount = _validateUserGetFileIDs(user, i, count, fileIdEntity)
if not csvPF:
_getMain().entityPerformActionNumItems([Ent.USER, user], jcount, Ent.DRIVE_FILE_SHORTCUT, i, count)
if jcount == 0:
continue
Ind.Increment()
j = 0
for fileId in fileIdEntity['list']:
j += 1
row = {'User': user, 'id': fileId}
try:
scresult = _getMain().callGAPI(drive.files(), 'get',
throwReasons=GAPI.DRIVE_GET_THROW_REASONS,
fileId=fileId, fields=scfields, supportsAllDrives=True)
row['name'] = scresult['name']
if scresult['mimeType'] != MIMETYPE_GA_SHORTCUT:
if not csvPF:
_getMain().entityActionFailedWarning([Ent.USER, user, _getMain()._getEntityMimeType(scresult), fileId],
Msg.INVALID_MIMETYPE.format(scresult['mimeType'], MIMETYPE_GA_SHORTCUT), j, jcount)
else:
row['code'] = SHORTCUT_CODE_NOT_A_SHORTCUT
csvPF.WriteRow(row)
continue
if 'owners' in scresult:
row['owner'] = scresult['owners'][0]['emailAddress']
row['parentId'] = scresult['parents'][0]
row[f'shortcutDetails{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}targetId'] = scresult['shortcutDetails']['targetId']
row[f'shortcutDetails{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}targetMimeType'] = scresult['shortcutDetails']['targetMimeType']
trfileId = scresult['shortcutDetails']['targetId']
try:
trresult = _getMain().callGAPI(drive.files(), 'get',
throwReasons=GAPI.DRIVE_GET_THROW_REASONS,
fileId=trfileId, fields=trfields, supportsAllDrives=True)
row['targetName'] = trresult['name']
row['targetId'] = trresult['id']
row['targetMimeType'] = trresult['mimeType']
entityList = [Ent.USER, user, Ent.DRIVE_FILE_SHORTCUT, f"{scresult['name']}({fileId})",
_getMain()._getEntityMimeType(trresult), f"{trresult['name']}({trfileId})"]
if scresult['shortcutDetails']['targetMimeType'] == trresult['mimeType']:
if not csvPF:
_getMain().entityActionPerformed(entityList, j, jcount)
else:
row['code'] = SHORTCUT_CODE_VALID
else:
if not csvPF:
_getMain().entityActionFailedWarning(entityList, Msg.MIMETYPE_MISMATCH.format(scresult['shortcutDetails']['targetMimeType'], trresult['mimeType']), j, jcount)
else:
row['code'] = SHORTCUT_CODE_MIMETYPE_MISMATCH
except GAPI.fileNotFound:
if not csvPF:
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.DRIVE_FILE_SHORTCUT, f"{scresult['name']}({fileId})",
_getMain()._getTargetEntityMimeType(scresult), trfileId], Msg.NOT_FOUND, j, jcount)
else:
row['code'] = SHORTCUT_CODE_TARGET_NOT_FOUND
except GAPI.fileNotFound:
if not csvPF:
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.DRIVE_FILE_SHORTCUT, fileId], Msg.NOT_FOUND, j, jcount)
else:
row['code'] = SHORTCUT_CODE_SHORTCUT_NOT_FOUND
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
break
if csvPF:
csvPF.WriteRow(row)
Ind.Decrement()
if csvPF:
csvPF.writeCSVfile('Check Shortcuts')
# gam <UserTypeEntity> update drivefile <DriveFileEntity> [copy] [returnidonly|returnlinkonly]
# [(localfile <FileName>|-)|(url <URL>)]
# [retainname | (newfilename <DriveFileName>) | (replacefilename <REMatchPattern> <RESubstitution>)*]
# [stripnameprefix <String>]
# [timestamp <Boolean>]] [timeformat <DateTimeFormat>]
# <DriveFileUpdateAttribute>*
# [(gsheet|csvsheet <SheetEntity> [clearfilter])|(addsheet <String>)]
# [charset <String>] [columndelimiter <Character>]
def updateDriveFile(users):
fileIdEntity = getDriveFileEntity()
body = {}
newName = None
assignLocalName = True
parameters = initDriveFileAttributes()
media_body = None
addSheetBody = addSheetEntity = None
updateSheetEntity = None
clearFilter = False
preserveModifiedTime = False
encoding = GC.Values[GC.CHARSET]
columnDelimiter = GC.Values[GC.CSV_INPUT_COLUMN_DELIMITER]
returnIdLink = None
operation = 'update'
while Cmd.ArgumentsRemaining():
myarg = _getMain().getArgument()
if myarg == 'copy':
operation = 'copy'
Act.Set(Act.COPY)
elif myarg == 'returnidonly':
returnIdLink = 'id'
elif myarg == 'returnlinkonly':
returnIdLink = 'webViewLink'
elif myarg == 'retainname':
assignLocalName = False
elif myarg == 'newfilename':
newName = _getMain().getString(Cmd.OB_DRIVE_FILE_NAME)
assignLocalName = False
elif getDriveFileAddRemoveParentAttribute(myarg, parameters):
pass
elif myarg == 'addsheet':
if updateSheetEntity:
_getMain().usageErrorExit(Msg.ARE_MUTUALLY_EXCLUSIVE.format(myarg, 'csvsheet'))
sheetTitle = _getMain().getString(Cmd.OB_STRING)
addSheetEntity = {'sheetType': Ent.SHEET, 'sheetValue': sheetTitle, 'sheetId': None, 'sheetTitle': sheetTitle}
addSheetBody = {'requests': [{'addSheet': {'properties': {'title': sheetTitle, 'sheetType': 'GRID'}}}]}
elif myarg in {'gsheet', 'csvsheet'}:
if addSheetEntity:
_getMain().usageErrorExit(Msg.ARE_MUTUALLY_EXCLUSIVE.format(myarg, 'addsheet'))
updateSheetEntity = _getMain().getSheetEntity(False)
elif myarg == 'clearfilter':
clearFilter = _getMain().getBoolean()
elif myarg == 'charset':
encoding = _getMain().getString(Cmd.OB_CHAR_SET)
elif myarg == 'columndelimiter':
columnDelimiter = _getMain().getCharacter()
else:
getDriveFileAttribute(myarg, body, parameters, True)
if assignLocalName and parameters[DFA_LOCALFILENAME] and parameters[DFA_LOCALFILENAME] != '-':
newName = parameters[DFA_LOCALFILENAME]
if newName:
if parameters[DFA_STRIPNAMEPREFIX] and newName.startswith(parameters[DFA_STRIPNAMEPREFIX]):
newName = newName[len(parameters[DFA_STRIPNAMEPREFIX]):]
if operation == 'update' and parameters[DFA_LOCALFILEPATH]:
if parameters[DFA_LOCALFILEPATH] != '-' and parameters[DFA_PRESERVE_FILE_TIMES]:
setPreservedFileTimes(body, parameters, True)
if not addSheetEntity and not updateSheetEntity:
media_body = getMediaBody(parameters)
elif operation == 'update' and parameters[DFA_URL]:
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 = _getMain().getEntityArgument(users)
for user in users:
i += 1
user, drive, jcount = _validateUserGetFileIDs(user, i, count, fileIdEntity,
entityType=Ent.DRIVE_FILE_OR_FOLDER if returnIdLink is None else None)
if jcount == 0:
continue
if not _getDriveFileParentInfo(drive, user, i, count, body, parameters, defaultToRoot=False):
continue
newParents = body.pop('parents', [])
if operation == 'update':
status, addParentsBase, removeParentsBase = _getDriveFileAddRemoveParentInfo(user, i, count, parameters, drive)
if not status:
continue
Ind.Increment()
j = 0
for fileId in fileIdEntity['list']:
j += 1
try:
addParents = addParentsBase[:]
removeParents = removeParentsBase[:]
if newParents or (not newName and parameters[DFA_REPLACEFILENAME]) or preserveModifiedTime:
result = _getMain().callGAPI(drive.files(), 'get',
throwReasons=GAPI.DRIVE_GET_THROW_REASONS,
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])
else:
body['name'] = newName
if parameters[DFA_TIMESTAMP]:
addTimestampToFilename(parameters, body)
if addSheetEntity or updateSheetEntity:
entityValueList = [Ent.USER, user, Ent.DRIVE_FILE_ID, fileId]
try:
result = _getMain().callGAPI(drive.files(), 'get',
throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.FILE_NOT_FOUND],
fileId=fileId, fields='id,mimeType,capabilities(canEdit)', supportsAllDrives=True)
if result['mimeType'] != MIMETYPE_GA_SPREADSHEET:
_getMain().entityActionNotPerformedWarning(entityValueList, f'{Msg.NOT_A} {Ent.Singular(Ent.SPREADSHEET)}', j, jcount)
continue
if not result['capabilities']['canEdit']:
_getMain().entityActionNotPerformedWarning(entityValueList, Msg.NOT_WRITABLE, j, jcount)
continue
_, sheet = _getMain().buildGAPIServiceObject(API.SHEETS, user)
if sheet is None:
continue
if addSheetEntity:
addresult = _getMain().callGAPI(sheet.spreadsheets(), 'batchUpdate',
throwReasons=GAPI.SHEETS_ACCESS_THROW_REASONS,
spreadsheetId=fileId, body=addSheetBody)
sheetEntity = addSheetEntity.copy()
sheetEntity['sheetId'] = addresult['replies'][0]['addSheet']['properties']['sheetId']
entityValueList.extend([sheetEntity['sheetType'], sheetEntity['sheetTitle']])
sheetEntity['sheetType'] = Ent.SHEET_ID # Temporarily set addsheet type to ID
else:
sheetEntity = updateSheetEntity.copy()
entityValueList.extend([sheetEntity['sheetType'], sheetEntity['sheetValue']])
spreadsheet = _getMain().callGAPI(sheet.spreadsheets(), 'get',
throwReasons=GAPI.SHEETS_ACCESS_THROW_REASONS,
spreadsheetId=fileId,
fields='spreadsheetUrl,sheets(properties(sheetId,title),protectedRanges(range(sheetId),requestingUserCanEdit))')
sheetId = _getMain().getSheetIdFromSheetEntity(spreadsheet, sheetEntity)
if sheetId is None:
_getMain().entityActionNotPerformedWarning(entityValueList, Msg.NOT_FOUND, j, jcount)
continue
if _getMain().protectedSheetId(spreadsheet, sheetId):
_getMain().entityActionNotPerformedWarning(entityValueList, Msg.NOT_WRITABLE, j, jcount)
continue
if addSheetEntity: # Restore addsheet type
sheetEntity['sheetType'] = Ent.SHEET
result = _getMain().callGAPI(drive.files(), 'update',
throwReasons=GAPI.DRIVE_ACCESS_THROW_REASONS+[GAPI.BAD_REQUEST, GAPI.CANNOT_ADD_PARENT,
GAPI.INSUFFICIENT_PARENT_PERMISSIONS,
GAPI.CANNOT_MODIFY_VIEWERS_CAN_COPY_CONTENT,
GAPI.TEAMDRIVES_PARENT_LIMIT,
GAPI.TEAMDRIVES_FOLDER_MOVE_IN_NOT_SUPPORTED,
GAPI.TEAMDRIVES_SHARING_RESTRICTION_NOT_ALLOWED],
fileId=fileId,
ocrLanguage=parameters[DFA_OCRLANGUAGE],
keepRevisionForever=parameters[DFA_KEEP_REVISION_FOREVER],
useContentAsIndexableText=parameters[DFA_USE_CONTENT_AS_INDEXABLE_TEXT],
addParents=','.join(addParents), removeParents=','.join(removeParents),
body=body, fields='id,name,mimeType,webViewLink', supportsAllDrives=True)
### File size check??
sbody = {'requests': []}
if clearFilter and updateSheetEntity:
sbody['requests'].append({'clearBasicFilter': {'sheetId': sheetId}})
sbody['requests'].append({'updateCells': {'range': {'sheetId': sheetId}, 'fields': '*'}})
sbody['requests'].append({'pasteData': {'coordinate': {'sheetId': sheetId, 'rowIndex': '0', 'columnIndex': '0'},
'data': _getMain().readFile(parameters[DFA_LOCALFILEPATH], encoding=encoding),
'type': 'PASTE_NORMAL', 'delimiter': columnDelimiter}})
_getMain().callGAPI(sheet.spreadsheets(), 'batchUpdate',
throwReasons=GAPI.SHEETS_ACCESS_THROW_REASONS,
spreadsheetId=fileId, body=sbody)
if returnIdLink:
_getMain().writeStdout(f'{result[returnIdLink]}\n')
else:
_getMain().entityModifierNewValueActionPerformed([Ent.USER, user, Ent.DRIVE_FILE, result['name'], sheetEntity['sheetType'], sheetEntity['sheetValue']],
Act.MODIFIER_WITH_CONTENT_FROM, parameters[DFA_LOCALFILENAME], j, jcount)
except GAPI.fileNotFound as e:
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.DRIVE_FILE_ID, fileId], str(e), j, jcount)
except (GAPI.notFound, GAPI.forbidden, GAPI.permissionDenied,
GAPI.internalError, GAPI.insufficientFilePermissions, GAPI.badRequest,
GAPI.invalid, GAPI.invalidArgument, GAPI.failedPrecondition) as e:
_getMain().entityActionFailedWarning(entityValueList, str(e), j, jcount)
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
break
else:
result = _getMain().callGAPI(drive.files(), 'update',
throwReasons=GAPI.DRIVE_ACCESS_THROW_REASONS+[GAPI.BAD_REQUEST, GAPI.CANNOT_ADD_PARENT,
GAPI.INSUFFICIENT_PARENT_PERMISSIONS,
GAPI.FILE_NEVER_WRITABLE, GAPI.CANNOT_MODIFY_VIEWERS_CAN_COPY_CONTENT,
GAPI.SHARE_OUT_NOT_PERMITTED, GAPI.SHARE_OUT_NOT_PERMITTED_TO_USER,
GAPI.TEAMDRIVES_PARENT_LIMIT, GAPI.TEAMDRIVES_FOLDER_MOVE_IN_NOT_SUPPORTED,
GAPI.TEAMDRIVES_SHARING_RESTRICTION_NOT_ALLOWED,
GAPI.CROSS_DOMAIN_MOVE_RESTRICTION, GAPI.UPLOAD_TOO_LARGE,
GAPI.TEAMDRIVES_SHORTCUT_FILE_NOT_SUPPORTED,
GAPI.FILE_OWNER_NOT_MEMBER_OF_WRITER_DOMAIN,
GAPI.FILE_WRITER_TEAMDRIVE_MOVE_IN_DISABLED],
fileId=fileId,
ocrLanguage=parameters[DFA_OCRLANGUAGE],
keepRevisionForever=parameters[DFA_KEEP_REVISION_FOREVER],
useContentAsIndexableText=parameters[DFA_USE_CONTENT_AS_INDEXABLE_TEXT],
addParents=','.join(addParents), removeParents=','.join(removeParents),
media_body=media_body, body=body, fields='id,name,mimeType,webViewLink',
supportsAllDrives=True)
if result:
if returnIdLink:
_getMain().writeStdout(f'{result[returnIdLink]}\n')
elif media_body:
_getMain().entityModifierNewValueActionPerformed([Ent.USER, user, Ent.DRIVE_FILE, result['name']],
Act.MODIFIER_WITH_CONTENT_FROM, parameters[DFA_LOCALFILENAME] or parameters[DFA_URL], j, jcount)
else:
_getMain().entityActionPerformed([Ent.USER, user, _getMain()._getEntityMimeType(result), result['name']], j, jcount)
else:
if returnIdLink:
_getMain().writeStdout(f'{fileId}\n')
else:
_getMain().entityActionPerformed([Ent.USER, user, Ent.DRIVE_FILE_OR_FOLDER_ID, fileId], j, jcount)
except (GAPI.fileNotFound, GAPI.forbidden, GAPI.internalError, GAPI.insufficientFilePermissions, GAPI.insufficientParentPermissions,
GAPI.unknownError, GAPI.invalid, GAPI.badRequest, GAPI.cannotAddParent,
GAPI.fileNeverWritable, GAPI.cannotModifyViewersCanCopyContent,
GAPI.shareInNotPermitted, GAPI.shareOutNotPermitted, GAPI.shareOutNotPermittedToUser,
GAPI.teamDrivesParentLimit, GAPI.teamDrivesFolderMoveInNotSupported,
GAPI.teamDrivesSharingRestrictionNotAllowed, GAPI.crossDomainMoveRestriction,
GAPI.uploadTooLarge, GAPI.teamDrivesShortcutFileNotSupported,
GAPI.fileOwnerNotMemberOfWriterDomain, GAPI.fileWriterTeamDriveMoveInDisabled) as e:
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.DRIVE_FILE_OR_FOLDER_ID, fileId], str(e), j, jcount)
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
break
Ind.Decrement()
else:
if newName:
if parameters[DFA_REPLACEFILENAME]:
body['name'] = processFilenameReplacements(newName, parameters[DFA_REPLACEFILENAME])
else:
body['name'] = newName
if parameters[DFA_TIMESTAMP]:
addTimestampToFilename(parameters, body)
Ind.Increment()
j = 0
for fileId in fileIdEntity['list']:
j += 1
try:
result = _getMain().callGAPI(drive.files(), 'copy',
throwReasons=GAPI.DRIVE_COPY_THROW_REASONS+[GAPI.CANNOT_MODIFY_VIEWERS_CAN_COPY_CONTENT],
fileId=fileId,
ignoreDefaultVisibility=parameters[DFA_IGNORE_DEFAULT_VISIBILITY],
keepRevisionForever=parameters[DFA_KEEP_REVISION_FOREVER],
body=body, fields='id,name,webViewLink', supportsAllDrives=True)
if returnIdLink:
writeReturnIdLink(returnIdLink, parameters[DFA_LOCALMIMETYPE], result)
else:
_getMain().entityModifierNewValueItemValueListActionPerformed([Ent.USER, user, Ent.DRIVE_FILE, fileId],
Act.MODIFIER_TO, result['name'], [Ent.DRIVE_FILE_ID, result['id']], j, jcount)
except (GAPI.fileNotFound, GAPI.forbidden, GAPI.internalError, GAPI.insufficientFilePermissions, GAPI.unknownError,
GAPI.invalid, GAPI.cannotCopyFile, GAPI.badRequest, GAPI.responsePreparationFailure, GAPI.fileNeverWritable, GAPI.fieldNotWritable,
GAPI.teamDrivesSharingRestrictionNotAllowed, GAPI.rateLimitExceeded, GAPI.userRateLimitExceeded,
GAPI.cannotModifyViewersCanCopyContent) as e:
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.DRIVE_FILE, fileId], str(e), j, jcount)
except (GAPI.storageQuotaExceeded, GAPI.teamDriveFileLimitExceeded, GAPI.teamDriveHierarchyTooDeep,) as e:
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.DRIVE_FILE, fileId], str(e), j, jcount)
break
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
break
Ind.Decrement()
STAT_FOLDER_TOTAL = 0
STAT_FOLDER_COPIED_MOVED = 1
STAT_FOLDER_SHORTCUT_CREATED = 2
STAT_FOLDER_SHORTCUT_EXISTS = 3
STAT_FOLDER_MERGED = 4
STAT_FOLDER_DUPLICATE = 5
STAT_FOLDER_FAILED = 6
STAT_FOLDER_NOT_WRITABLE = 7
STAT_FOLDER_PERMISSIONS_FAILED = 8
STAT_FILE_TOTAL = 9
STAT_FILE_COPIED_MOVED = 10
STAT_FILE_SHORTCUT_CREATED = 11
STAT_FILE_SHORTCUT_EXISTS = 12
STAT_FILE_DUPLICATE = 13
STAT_FILE_FAILED = 14
STAT_FILE_NOT_COPYABLE_MOVABLE = 15
STAT_FILE_IN_SKIPIDS = 16
STAT_FILE_PERMISSIONS_FAILED = 17
STAT_FILE_PROTECTEDRANGES_FAILED = 18
STAT_USER_NOT_ORGANIZER = 19
STAT_LENGTH = 20
FOLDER_SUBTOTAL_STATS = [STAT_FOLDER_COPIED_MOVED, STAT_FOLDER_SHORTCUT_CREATED, STAT_FOLDER_SHORTCUT_EXISTS,
STAT_FOLDER_DUPLICATE, STAT_FOLDER_MERGED, STAT_FOLDER_FAILED, STAT_FOLDER_NOT_WRITABLE]
FILE_SUBTOTAL_STATS = [STAT_FILE_COPIED_MOVED, STAT_FILE_SHORTCUT_CREATED, STAT_FILE_SHORTCUT_EXISTS,
STAT_FILE_DUPLICATE, STAT_FILE_FAILED, STAT_FILE_NOT_COPYABLE_MOVABLE, STAT_FILE_IN_SKIPIDS]
def _initStatistics():
return [0] * STAT_LENGTH
def _incrStatistic(statistics, stat):
statistics[stat] += 1
if stat in FOLDER_SUBTOTAL_STATS:
statistics[STAT_FOLDER_TOTAL] += 1
elif stat in FILE_SUBTOTAL_STATS:
statistics[STAT_FILE_TOTAL] += 1

View File

@@ -0,0 +1,836 @@
"""File tree building, permission matching, file entity selection.
Part of the drive sub-package, extracted from drive.py."""
"""GAM Google Drive file, permission, shared drive, and label management."""
import re
import sys
from gamlib import glaction
from gamlib import glapi as API
from gamlib import glcfg as GC
from gamlib import glclargs
from gamlib import glentity
from gamlib import glgapi as GAPI
from gamlib import glglobals as GM
from gamlib import glindent
from gamlib import glmsgs as Msg
Act = glaction.GamAction()
Ent = glentity.GamEntity()
Ind = glindent.GamIndent()
Cmd = glclargs.GamCLArgs()
APPLICATION_VND_GOOGLE_APPS = 'application/vnd.google-apps.'
MIMETYPE_GA_DOCUMENT = f'{APPLICATION_VND_GOOGLE_APPS}document'
MIMETYPE_GA_DRAWING = f'{APPLICATION_VND_GOOGLE_APPS}drawing'
MIMETYPE_GA_FILE = f'{APPLICATION_VND_GOOGLE_APPS}file'
MIMETYPE_GA_FOLDER = f'{APPLICATION_VND_GOOGLE_APPS}folder'
MIMETYPE_GA_FORM = f'{APPLICATION_VND_GOOGLE_APPS}form'
MIMETYPE_GA_FUSIONTABLE = f'{APPLICATION_VND_GOOGLE_APPS}fusiontable'
MIMETYPE_GA_JAM = f'{APPLICATION_VND_GOOGLE_APPS}jam'
MIMETYPE_GA_MAP = f'{APPLICATION_VND_GOOGLE_APPS}map'
MIMETYPE_GA_PRESENTATION = f'{APPLICATION_VND_GOOGLE_APPS}presentation'
MIMETYPE_GA_SCRIPT = f'{APPLICATION_VND_GOOGLE_APPS}script'
MIMETYPE_GA_SCRIPT_JSON = f'{APPLICATION_VND_GOOGLE_APPS}script+json'
MIMETYPE_GA_SHORTCUT = f'{APPLICATION_VND_GOOGLE_APPS}shortcut'
MIMETYPE_GA_3P_SHORTCUT = f'{APPLICATION_VND_GOOGLE_APPS}drive-sdk'
MIMETYPE_GA_SITE = f'{APPLICATION_VND_GOOGLE_APPS}site'
MIMETYPE_GA_SPREADSHEET = f'{APPLICATION_VND_GOOGLE_APPS}spreadsheet'
ME_IN_OWNERS = "'me' in owners"
ME_IN_OWNERS_AND = ME_IN_OWNERS + " and "
NOT_ME_IN_OWNERS = "not " + ME_IN_OWNERS
NOT_ME_IN_OWNERS_AND = NOT_ME_IN_OWNERS + " and "
WITH_ANY_FILE_NAME = "name = '{0}'"
WITH_MY_FILE_NAME = ME_IN_OWNERS_AND + WITH_ANY_FILE_NAME
WITH_OTHER_FILE_NAME = NOT_ME_IN_OWNERS_AND + WITH_ANY_FILE_NAME
ROOT = 'root'
ORPHANS = 'Orphans'
SHARED_WITHME = 'SharedWithMe'
SHARED_DRIVES = 'SharedDrives'
def _getMain():
return sys.modules['gam']
from gam.cmd.drive.core import (
MimeTypeCheck,
DRIVE_BY_NAME_CHOICE_MAP,
LOCATION_ALL_DRIVES,
LOCATION_CHOICE_MAP,
LOCATION_ONLY_SHARED_DRIVES,
_getSharedDriveNameFromId,
_mapDrive2QueryToDrive3,
cleanFileIDsList,
escapeDriveFileName,
getEscapedDriveFileName,
initDriveFileEntity,
)
def __getattr__(name):
"""Fall back to gam module for any undefined names."""
main = _getMain()
try:
return getattr(main, name)
except AttributeError:
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
def initFileTree(drive, shareddrive, DLP, shareddriveFields, showParent, user, i, count):
fileTree = {
ORPHANS: {'info': {'id': ORPHANS, 'name': ORPHANS, 'mimeType': MIMETYPE_GA_FOLDER, 'ownedByMe': True, 'parents': []},
'noParents': True, 'children': []},
SHARED_WITHME: {'info': {'id': SHARED_WITHME, 'name': SHARED_WITHME, 'mimeType': MIMETYPE_GA_FOLDER, 'ownedByMe': False, 'parents': []},
'noParents': True, 'children': []},
SHARED_DRIVES: {'info': {'id': SHARED_DRIVES, 'name': SHARED_DRIVES, 'mimeType': MIMETYPE_GA_FOLDER, 'ownedByMe': False, 'parents': [], 'driveId': SHARED_DRIVES},
'noParents': True, 'children': []},
}
try:
if not shareddrive:
fileId = ROOT
f_file = _getMain().callGAPI(drive.files(), 'get',
throwReasons=GAPI.DRIVE_USER_THROW_REASONS,
fileId=fileId, fields=FILEPATH_FIELDS)
f_file['parents'] = []
fileTree[f_file['id']] = {'info': f_file, 'noParents': True, 'children': []}
elif 'driveId' in shareddrive:
fileId = shareddrive['driveId']
f_file = _getMain().callGAPI(drive.files(), 'get',
throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.NOT_FOUND, GAPI.FILE_NOT_FOUND],
fileId=fileId, supportsAllDrives=True, fields=FILEPATH_FIELDS)
f_file['parents'] = []
fileTree[f_file['id']] = {'info': f_file, 'noParents': True, 'children': []}
name = _getMain().callGAPI(drive.drives(), 'get',
throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.FILE_NOT_FOUND, GAPI.NOT_FOUND],
driveId=fileId, fields='name')['name']
fileTree[f_file['id']]['info']['name'] = f'{SHARED_DRIVES}/{name}'
else:
fileId = None
if DLP and (DLP.getSharedDriveNames or DLP.checkLocation in {LOCATION_ALL_DRIVES, LOCATION_ONLY_SHARED_DRIVES}):
tdrives = _getMain().callGAPIpages(drive.drives(), 'list', 'drives',
throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.INVALID, GAPI.NO_LIST_TEAMDRIVES_ADMINISTRATOR_PRIVILEGE],
fields='nextPageToken,drives(id,name)', pageSize=100)
for tdrive in tdrives:
fileId = tdrive['id']
if fileId not in fileTree:
fileTree[fileId] = {'info': {'id': fileId, 'name': f'{SHARED_DRIVES}/{tdrive["name"]}', 'mimeType': MIMETYPE_GA_FOLDER},
'noParents': True, 'children': []}
for field in shareddriveFields:
if field in tdrive:
fileTree[fileId]['info'][field] = tdrive[field]
if showParent:
fileTree[fileId]['info']['driveId'] = fileId
fileTree[SHARED_DRIVES]['children'].append(fileId)
except (GAPI.notFound, GAPI.fileNotFound, GAPI.invalid, GAPI.noListTeamDrivesAdministratorPrivilege) as e:
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.SHAREDDRIVE, fileId], str(e), i, count)
return (None, False)
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
return (None, False)
return (fileTree, True)
def extendFileTree(fileTree, feed, DLP, stripCRsFromName):
for f_file in feed:
if DLP and (not DLP.CheckOnlySharedDrives(f_file) or not DLP.CheckExcludeTrashed(f_file)):
continue
if stripCRsFromName:
f_file['name'] = _stripControlCharsFromName(f_file['name'])
fileId = f_file['id']
if not f_file.get('parents', []):
if not f_file.get('driveId'):
if f_file['mimeType'] == MIMETYPE_GA_FOLDER and f_file['name'] == _getMain().MY_DRIVE:
f_file['parents'] = []
else:
f_file['parents'] = [ORPHANS] if f_file.get('ownedByMe', False) and 'sharedWithMeTime' not in f_file else [SHARED_WITHME]
else:
f_file['parents'] = [SHARED_DRIVES] if 'sharedWithMeTime' not in f_file else [SHARED_WITHME]
if fileId not in fileTree:
fileTree[fileId] = {'info': f_file, 'children': []}
else:
fileTree[fileId]['info'] = f_file
for parentId in f_file['parents']:
if parentId not in fileTree:
fileTree[parentId] = {'info': {'id': parentId, 'name': parentId, 'mimeType': MIMETYPE_GA_FOLDER}, 'children': []}
fileTree[parentId]['children'].append(fileId)
def extendFileTreeParents(drive, fileTree, fields):
def _followParent(fileId):
try:
result = _getMain().callGAPI(drive.files(), 'get',
throwReasons=GAPI.DRIVE_GET_THROW_REASONS,
fileId=fileId, fields=fields, supportsAllDrives=True)
if not result.get('parents', []):
if not result.get('driveId'):
result['parents'] = [ORPHANS] if result.get('ownedByMe', False) and 'sharedWithMeTime' not in result else [SHARED_WITHME]
else:
if result['name'] == _getMain().TEAM_DRIVE:
result['name'] = _getSharedDriveNameFromId(drive, result['driveId'])
result['parents'] = [SHARED_DRIVES] if 'sharedWithMeTime' not in result else [SHARED_WITHME]
fileTree[fileId]['info'] = result
fileTree[fileId]['info']['noDisplay'] = True
for parentId in result['parents']:
if parentId not in fileTree:
fileTree[parentId] = {'info': {'id': parentId, 'name': parentId, 'mimeType': MIMETYPE_GA_FOLDER}, 'children': []}
_followParent(parentId)
fileTree[parentId]['children'].append(fileId)
except (GAPI.fileNotFound, GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy):
fileTree[fileId] = {'info': {'id': fileId, 'name': fileId, 'mimeType': MIMETYPE_GA_FOLDER, 'parents': [], 'noDisplay': True},
'children': []}
for fileId in list(fileTree):
f_file = fileTree[fileId]
if 'parents' not in f_file['info'] and not f_file.get('noParents', False):
f_file['info']['noDisplay'] = True
_followParent(fileId)
def buildFileTree(feed, drive):
fileTree = {
ORPHANS: {'info': {'id': ORPHANS, 'name': ORPHANS, 'mimeType': MIMETYPE_GA_FOLDER, 'ownedByMe': True}, 'children': []},
}
try:
f_file = _getMain().callGAPI(drive.files(), 'get',
throwReasons=GAPI.DRIVE_USER_THROW_REASONS,
fileId=ROOT, fields=FILEPATH_FIELDS)
fileTree[f_file['id']] = {'info': f_file, 'children': []}
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy,
GAPI.notFound, GAPI.invalid, GAPI.noListTeamDrivesAdministratorPrivilege):
pass
for f_file in feed:
if 'parents' not in f_file:
f_file['parents'] = []
fileId = f_file['id']
if fileId not in fileTree:
fileTree[fileId] = {'info': f_file, 'children': []}
else:
fileTree[fileId]['info'] = f_file
parents = f_file['parents']
if not parents:
parents = [ORPHANS]
for parentId in parents:
if parentId not in fileTree:
fileTree[parentId] = {'info': {'id': parentId, 'name': parentId, 'mimeType': MIMETYPE_GA_FOLDER}, 'children': []}
fileTree[parentId]['children'].append(fileId)
return fileTree
def addFilePathsToRow(drive, fileTree, fileEntryInfo, filePathInfo, csvPF, row,
fullpath=False, showDepth=False, folderPathOnly=False, parentPathOnly=False):
_, paths, maxDepth = getFilePaths(drive, fileTree, fileEntryInfo, filePathInfo,
fullpath=fullpath, showDepth=showDepth, folderPathOnly=folderPathOnly, parentPathOnly=parentPathOnly)
kcount = len(paths)
if showDepth:
row['depth'] = maxDepth
row['paths'] = kcount
k = 0
for path in sorted(paths):
key = f'path{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}{k}'
csvPF.AddTitles(key)
if GC.Values[GC.CSV_OUTPUT_CONVERT_CR_NL] and (path.find('\n') >= 0 or path.find('\r') >= 0):
row[key] = _getMain().escapeCRsNLs(path)
else:
row[key] = path
k += 1
def addFilePathsToInfo(drive, fileTree, fileEntryInfo, filePathInfo, addParentsToTree=False, folderPathOnly=False, parentPathOnly=False):
_, paths, _ = getFilePaths(drive, fileTree, fileEntryInfo, filePathInfo, addParentsToTree=addParentsToTree,
showDepth=False, folderPathOnly=folderPathOnly, parentPathOnly=parentPathOnly)
fileEntryInfo['paths'] = []
for path in sorted(paths):
if GC.Values[GC.CSV_OUTPUT_CONVERT_CR_NL] and (path.find('\n') >= 0 or path.find('\r') >= 0):
fileEntryInfo['paths'].append(_getMain().escapeCRsNLs(path))
else:
fileEntryInfo['paths'].append(path)
def _validateACLOwnerType(location, body):
if body.get('role', '') == 'owner' and body['type'] != 'user':
Cmd.SetLocation(location)
_getMain().usageErrorExit(Msg.INVALID_PERMISSION_ATTRIBUTE_TYPE.format(f'role {body["role"]}', body['type']))
def _validateACLAttributes(myarg, location, body, field, validTypes):
if field in body and body['type'] not in validTypes:
Cmd.SetLocation(location-1)
_getMain().usageErrorExit(Msg.INVALID_PERMISSION_ATTRIBUTE_TYPE.format(myarg, body['type']))
def _validatePermissionOwnerType(location, body):
if 'role' in body:
badTypes = body['type']-{'user'}
if 'owner' in body['role'] and badTypes:
Cmd.SetLocation(location)
_getMain().usageErrorExit(Msg.INVALID_PERMISSION_ATTRIBUTE_TYPE.format('role owner', ','.join(badTypes)))
def _validatePermissionAttributes(myarg, location, body, field, validTypes):
if field in body:
badTypes = body['type']-validTypes
if badTypes:
Cmd.SetLocation(location-1)
_getMain().usageErrorExit(Msg.INVALID_PERMISSION_ATTRIBUTE_TYPE.format(myarg, ','.join(badTypes)))
DRIVEFILE_ACL_ROLES_MAP = {
'commenter': 'commenter',
'contentmanager': 'fileOrganizer',
'contributor': 'writer',
'editor': 'writer',
'fileorganizer': 'fileOrganizer',
'fileorganiser': 'fileOrganizer',
'manager': 'organizer',
'organizer': 'organizer',
'organiser': 'organizer',
'owner': 'owner',
'read': 'reader',
'reader': 'reader',
'viewer': 'reader',
'writer': 'writer',
}
DRIVEFILE_ACL_PERMISSION_TYPES = ['anyone', 'domain', 'group', 'user'] # anyone must be first element
DRIVEFILE_ACL_PERMISSION_DETAILS_TYPES = ['file', 'member']
class PermissionMatch():
_PERMISSION_MATCH_ACTION_MAP = {'process': True, 'skip': False}
_PERMISSION_MATCH_MODE_MAP = {'or': True, 'and': False}
def __init__(self):
self.permissionMatches = []
self.permissionMatchKeep = self.permissionMatchOr = True
self.permissionFields = set()
self.clearDefaultMatch = False
self.checkDetails = False
def GetMatch(self):
startEndTime = _getMain().StartEndTime('expirationstart', 'expirationend')
roleLocation = withLinkLocation = expirationStartLocation = expirationEndLocation = deletedLocation = None
requiredMatch = not _getMain().checkArgumentPresent('not')
body = {}
while Cmd.ArgumentsRemaining():
myarg = _getMain().getArgument()
if myarg in {'type', 'nottype'}:
body[myarg] = set()
body[myarg].add(_getMain().getChoice(DRIVEFILE_ACL_PERMISSION_TYPES))
self.permissionFields.add('type')
elif myarg in {'typelist', 'nottypelist'}:
arg = 'type' if myarg == 'typelist' else 'nottype'
body[arg] = set()
for ptype in _getMain().getString(Cmd.OB_PERMISSION_TYPE_LIST).lower().replace(',', ' ').split():
if ptype in DRIVEFILE_ACL_PERMISSION_TYPES:
body[arg].add(ptype)
else:
_getMain().invalidChoiceExit(ptype, DRIVEFILE_ACL_PERMISSION_TYPES, True)
self.permissionFields.add('type')
elif myarg in {'role', 'notrole'}:
roleLocation = Cmd.Location()
body[myarg] = set()
body[myarg].add(_getMain().getChoice(DRIVEFILE_ACL_ROLES_MAP, mapChoice=True))
self.permissionFields.add('role')
elif myarg in {'rolelist', 'notrolelist'}:
arg = 'role' if myarg == 'rolelist' else 'notrole'
body[arg] = set()
roleLocation = Cmd.Location()
for prole in _getMain().getString(Cmd.OB_PERMISSION_ROLE_LIST).lower().replace(',', ' ').split():
if prole in DRIVEFILE_ACL_ROLES_MAP:
body[arg].add(DRIVEFILE_ACL_ROLES_MAP[prole])
else:
_getMain().invalidChoiceExit(prole, DRIVEFILE_ACL_ROLES_MAP, True)
self.permissionFields.add('role')
elif myarg == 'emailaddress':
body['emailAddress'] = _getMain().getREPattern(re.IGNORECASE)
self.permissionFields.add('emailAddress')
elif myarg == 'emailaddresslist':
body[myarg] = set(_getMain().getString(Cmd.OB_EMAIL_ADDRESS_LIST).replace(',', ' ').lower().split())
self.permissionFields.add('emailAddress')
elif myarg == 'permissionidlist':
body[myarg] = set(_getMain().getString(Cmd.OB_PERMISSION_ID_LIST).replace(',', ' ').split())
self.permissionFields.add('id')
elif myarg in {'domain', 'notdomain'}:
body[myarg] = _getMain().getREPattern(re.IGNORECASE)
self.permissionFields.add('domain')
self.permissionFields.add('emailAddress')
elif myarg in {'domainlist', 'notdomainlist'}:
body[myarg] = set(_getMain().getString(Cmd.OB_DOMAIN_NAME_LIST).replace(',', ' ').lower().split())
self.permissionFields.add('domain')
self.permissionFields.add('emailAddress')
elif myarg == 'withlink':
withLinkLocation = Cmd.Location()
body['allowFileDiscovery'] = not _getMain().getBoolean()
self.permissionFields.add('allowFileDiscovery')
elif myarg in {'allowfilediscovery', 'discoverable'}:
withLinkLocation = Cmd.Location()
body['allowFileDiscovery'] = _getMain().getBoolean()
self.permissionFields.add('allowFileDiscovery')
elif myarg in {'name', 'displayname'}:
body['displayName'] = _getMain().getREPattern(re.IGNORECASE)
self.permissionFields.add('displayName')
elif myarg == 'expirationstart':
expirationStartLocation = Cmd.Location()
startEndTime.Get(myarg)
body[myarg] = startEndTime.startDateTime
self.permissionFields.add('expirationTime')
elif myarg == 'expirationend':
expirationEndLocation = Cmd.Location()
startEndTime.Get(myarg)
body[myarg] = startEndTime.endDateTime
self.permissionFields.add('expirationTime')
elif myarg == 'deleted':
deletedLocation = Cmd.Location()
body[myarg] = _getMain().getBoolean()
self.permissionFields.add('deleted')
elif myarg == 'inherited':
body[myarg] = _getMain().getBoolean()
self.permissionFields.add('permissionDetails')
self.checkDetails = True
elif myarg == 'permtype':
body['permissionType'] = _getMain().getChoice(DRIVEFILE_ACL_PERMISSION_DETAILS_TYPES)
self.permissionFields.add('permissionDetails')
self.checkDetails = True
elif myarg in {'em', 'endmatch'}:
break
else:
_getMain().unknownArgumentExit()
if self.clearDefaultMatch:
self.permissionMatches = []
self.clearDefaultMatch = False
if body:
if 'type' in body:
_validatePermissionOwnerType(roleLocation, body)
_validatePermissionAttributes('allowfilediscovery/withlink', withLinkLocation, body, 'allowFileDiscovery', {'anyone', 'domain'})
_validatePermissionAttributes('expirationstart', expirationStartLocation, body, 'expirationstart', {'user', 'group'})
_validatePermissionAttributes('expirationend', expirationEndLocation, body, 'expirationend', {'user', 'group'})
_validatePermissionAttributes('deleted', deletedLocation, body, 'deleted', {'user', 'group'})
self.permissionMatches.append((requiredMatch, body))
def SetDefaultMatch(self, requiredMatch, body):
self.clearDefaultMatch = True
self.permissionMatches.append((requiredMatch, body))
def ProcessArgument(self, myarg):
if myarg in {'pm', 'permissionmatch'}:
self.GetMatch()
elif myarg in {'pma', 'permissionmatchaction'}:
self.permissionMatchKeep = _getMain().getChoice(PermissionMatch._PERMISSION_MATCH_ACTION_MAP, mapChoice=True)
elif myarg in {'pmm', 'permissionmatchmode'}:
self.permissionMatchOr = _getMain().getChoice(PermissionMatch._PERMISSION_MATCH_MODE_MAP, mapChoice=True)
else:
return False
return True
@staticmethod
def CheckPermissionMatch(permission, permissionMatch):
match = False
for field, value in permissionMatch[1].items():
if field in {'type', 'role'}:
if permission.get(field, '') not in value:
break
elif field == 'nottype':
if permission.get('type', '') in value:
break
elif field == 'notrole':
if permission.get('role', '') in value:
break
elif field in {'allowFileDiscovery', 'deleted'}:
if value != permission.get(field, False):
break
elif field in {'inherited', 'permissionType'}:
permDetails = permission.pop('permissionDetails', None)
if permDetails:
dfltValue = False if field == 'inherited' else ''
permission['permissionDetails'] = []
if permission.get('inheritedPermissionsDisabled', False):
obreak = False
for permissionDetail in permDetails:
if permissionDetail.get('inherited', False):
continue
permission['permissionDetails'].append(permissionDetail)
if value != permissionDetail.get(field, dfltValue):
obreak = True
if obreak:
break
else:
permission['permissionDetails'].append(permDetails[len(permDetails)-1])
if value != permDetails[0].get(field, dfltValue):
break
else:
break
elif field in {'expirationstart', 'expirationend'}:
if 'expirationTime' in permission:
expirationDateTime = arrow.get(permission['expirationTime'])
if field == 'expirationstart':
if expirationDateTime < value:
break
else:
if expirationDateTime > value:
break
else:
break
elif field == 'emailaddresslist':
emailAddress = permission.get('emailAddress')
if emailAddress:
if emailAddress.lower() not in value:
break
else:
break
elif field == 'permissionidlist':
permissionId = permission.get('id')
if permissionId:
if permissionId not in value:
break
else:
break
elif field not in {'domain', 'notdomain', 'domainlist', 'notdomainlist'}:
if not value.match(permission.get(field, '')):
break
else:
if 'domain' in permission:
domain = permission['domain'].lower()
elif 'emailAddress' in permission and permission['emailAddress']:
_, domain = _getMain().splitEmailAddress(permission['emailAddress'].lower())
else:
break
if ((field == 'domain' and not value.match(domain)) or
(field == 'notdomain' and value.match(domain)) or
(field == 'domainlist' and domain not in value) or
(field == 'notdomainlist' and domain in value)):
break
else:
match = True
return match == permissionMatch[0]
def GetMatchingPermissions(self, permissions):
if not self.permissionMatches:
return permissions
matchingPermissions = []
for permission in permissions:
requiredMatches = 1 if self.permissionMatchOr else len(self.permissionMatches)
for permissionMatch in self.permissionMatches:
if self.CheckPermissionMatch(permission, permissionMatch):
requiredMatches -= 1
if requiredMatches == 0:
if self.permissionMatchKeep:
matchingPermissions.append(permission)
break
else:
if not self.permissionMatchKeep:
matchingPermissions.append(permission)
return matchingPermissions
def CheckPermissionMatches(self, permissions):
if not self.permissionMatches:
return True
requiredMatches = 1 if self.permissionMatchOr else len(self.permissionMatches)
for permission in permissions:
for permissionMatch in self.permissionMatches:
if self.CheckPermissionMatch(permission, permissionMatch):
requiredMatches -= 1
if requiredMatches == 0:
return self.permissionMatchKeep
return not self.permissionMatchKeep
def noFileSelectFileIdEntity(fileIdEntity):
return (not fileIdEntity
or (not fileIdEntity['dict']
and not fileIdEntity['query']
and not fileIdEntity['shareddrivefilequery']
and not fileIdEntity['list']))
SHOW_OWNED_BY_CHOICE_MAP = {'any': None, 'me': True, 'others': False}
class DriveListParameters():
def __init__(self, myargOptions):
self.PM = PermissionMatch()
self.myargOptions = myargOptions
self.checkLocation = None
self.excludeTrashed = False
self.filenameMatchPattern = None
self.getSharedDriveNames = False
self.kwargs = {}
self.fileIdEntity = {}
self.locationFileIds = []
self.locationSet = False
self.maxItems = 0
self.mimeTypeCheck = MimeTypeCheck()
self.maximumFileSize = None
self.minimumFileSize = None
self.onlySharedDrives = False
self.queryTimes = {}
self.showOwnedBy = True
self.showSharedByMe = None
SHOW_SHARED_BY_ME_CHOICE_MAP = {'any': None, 'true': True, 'false': False}
def ProcessArgument(self, myarg, fileIdEntity):
if myarg == 'maxfiles':
self.maxItems = _getMain().getInteger(minVal=0)
elif myarg == 'maximumfilesize':
self.maximumFileSize = _getMain().getInteger(minVal=0)
elif myarg == 'minimumfilesize':
self.minimumFileSize = _getMain().getInteger(minVal=0)
elif myarg == 'showsharedbyme':
self.showSharedByMe = _getMain().getChoice(self.SHOW_SHARED_BY_ME_CHOICE_MAP, mapChoice=True)
elif myarg == 'filenamematchpattern':
self.filenameMatchPattern = _getMain().getREPattern(re.IGNORECASE)
elif self.PM.ProcessArgument(myarg):
pass
elif myarg == 'anyowner':
self.showOwnedBy = None
self.UpdateAnyOwnerQuery()
elif myarg == 'showownedby':
self.showOwnedBy = _getMain().getChoice(SHOW_OWNED_BY_CHOICE_MAP, mapChoice=True)
self.UpdateQueryWithShowOwnedBy()
elif myarg == 'showmimetype':
self.mimeTypeCheck.Get()
if self.myargOptions['mimeTypeInQuery']:
self.AppendToQuery(self.mimeTypeCheck.AddMimeTypeToQuery(self.fileIdEntity.get('query', '')))
elif myarg == 'excludetrashed':
self.excludeTrashed = True
elif myarg.startswith('querytime'):
self.queryTimes[myarg] = _getMain().getTimeOrDeltaFromNow()
elif noFileSelectFileIdEntity(fileIdEntity):
if self.myargOptions['allowQuery'] and myarg == 'query':
self.AppendToQuery(_getMain().getString(Cmd.OB_QUERY))
elif self.myargOptions['allowQuery'] and myarg.startswith('query:'):
self.AppendToQuery(Cmd.Previous().strip()[6:])
elif self.myargOptions['allowQuery'] and myarg == 'fullquery':
self.SetQuery(_getMain().getString(Cmd.OB_QUERY, minLen=0))
elif self.myargOptions['allowQuery'] and myarg in _getMain().QUERY_SHORTCUTS_MAP:
self.UpdateAnyOwnerQuery()
self.AppendToQuery(_getMain().QUERY_SHORTCUTS_MAP[myarg])
elif self.myargOptions['allowChoose'] and myarg == 'choose':
myarg = _getMain().checkGetArgument()
if myarg in DRIVE_BY_NAME_CHOICE_MAP:
self.SetQuery(DRIVE_BY_NAME_CHOICE_MAP[myarg].format(getEscapedDriveFileName()))
elif myarg in LOCATION_CHOICE_MAP:
self.locationSet = True
self.SetLocation(LOCATION_CHOICE_MAP[myarg])
elif myarg.find(':') > 0:
kw, value = myarg.split(':', 1)
if kw in DRIVE_BY_NAME_CHOICE_MAP:
self.SetQuery(DRIVE_BY_NAME_CHOICE_MAP[kw].format(escapeDriveFileName(value)))
else:
_getMain().invalidChoiceExit(myarg, list(DRIVE_BY_NAME_CHOICE_MAP), True)
else:
_getMain().invalidChoiceExit(myarg, list(DRIVE_BY_NAME_CHOICE_MAP)+list(LOCATION_CHOICE_MAP), True)
elif self.myargOptions['allowCorpora'] and myarg == 'corpora':
corpora = _getMain().getChoice(CORPORA_CHOICE_MAP)
self.kwargs['corpora'] = CORPORA_CHOICE_MAP[corpora]
self.kwargs['includeItemsFromAllDrives'] = self.kwargs['supportsAllDrives'] = True
self.onlySharedDrives = corpora in {'onlyshareddrives', 'onlyteamdrives'}
self.getSharedDriveNames = True
self.UpdateAnyOwnerQuery()
self.SetLocationFileIDsList(LOCATION_CHOICE_MAP['alldrives' if not self.onlySharedDrives else 'onlyshareddrives'])
else:
return False
else:
if (myarg == 'query' or
myarg.startswith('query:') or
myarg == 'fullquery' or
myarg in _getMain().QUERY_SHORTCUTS_MAP or
myarg in DRIVE_BY_NAME_CHOICE_MAP):
_getMain().usageErrorExit(Msg.ARE_MUTUALLY_EXCLUSIVE.format('select', myarg))
return False
return True
def InitDriveIdEntity(self):
if not self.fileIdEntity:
self.fileIdEntity = initDriveFileEntity()
self.fileIdEntity['query'] = ME_IN_OWNERS
def AppendToQuery(self, query):
self.InitDriveIdEntity()
if self.fileIdEntity['query']:
self.fileIdEntity['query'] += ' and ('+query+')'
else:
self.fileIdEntity['query'] = query
def SetQuery(self, query):
self.InitDriveIdEntity()
self.fileIdEntity['query'] = query
def UpdateAnyOwnerQuery(self):
self.InitDriveIdEntity()
self.fileIdEntity['query'] = _updateAnyOwnerQuery(self.fileIdEntity['query'])
def UpdateQueryWithShowOwnedBy(self):
self.InitDriveIdEntity()
if self.showOwnedBy is None:
self.fileIdEntity['query'] = _updateAnyOwnerQuery(self.fileIdEntity['query'])
elif not self.showOwnedBy:
if self.fileIdEntity['query'].find(NOT_ME_IN_OWNERS) >= 0:
pass
else:
self.fileIdEntity['query'] = _stripMeInOwners(self.fileIdEntity['query'])
if self.fileIdEntity['query']:
self.fileIdEntity['query'] = NOT_ME_IN_OWNERS_AND+self.fileIdEntity['query']
else:
self.fileIdEntity['query'] = NOT_ME_IN_OWNERS
else:
if self.fileIdEntity['query'].find(ME_IN_OWNERS) >= 0:
pass
else:
self.fileIdEntity['query'] = _stripNotMeInOwners(self.fileIdEntity['query'])
if self.fileIdEntity['query']:
self.fileIdEntity['query'] = ME_IN_OWNERS_AND+self.fileIdEntity['query']
else:
self.fileIdEntity['query'] = ME_IN_OWNERS
def CheckShowOwnedBy(self, fileInfo):
return self.showOwnedBy is None or fileInfo.get('ownedByMe', self.showOwnedBy) == self.showOwnedBy
def CheckShowSharedByMe(self, fileInfo):
return self.showSharedByMe is None or (fileInfo.get('shared', self.showSharedByMe) == self.showSharedByMe and fileInfo.get('ownedByMe', False))
def SetLocationFileIDsList(self, location):
self.locationFileIds = location['fileids']
self.checkLocation = location['location']
self.InitDriveIdEntity()
cleanFileIDsList(self.fileIdEntity, self.locationFileIds)
def SetLocation(self, location):
self.SetLocationFileIDsList(location)
if self.checkLocation in {LOCATION_ALL_DRIVES, LOCATION_ONLY_SHARED_DRIVES}:
self.kwargs = {'corpora': 'allDrives', 'includeItemsFromAllDrives': True, 'supportsAllDrives': True}
self.showOwnedBy = None
self.fileIdEntity['query'] = ''
self.onlySharedDrives = self.checkLocation == LOCATION_ONLY_SHARED_DRIVES
elif location['setShowOwnedBy']:
self.showOwnedBy = location['owner']
if self.showOwnedBy is None:
self.fileIdEntity['query'] = ''
elif self.showOwnedBy:
self.fileIdEntity['query'] = ME_IN_OWNERS
else:
self.fileIdEntity['query'] = NOT_ME_IN_OWNERS
else:
if self.fileIdEntity['query'].find(NOT_ME_IN_OWNERS) >= 0:
self.fileIdEntity['query'] = NOT_ME_IN_OWNERS
elif self.fileIdEntity['query'].find(ME_IN_OWNERS) >= 0:
self.fileIdEntity['query'] = ME_IN_OWNERS
def SetShowOwnedBy(self, showOwnedBy):
self.showOwnedBy = showOwnedBy
def GetFileIdEntity(self):
if not self.fileIdEntity:
self.fileIdEntity = initDriveFileEntity()
self.fileIdEntity['query'] = ME_IN_OWNERS
return self.fileIdEntity
def AddMimeTypeToQuery(self):
if not self.fileIdEntity:
self.fileIdEntity = initDriveFileEntity()
if self.mimeTypeCheck.mimeTypes or self.mimeTypeCheck.category:
self.fileIdEntity['query'] = self.mimeTypeCheck.AddMimeTypeToQuery(self.fileIdEntity['query'])
def Finalize(self, fileIdEntity):
self.fileIdEntity.setdefault('query', '')
if self.excludeTrashed:
self.AppendToQuery('trashed=false')
if self.fileIdEntity['query']:
for queryTimeName, queryTimeValue in self.queryTimes.items():
self.fileIdEntity['query'] = self.fileIdEntity['query'].replace(f'#{queryTimeName}#', queryTimeValue)
self.fileIdEntity['query'] = _mapDrive2QueryToDrive3(self.fileIdEntity['query'])
if not fileIdEntity.get('shareddrive'):
if self.fileIdEntity['query']:
if self.fileIdEntity['query'].find(NOT_ME_IN_OWNERS) >= 0 or (not self.showOwnedBy and self.showOwnedBy is not None):
if not self.locationFileIds:
self.SetLocationFileIDsList(LOCATION_CHOICE_MAP['ownedbyothers'])
self.SetShowOwnedBy(False)
elif self.fileIdEntity['query'].find(ME_IN_OWNERS) >= 0 or self.showOwnedBy:
if not self.locationFileIds:
self.SetLocationFileIDsList(LOCATION_CHOICE_MAP['ownedbyme'])
self.SetShowOwnedBy(True)
else:
if not self.locationFileIds:
self.SetLocationFileIDsList(LOCATION_CHOICE_MAP['ownedbyany'])
self.SetShowOwnedBy(None)
else:
if not self.locationFileIds:
self.SetLocationFileIDsList(LOCATION_CHOICE_MAP['ownedbyany'])
self.SetShowOwnedBy(None)
else:
self.UpdateAnyOwnerQuery()
# if not self.locationFileIds:
# self.SetLocationFileIDsList(LOCATION_CHOICE_MAP['onlyshareddrives'])
self.SetShowOwnedBy(None)
def GetLocationFileIdsFromTree(self, fileTree, fileIdEntity):
cleanList = []
for fileId in self.locationFileIds:
if fileId == ROOT or (fileId in fileTree and fileTree[fileId]['children']):
cleanList.append(fileId)
cleanFileIDsList(fileIdEntity, cleanList)
def CheckExcludeTrashed(self, fileInfo):
return not self.excludeTrashed or not fileInfo.get('trashed', False)
def CheckFilenameMatch(self, fileInfo):
return not self.filenameMatchPattern or self.filenameMatchPattern.match(fileInfo['name'])
def CheckMimeType(self, fileInfo):
return self.mimeTypeCheck.Check(fileInfo['mimeType'])
def CheckFileSize(self, fileInfo, sizeField):
size = int(fileInfo.get(sizeField, '0'))
if self.minimumFileSize is not None and size < self.minimumFileSize:
return False
if self.maximumFileSize is not None and size > self.maximumFileSize:
return False
return True
def CheckOnlySharedDrives(self, fileInfo):
return not self.onlySharedDrives or fileInfo.get('driveId') is not None
def CheckFilePermissionMatches(self, fileInfo):
return self.PM.CheckPermissionMatches(fileInfo.get('permissions', []))
def GetFileMatchingPermission(self, fileInfo):
return self.PM.GetMatchingPermissions(fileInfo.get('permissions', []))
def _getGettingEntity(user, fileIdEntity):
driveId = fileIdEntity.get('shareddrive', {}).get('driveId', None)
if not driveId:
return user
return f"{user} on {Ent.Singular(Ent.SHAREDDRIVE_ID)}: {driveId}"
OWNED_BY_ME_FIELDS_TITLES = ['ownedByMe']
FILELIST_FIELDS_TITLES = ['id', 'name', 'mimeType', 'parents']
DRIVE_INDEXED_TITLES = ['parents', 'path', 'permissions']
CHECK_LOCATION_FIELDS_TITLES = ['driveId', 'id', 'mimeType', 'ownedByMe', 'parents', 'sharedWithMeTime', 'shared']
FILECOUNT_SUMMARY_NONE = 0
FILECOUNT_SUMMARY_ONLY = -1
FILECOUNT_SUMMARY_PLUS = 1
FILECOUNT_SUMMARY_CHOICE_MAP = {
'none': FILECOUNT_SUMMARY_NONE,
'only': FILECOUNT_SUMMARY_ONLY,
'plus': FILECOUNT_SUMMARY_PLUS
}
FILECOUNT_SUMMARY_USER = 'Summary'
SIZE_FIELD_CHOICE_MAP = {
'size': 'size',
'quotabytesused': 'quotaBytesUsed'
}
# gam <UserTypeEntity> print filelist [todrive <ToDriveAttribute>*]
# [((query <QueryDriveFile>) | (fullquery <QueryDriveFile>) | <DriveFileQueryShortcut>) (querytime<String> <Time>)*]
# [continueoninvalidquery [<Boolean>]]
# [choose <DriveFileNameEntity>|<DriveFileEntityShortcut>]
# [corpora <CorporaAttribute>]
# [select <DriveFileEntity> [selectsubquery <QueryDriveFile>]
# [(norecursion [<Boolean>])|(depth <Number>)] [showparent]]
# [anyowner|(showownedby any|me|others)]
# [showmimetype [not] <MimeTypeList>] [showmimetype category <MimeTypeNameList>] [mimetypeinquery [<Boolean>]]
# [sizefield quotabytesused|size] [minimumfilesize <Integer>] [maximumfilesize <Integer>]
# [filenamematchpattern <REMatchPattern>]
# <PermissionMatch>* [<PermissionMatchMode>] [<PermissionMatchAction>] [pmfilter] [oneitemperrow]
# [excludetrashed]
# [maxfiles <Integer>] [nodataheaders <String>]
# [countsonly [summary none|only|plus] [summaryuser <String>]
# [showsource] [showsize] [showsizeunits] [showmimetypesize]]
# [countsrowfilter]
# [filepath|fullpath [folderpathonly|parentpathonly [<Boolean>]] [pathdelimiter <Character>] [addpathstojson] [showdepth]] [buildtree]
# [allfields|<DriveFieldName>*|(fields <DriveFieldNameList>)]
# [showdrivename] [showshareddrivepermissions]
# (showlabels details|ids)|(includelabels <DriveLabelIDList>)]
# [includepermissionsforview published]
# [showparentsidsaslist] [showpermissionslast]
# (orderby <DriveFileOrderByFieldName> [ascending|descending])* [delimiter <Character>]
# [stripcrsfromname]
# (addcsvdata <FieldName> <String>)* [includecsvdatainjson [<Boolean>]]
# [formatjson [quotechar <Character>]]

584
src/gam/cmd/drive/labels.py Normal file
View File

@@ -0,0 +1,584 @@
"""Drive labels and label permissions.
Part of the drive sub-package, extracted from drive.py."""
"""GAM Google Drive file, permission, shared drive, and label management."""
import re
import json
import sys
from gamlib import glaction
from gamlib import glapi as API
from gamlib import glcfg as GC
from gamlib import glclargs
from gamlib import glentity
from gamlib import glgapi as GAPI
from gamlib import glglobals as GM
from gamlib import glindent
from gamlib import glmsgs as Msg
Act = glaction.GamAction()
Ent = glentity.GamEntity()
Ind = glindent.GamIndent()
Cmd = glclargs.GamCLArgs()
APPLICATION_VND_GOOGLE_APPS = 'application/vnd.google-apps.'
MIMETYPE_GA_DOCUMENT = f'{APPLICATION_VND_GOOGLE_APPS}document'
MIMETYPE_GA_DRAWING = f'{APPLICATION_VND_GOOGLE_APPS}drawing'
MIMETYPE_GA_FILE = f'{APPLICATION_VND_GOOGLE_APPS}file'
MIMETYPE_GA_FOLDER = f'{APPLICATION_VND_GOOGLE_APPS}folder'
MIMETYPE_GA_FORM = f'{APPLICATION_VND_GOOGLE_APPS}form'
MIMETYPE_GA_FUSIONTABLE = f'{APPLICATION_VND_GOOGLE_APPS}fusiontable'
MIMETYPE_GA_JAM = f'{APPLICATION_VND_GOOGLE_APPS}jam'
MIMETYPE_GA_MAP = f'{APPLICATION_VND_GOOGLE_APPS}map'
MIMETYPE_GA_PRESENTATION = f'{APPLICATION_VND_GOOGLE_APPS}presentation'
MIMETYPE_GA_SCRIPT = f'{APPLICATION_VND_GOOGLE_APPS}script'
MIMETYPE_GA_SCRIPT_JSON = f'{APPLICATION_VND_GOOGLE_APPS}script+json'
MIMETYPE_GA_SHORTCUT = f'{APPLICATION_VND_GOOGLE_APPS}shortcut'
MIMETYPE_GA_3P_SHORTCUT = f'{APPLICATION_VND_GOOGLE_APPS}drive-sdk'
MIMETYPE_GA_SITE = f'{APPLICATION_VND_GOOGLE_APPS}site'
MIMETYPE_GA_SPREADSHEET = f'{APPLICATION_VND_GOOGLE_APPS}spreadsheet'
ME_IN_OWNERS = "'me' in owners"
ME_IN_OWNERS_AND = ME_IN_OWNERS + " and "
NOT_ME_IN_OWNERS = "not " + ME_IN_OWNERS
NOT_ME_IN_OWNERS_AND = NOT_ME_IN_OWNERS + " and "
WITH_ANY_FILE_NAME = "name = '{0}'"
WITH_MY_FILE_NAME = ME_IN_OWNERS_AND + WITH_ANY_FILE_NAME
WITH_OTHER_FILE_NAME = NOT_ME_IN_OWNERS_AND + WITH_ANY_FILE_NAME
ROOT = 'root'
ORPHANS = 'Orphans'
SHARED_WITHME = 'SharedWithMe'
SHARED_DRIVES = 'SharedDrives'
def _getMain():
return sys.modules['gam']
def __getattr__(name):
"""Fall back to gam module for any undefined names."""
main = _getMain()
try:
return getattr(main, name)
except AttributeError:
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
def _getDisplayDriveLabelsParameters(myarg, parameters):
if myarg in DRIVELABELS_PROJECTION_CHOICE_MAP:
parameters['view'] = DRIVELABELS_PROJECTION_CHOICE_MAP[myarg]
elif myarg == 'language':
parameters['languageCode'] = _getMain().getLanguageCode(_getMain().BCP47_LANGUAGE_CODES_MAP)
elif myarg in _getMain().ADMIN_ACCESS_OPTIONS:
parameters['useAdminAccess'] = True
elif myarg == 'publishedonly':
parameters['publishedOnly'] = _getMain().getBoolean()
elif myarg == 'minimumrole':
parameters['minimumRole'] = _getMain().getChoice(DRIVELABELS_PERMISSION_ROLE_MAP, mapChoice=True)
else:
return False
return True
def normalizeDriveLabelID(driveLabelID):
atLoc = driveLabelID.find('@')
if atLoc != -1:
driveLabelID = driveLabelID[:atLoc]
if driveLabelID.startswith('labels/'):
return driveLabelID[7:]
return driveLabelID
def normalizeDriveLabelName(driveLabelName):
if driveLabelName.startswith('labels/'):
return driveLabelName
return f'labels/{driveLabelName}'
def validateDriveLabelName(name, kvList, j, jcount, permName=False):
name = normalizeDriveLabelName(name)
# Label name
if not permName:
mg = re.match(r'^(labels/[^/]+)$', name)
if not mg:
_getMain().entityActionNotPerformedWarning(kvList, 'Expected labels/<String>', j, jcount)
return None
return name
# Label permission name
mg = re.match(r'^(labels/[^/]+)/permissions/(?:audiences|groups|people)/.+$', name)
if not mg:
_getMain().entityActionNotPerformedWarning(kvList, 'Expected labels/<String>/permissions/(audiences|groups|people)/<String>', j, jcount)
return (None, None)
return (name, mg.group(1))
def _showDriveLabel(label, j, jcount, FJQC):
if FJQC.formatJSON:
_getMain().printLine(json.dumps(_getMain().cleanJSON(label, timeObjects=DRIVELABELS_TIME_OBJECTS), ensure_ascii=False, sort_keys=True))
return
_getMain().printEntity([Ent.CLASSIFICATION_LABEL_NAME, f'{label["name"]}'], j, jcount)
Ind.Increment()
_getMain().showJSON(None, label, timeObjects=DRIVELABELS_TIME_OBJECTS, dictObjectsKey={'fields': 'id', 'choices': 'id'})
Ind.Decrement()
# gam [<UserTypeEntity>] info classificationlabels <ClassificationLabelNameEntity>
# [[basic|full] [languagecode <BCP47LanguageCode>]
# [formatjson] [asadmin]
def infoDriveLabels(users, useAdminAccess=False):
driveLabelNameEntity = _getMain().getUserObjectEntity(Cmd.OB_CLASSIFICATION_LABEL_NAME, Ent.CLASSIFICATION_LABEL, shlexSplit=True)
FJQC = _getMain().FormatJSONQuoteChar()
parameters = {'useAdminAccess': useAdminAccess}
while Cmd.ArgumentsRemaining():
myarg = _getMain().getArgument()
if _getDisplayDriveLabelsParameters(myarg, parameters):
pass
else:
FJQC.GetFormatJSON(myarg)
api = API.DRIVELABELS_ADMIN if parameters['useAdminAccess'] else API.DRIVELABELS_USER
i, count, users = _getMain().getEntityArgument(users)
for user in users:
i += 1
user, drive, labelNames, jcount = _getMain()._validateUserGetObjectList(user, i, count, driveLabelNameEntity,
api=api, showAction=not FJQC.formatJSON)
if jcount == 0:
continue
Ind.Increment()
j = 0
for name in labelNames:
j += 1
kvList = [Ent.USER, user, Ent.CLASSIFICATION_LABEL_NAME, name]
name = validateDriveLabelName(name, kvList, j, jcount, False)
if name is None:
continue
try:
label = _getMain().callGAPI(drive.labels(), 'get',
bailOnInternalError=True,
throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.NOT_FOUND, GAPI.PERMISSION_DENIED,
GAPI.INVALID_ARGUMENT, GAPI.INTERNAL_ERROR],
name=name, **parameters)
_showDriveLabel(label, j, jcount, FJQC)
except GAPI.notFound as e:
_getMain().entityActionFailedWarning(kvList, str(e), j, jcount)
except (GAPI.permissionDenied, GAPI.invalidArgument, GAPI.internalError) as e:
_getMain().entityActionFailedWarning(kvList, str(e), j, jcount)
break
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
break
Ind.Decrement()
def doInfoDriveLabels():
infoDriveLabels([_getMain()._getAdminEmail()], True)
# gam [<UserTypeEntity>] print classificationlabels> [todrive <ToDriveAttribute>*]
# [basic|full] [languagecode <BCP47LanguageCode>]
# [publishedonly [<Boolean>]] [minimumrole applier|editor|organizer|reader]
# [formatjson [quotechar <Character>]] [asadmin]
# gam [<UserTypeEntity>] show classificationlabels
# [basic|full] [languagecode <BCP47LanguageCode>]
# [publishedonly [<Boolean>]] [minimumrole applier|editor|organizer|reader]
# [formatjson] [asadmin]
def printShowDriveLabels(users, useAdminAccess=False):
csvPF = _getMain().CSVPrintFile(['User', 'name', 'description', 'id'], 'sortall') if Act.csvFormat() else None
FJQC = _getMain().FormatJSONQuoteChar(csvPF)
parameters = {'useAdminAccess': useAdminAccess}
while Cmd.ArgumentsRemaining():
myarg = _getMain().getArgument()
if csvPF and myarg == 'todrive':
csvPF.GetTodriveParameters()
elif _getDisplayDriveLabelsParameters(myarg, parameters):
pass
else:
FJQC.GetFormatJSONQuoteChar(myarg, True)
if csvPF and FJQC.formatJSON:
csvPF.SetJSONTitles(['User', 'name', 'JSON'])
api = API.DRIVELABELS_ADMIN if parameters['useAdminAccess'] else API.DRIVELABELS_USER
i, count, users = _getMain().getEntityArgument(users)
for user in users:
i += 1
user, drive = _getMain().buildGAPIServiceObject(api, user, i, count)
if not drive:
continue
if csvPF:
_getMain().printGettingAllEntityItemsForWhom(Ent.CLASSIFICATION_LABEL, user, i, count)
pageMessage = _getMain().getPageMessageForWhom()
else:
pageMessage = None
try:
labels = _getMain().callGAPIpages(drive.labels(), 'list', 'labels',
pageMessage=pageMessage,
throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.PERMISSION_DENIED],
**parameters, fields='nextPageToken,labels', pageSize=200)
if not csvPF:
jcount = len(labels)
if not FJQC.formatJSON:
_getMain().entityPerformActionNumItems([Ent.USER, user], jcount, Ent.CLASSIFICATION_LABEL, i, count)
Ind.Increment()
j = 0
for label in labels:
j += 1
_showDriveLabel(label, j, jcount, FJQC)
Ind.Decrement()
else:
for label in labels:
row = _getMain().flattenJSON(label, flattened={'User': user}, timeObjects=DRIVELABELS_TIME_OBJECTS)
if not FJQC.formatJSON:
csvPF.WriteRowTitles(row)
elif csvPF.CheckRowTitles(row):
row = {'User': user, 'name': label['name']}
row['JSON'] = json.dumps(_getMain().cleanJSON(label, timeObjects=DRIVELABELS_TIME_OBJECTS),
ensure_ascii=False, sort_keys=True)
csvPF.WriteRowNoFilter(row)
except GAPI.permissionDenied as e:
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.CLASSIFICATION_LABEL, None], str(e), i, count)
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
if csvPF:
csvPF.writeCSVfile('Classification Labels')
def doPrintShowDriveLabels():
printShowDriveLabels([_getMain()._getAdminEmail()], True)
def _showDriveLabelPermission(labelperm, j, jcount, FJQC):
if FJQC.formatJSON:
_getMain().printLine(json.dumps(_getMain().cleanJSON(labelperm), ensure_ascii=False, sort_keys=True))
return
_getMain().printEntity([Ent.CLASSIFICATION_LABEL_PERMISSION_NAME, f'{labelperm["name"]}'], j, jcount)
Ind.Increment()
_getMain().showJSON(None, labelperm)
Ind.Decrement()
# gam [<UserTypeEntity>] create classificationlabelpermission <ClassificationLabelNameEntity>
# (user <UserItem>) | (group <GroupItem) | (audience <String>)
# role applier|editor|organizer|reader
# [nodetails|formatjson] [asadmin]
def createDriveLabelPermissions(users, useAdminAccess=False):
driveLabelNameEntity = _getMain().getUserObjectEntity(Cmd.OB_CLASSIFICATION_LABEL_NAME, Ent.CLASSIFICATION_LABEL_PERMISSION, shlexSplit=True)
FJQC = _getMain().FormatJSONQuoteChar()
parameters = {'useAdminAccess': useAdminAccess}
body = {}
showDetails = True
while Cmd.ArgumentsRemaining():
myarg = _getMain().getArgument()
if myarg in _getMain().ADMIN_ACCESS_OPTIONS:
parameters['useAdminAccess'] = True
elif myarg == 'role':
body['role'] = _getMain().getChoice(DRIVELABELS_PERMISSION_ROLE_MAP, mapChoice=True)
elif myarg in {'user', 'group'}:
email = _getMain().getEmailAddress(returnUIDprefix='id:')
body['email'], status = _getMain().convertUIDtoEmailAddressWithType(email, emailTypes=[myarg])
if status == 'unknown':
Cmd.Backup()
_getMain().usageErrorExit(Msg.ENTITY_DOES_NOT_EXIST.format(email))
elif myarg == 'audience':
audience = _getMain().getString(Cmd.OB_STRING)
if not audience.startswith('audiences/'):
audience = 'audiences/'+audience
body['audience'] = audience
elif myarg == 'nodetails':
showDetails = False
else:
FJQC.GetFormatJSON(myarg)
if 'role' not in body:
_getMain().missingArgumentExit(f'role {"|".join(DRIVELABELS_PERMISSION_ROLE_MAP.keys())}')
if 'email' not in body and 'audience' not in body:
_getMain().missingArgumentExit('(user <UserItem>) | (group <GroupItem) | (audience <String>)')
api = API.DRIVELABELS_ADMIN if parameters['useAdminAccess'] else API.DRIVELABELS_USER
i, count, users = _getMain().getEntityArgument(users)
for user in users:
i += 1
user, drive, labelNames, jcount = _getMain()._validateUserGetObjectList(user, i, count, driveLabelNameEntity,
api=api, showAction=not FJQC.formatJSON)
if jcount == 0:
continue
Ind.Increment()
j = 0
for name in labelNames:
j += 1
kvList = [Ent.USER, user, Ent.CLASSIFICATION_LABEL_NAME, name, Ent.CLASSIFICATION_LABEL_PERMISSION, None]
name = validateDriveLabelName(name, kvList, j, jcount, False)
if name is None:
continue
try:
labelperm = _getMain().callGAPI(drive.labels().permissions(), 'create',
bailOnInternalError=True,
throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.PERMISSION_DENIED, GAPI.NOT_FOUND,
GAPI.INVALID, GAPI.INTERNAL_ERROR],
parent=name, body=body, **parameters)
kvList = [Ent.USER, user, Ent.CLASSIFICATION_LABEL_PERMISSION, labelperm['name']]
if not FJQC.formatJSON:
_getMain().entityActionPerformed(kvList, j, jcount)
if showDetails:
Ind.Increment()
_showDriveLabelPermission(labelperm, j, jcount, FJQC)
Ind.Decrement()
except (GAPI.permissionDenied, GAPI.notFound, GAPI.invalid, GAPI.internalError) as e:
_getMain().entityActionFailedWarning(kvList, str(e), j, jcount)
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
break
Ind.Decrement()
def doCreateDriveLabelPermissions():
createDriveLabelPermissions([_getMain()._getAdminEmail()], True)
# gam [<UserTypeEntity>] delete classificationlabelpermission <ClassificationLabelNameEntity>
# (user <UserItem>) | (group <GroupItem) | (audience <String>)
# [asadmin]
# gam [<UserTypeEntity>] remove classificationlabelpermission <ClassificationLabelPermissionNameEntity>
# [asadmin]
def deleteDriveLabelPermissions(users, useAdminAccess=False):
doDelete = Act.Get() == Act.DELETE
if doDelete:
driveLabelNameEntity = _getMain().getUserObjectEntity(Cmd.OB_CLASSIFICATION_LABEL_NAME, Ent.CLASSIFICATION_LABEL, shlexSplit=True)
else:
driveLabelNameEntity = _getMain().getUserObjectEntity(Cmd.OB_CLASSIFICATION_LABEL_PERMISSION_NAME, Ent.CLASSIFICATION_LABEL_PERMISSION, shlexSplit=True)
parameters = {'useAdminAccess': useAdminAccess, 'requests': [None]}
labelperm = ''
while Cmd.ArgumentsRemaining():
myarg = _getMain().getArgument()
if myarg in _getMain().ADMIN_ACCESS_OPTIONS:
parameters['useAdminAccess'] = True
elif doDelete and myarg in {'user', 'group'}:
labelperm = ['people/', 'groups/'][myarg == 'group']+_getMain().convertEmailAddressToUID(_getMain().getEmailAddress(), cd=None, emailType=myarg, savedLocation=None)
elif doDelete and myarg == 'audience':
audience = _getMain().getString(Cmd.OB_STRING)
if not audience.startswith('audiences/'):
audience = 'audiences/'+audience
labelperm = audience
else:
_getMain().unknownArgumentExit()
if doDelete and not labelperm:
_getMain().missingArgumentExit('(user <UserItem>) | (group <GroupItem) | (audience <String>)')
api = API.DRIVELABELS_ADMIN if parameters['useAdminAccess'] else API.DRIVELABELS_USER
i, count, users = _getMain().getEntityArgument(users)
for user in users:
i += 1
user, drive, labelPermNames, jcount = _getMain()._validateUserGetObjectList(user, i, count, driveLabelNameEntity,
api=api, showAction=True)
if jcount == 0:
continue
Ind.Increment()
j = 0
for name in labelPermNames:
j += 1
kvList = [Ent.USER, user, Ent.CLASSIFICATION_LABEL_PERMISSION_NAME, name]
if doDelete:
parent = validateDriveLabelName(name, kvList, j, jcount, False)
if parent is None:
continue
name = parent+'/permissions/'+labelperm
else:
name, parent = validateDriveLabelName(name, kvList, j, jcount, True)
if name is None:
continue
kvList[-1] = name
parameters['requests'][0] = {'name': name}
try:
_getMain().callGAPI(drive.labels().permissions(), 'batchDelete',
throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.PERMISSION_DENIED, GAPI.INVALID, GAPI.NOT_FOUND],
parent=parent, body=parameters)
_getMain().entityActionPerformed(kvList, j, jcount)
except (GAPI.permissionDenied, GAPI.invalid, GAPI.notFound) as e:
_getMain().entityActionFailedWarning(kvList, str(e), j, jcount)
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
break
Ind.Decrement()
def doDeleteDriveLabelPermissions():
deleteDriveLabelPermissions([_getMain()._getAdminEmail()], True)
# gam [<UserTypeEntity>] print classificationlabelpermissions <ClassificationLabelNameEntity> [todrive <ToDriveAttribute>*]
# [formatjson [quotechar <Character>]] [asadmin]
# gam [<UserTypeEntity>] show classificationlabelpermissions <ClassificationLabelNameEntity>
# [formatjson] [asadmin]
def printShowDriveLabelPermissions(users, useAdminAccess=False):
csvPF = _getMain().CSVPrintFile(['User', 'name', 'email', 'role', 'person', 'group', 'audience'], 'sortall') if Act.csvFormat() else None
driveLabelNameEntity = _getMain().getUserObjectEntity(Cmd.OB_CLASSIFICATION_LABEL_NAME, Ent.CLASSIFICATION_LABEL_PERMISSION, shlexSplit=True)
FJQC = _getMain().FormatJSONQuoteChar(csvPF)
parameters = {'useAdminAccess': useAdminAccess}
while Cmd.ArgumentsRemaining():
myarg = _getMain().getArgument()
if csvPF and myarg == 'todrive':
csvPF.GetTodriveParameters()
elif myarg in _getMain().ADMIN_ACCESS_OPTIONS:
parameters['useAdminAccess'] = True
else:
FJQC.GetFormatJSONQuoteChar(myarg, True)
if csvPF and FJQC.formatJSON:
csvPF.SetJSONTitles(['User', 'name', 'JSON'])
api = API.DRIVELABELS_ADMIN if parameters['useAdminAccess'] else API.DRIVELABELS_USER
i, count, users = _getMain().getEntityArgument(users)
for user in users:
i += 1
user, drive, labelNames, jcount = _getMain()._validateUserGetObjectList(user, i, count, driveLabelNameEntity,
api=api, showAction=FJQC is None or not FJQC.formatJSON)
if jcount == 0:
continue
Ind.Increment()
j = 0
for name in labelNames:
j += 1
kvList = [Ent.USER, user, Ent.CLASSIFICATION_LABEL_NAME, name, Ent.CLASSIFICATION_LABEL_PERMISSION, None]
name = validateDriveLabelName(name, kvList, j, jcount, False)
if name is None:
continue
kvList = [Ent.USER, user, Ent.CLASSIFICATION_LABEL_NAME, name]
if csvPF:
_getMain().printGettingAllEntityItemsForWhom(Ent.CLASSIFICATION_LABEL_PERMISSION, name, j, jcount)
pageMessage = _getMain().getPageMessageForWhom()
else:
pageMessage = None
try:
labelperms = _getMain().callGAPIpages(drive.labels().permissions(), 'list', 'labelPermissions',
pageMessage=pageMessage,
throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.PERMISSION_DENIED, GAPI.NOT_FOUND],
parent=name, **parameters, fields='nextPageToken,labelPermissions', pageSize=200)
if not csvPF:
jcount = len(labelperms)
if not FJQC.formatJSON:
_getMain().entityPerformActionNumItems(kvList, jcount, Ent.CLASSIFICATION_LABEL_PERMISSION, i, count)
Ind.Increment()
j = 0
for labelperm in labelperms:
j += 1
_showDriveLabelPermission(labelperm, j, jcount, FJQC)
Ind.Decrement()
else:
for labelperm in labelperms:
row = _getMain().flattenJSON(labelperm, flattened={'User': user})
if not FJQC.formatJSON:
csvPF.WriteRowTitles(row)
elif csvPF.CheckRowTitles(row):
row = {'User': user, 'name': labelperm['name']}
row['JSON'] = json.dumps(_getMain().cleanJSON(labelperm), ensure_ascii=False, sort_keys=True)
csvPF.WriteRowNoFilter(row)
except (GAPI.permissionDenied, GAPI.notFound) as e:
_getMain().entityActionFailedWarning(kvList, str(e), j, jcount)
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
break
Ind.Decrement()
if csvPF:
csvPF.writeCSVfile('Classification Label Permissions')
def doPrintShowDriveLabelPermissions():
printShowDriveLabelPermissions([_getMain()._getAdminEmail()], True)
DRIVELABEL_FIELD_TYPE_MAP = {
'text': 'setTextValues',
'selection': 'setSelectionValues',
'integer': 'setIntegerValues',
'date': 'setDateValues',
'user': 'setUserValues',
}
# gam <UserTypeEntity> process filedrivelabels <DriveFileEntity>
# (addlabel <ClassificationLabelIDList>)*
# (deletelabel <ClassificationLabelIDList>)*
# (addlabelfield <ClassificationLabelID> <ClassificationLabelFieldID>
# (text <String>)|selection <ClassificationLabelSelectionIDList>)|
# (integer <Number>)|(date <Date>)|(user <EmailAddressList>))*
# (deletelabelfield <ClassificationLabelID> <ClassificationLabelFieldID>)*
# [nodetails]
def processFileDriveLabels(users):
fileIdEntity = getDriveFileEntity()
actionList = {'addlabel': {'action': Act.CREATE, 'list': []},
'deletelabel': {'action': Act.DELETE, 'list': []},
'addlabelfield': {'action': Act.CREATE, 'list': []},
'deletelabelfield': {'action': Act.DELETE, 'list': []},
}
showDetails = True
kcount = 0
while Cmd.ArgumentsRemaining():
myarg = _getMain().getArgument()
if myarg == 'addlabel':
labelIds = _getMain().getEntityList(Cmd.OB_CLASSIFICATION_LABEL_ID, shlexSplit=True)
for labelId in labelIds:
actionList[myarg]['list'].append({'labelModifications': [{'labelId': normalizeDriveLabelID(labelId)}]})
kcount += 1
elif myarg == 'deletelabel':
labelIds = _getMain().getEntityList(Cmd.OB_CLASSIFICATION_LABEL_ID, shlexSplit=True)
for labelId in labelIds:
actionList[myarg]['list'].append({'labelModifications': [{'labelId': normalizeDriveLabelID(labelId), 'removeLabel': True}]})
kcount += 1
elif myarg == 'addlabelfield':
labelId = normalizeDriveLabelID(_getMain().getString(Cmd.OB_CLASSIFICATION_LABEL_ID))
fieldId = _getMain().getString(Cmd.OB_CLASSIFICATION_LABEL_FIELD_ID)
fieldType = _getMain().getChoice(DRIVELABEL_FIELD_TYPE_MAP, mapChoice=True)
if fieldType == 'setTextValues':
valueList = [_getMain().getString(Cmd.OB_STRING, minLen=0)]
elif fieldType == 'setSelectionValues':
valueList = _getMain().convertEntityToList(_getMain().getString(Cmd.OB_CLASSIFICATION_LABEL_SELECTION_ID_LIST, minLen=0), shlexSplit=True)
elif fieldType == 'setIntegerValues':
valueList = [_getMain().getInteger()]
elif fieldType == 'setDateValues':
valueList = [_getMain().getYYYYMMDD()]
else: #elif fieldType == 'setUserValues':
valueList = _getMain().convertEntityToList(_getMain().getString(Cmd.OB_EMAIL_ADDRESS_LIST, minLen=0))
actionList[myarg]['list'].append({'labelModifications': [{'labelId': labelId,
'fieldModifications': [{'fieldId': fieldId, fieldType: valueList}]}]})
kcount += 1
elif myarg == 'deletelabelfield':
labelId = normalizeDriveLabelID(_getMain().getString(Cmd.OB_CLASSIFICATION_LABEL_ID))
fieldId = _getMain().getString(Cmd.OB_CLASSIFICATION_LABEL_FIELD_ID)
actionList[myarg]['list'].append({'labelModifications': [{'labelId': labelId,
'fieldModifications': [{'fieldId': fieldId, 'unsetValues': True}]}]})
kcount += 1
elif myarg == 'nodetails':
showDetails = False
else:
_getMain().unknownArgumentExit()
i, count, users = _getMain().getEntityArgument(users)
for user in users:
i += 1
user, drive, jcount = _validateUserGetFileIDs(user, i, count, fileIdEntity, entityType=None)
if jcount == 0:
continue
Act.Set(Act.PROCESS)
_getMain().entityPerformActionSubItemModifierNumItems([Ent.USER, user], Ent.CLASSIFICATION_LABEL, Act.MODIFIER_FOR, jcount, Ent.DRIVE_FILE_OR_FOLDER)
Ind.Increment()
j = 0
userError = False
for fileId in fileIdEntity['list']:
j += 1
k = 0
kvList = [Ent.USER, user, Ent.DRIVE_FILE_OR_FOLDER_ID, fileId]
Act.Set(Act.PROCESS)
_getMain().entityPerformActionNumItems(kvList, kcount, Ent.CLASSIFICATION_LABEL)
Ind.Increment()
for operation in ['deletelabelfield', 'deletelabel', 'addlabel', 'addlabelfield']:
Act.Set(actionList[operation]['action'])
for action in actionList[operation]['list']:
k += 1
xkvList = kvList.copy()
xkvList.extend([Ent.CLASSIFICATION_LABEL_ID, action['labelModifications'][0]['labelId']])
if 'fieldModifications' in action['labelModifications'][0]:
xkvList.extend([Ent.CLASSIFICATION_LABEL_FIELD_ID, action['labelModifications'][0]['fieldModifications'][0]['fieldId']])
try:
label = _getMain().callGAPI(drive.files(), 'modifyLabels',
throwReasons=GAPI.DRIVE3_MODIFY_LABEL_THROW_REASONS,
fileId=fileId, body=action)
_getMain().entityActionPerformed(xkvList, k, kcount)
if showDetails:
Ind.Increment()
_getMain().showJSON(None, label, timeObjects=DRIVELABELS_TIME_OBJECTS, dictObjectsKey={'fields': 'id', 'modifiedLabels': 'id'})
Ind.Decrement()
except GAPI.fileNotFound as e:
_getMain().entityActionFailedWarning(kvList, str(e), j, jcount)
break
except (GAPI.notFound, GAPI.forbidden, GAPI.internalError,
GAPI.fileNeverWritable, GAPI.applyLabelForbidden,
GAPI.insufficientFilePermissions, GAPI.unknownError, GAPI.invalidInput, GAPI.badRequest,
GAPI.labelMutationUnknownField, GAPI.labelMutationIllegalSelection, GAPI.labelMutationForbidden,
GAPI.labelMultipleValuesForSingularField) as e:
_getMain().entityActionFailedWarning(xkvList, str(e), k, kcount)
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
userError = True
break
Ind.Decrement()
if userError:
break
Ind.Decrement()
# gam print ownership <DriveFileID>|(drivefilename <DriveFileName>) [todrive <ToDriveAttribute>*]
# (addcsvdata <FieldName> <String>)*
# [formatjson [quotechar <Character>]]
# gam show ownership <DriveFileID>|(drivefilename <DriveFileName>)
# [formatjson]

287
src/gam/cmd/drive/looker.py Normal file
View File

@@ -0,0 +1,287 @@
"""Looker Studio asset and permission management.
Part of the drive sub-package, extracted from drive.py."""
"""GAM Google Drive file, permission, shared drive, and label management."""
import re
import json
import sys
from gamlib import glaction
from gamlib import glapi as API
from gamlib import glcfg as GC
from gamlib import glclargs
from gamlib import glentity
from gamlib import glgapi as GAPI
from gamlib import glglobals as GM
from gamlib import glindent
from gamlib import glmsgs as Msg
Act = glaction.GamAction()
Ent = glentity.GamEntity()
Ind = glindent.GamIndent()
Cmd = glclargs.GamCLArgs()
APPLICATION_VND_GOOGLE_APPS = 'application/vnd.google-apps.'
MIMETYPE_GA_DOCUMENT = f'{APPLICATION_VND_GOOGLE_APPS}document'
MIMETYPE_GA_DRAWING = f'{APPLICATION_VND_GOOGLE_APPS}drawing'
MIMETYPE_GA_FILE = f'{APPLICATION_VND_GOOGLE_APPS}file'
MIMETYPE_GA_FOLDER = f'{APPLICATION_VND_GOOGLE_APPS}folder'
MIMETYPE_GA_FORM = f'{APPLICATION_VND_GOOGLE_APPS}form'
MIMETYPE_GA_FUSIONTABLE = f'{APPLICATION_VND_GOOGLE_APPS}fusiontable'
MIMETYPE_GA_JAM = f'{APPLICATION_VND_GOOGLE_APPS}jam'
MIMETYPE_GA_MAP = f'{APPLICATION_VND_GOOGLE_APPS}map'
MIMETYPE_GA_PRESENTATION = f'{APPLICATION_VND_GOOGLE_APPS}presentation'
MIMETYPE_GA_SCRIPT = f'{APPLICATION_VND_GOOGLE_APPS}script'
MIMETYPE_GA_SCRIPT_JSON = f'{APPLICATION_VND_GOOGLE_APPS}script+json'
MIMETYPE_GA_SHORTCUT = f'{APPLICATION_VND_GOOGLE_APPS}shortcut'
MIMETYPE_GA_3P_SHORTCUT = f'{APPLICATION_VND_GOOGLE_APPS}drive-sdk'
MIMETYPE_GA_SITE = f'{APPLICATION_VND_GOOGLE_APPS}site'
MIMETYPE_GA_SPREADSHEET = f'{APPLICATION_VND_GOOGLE_APPS}spreadsheet'
ME_IN_OWNERS = "'me' in owners"
ME_IN_OWNERS_AND = ME_IN_OWNERS + " and "
NOT_ME_IN_OWNERS = "not " + ME_IN_OWNERS
NOT_ME_IN_OWNERS_AND = NOT_ME_IN_OWNERS + " and "
WITH_ANY_FILE_NAME = "name = '{0}'"
WITH_MY_FILE_NAME = ME_IN_OWNERS_AND + WITH_ANY_FILE_NAME
WITH_OTHER_FILE_NAME = NOT_ME_IN_OWNERS_AND + WITH_ANY_FILE_NAME
ROOT = 'root'
ORPHANS = 'Orphans'
SHARED_WITHME = 'SharedWithMe'
SHARED_DRIVES = 'SharedDrives'
def _getMain():
return sys.modules['gam']
def __getattr__(name):
"""Fall back to gam module for any undefined names."""
main = _getMain()
try:
return getattr(main, name)
except AttributeError:
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
LOOKERSTUDIO_ASSETTYPE_CHOICE_MAP = {
'report': ['REPORT'],
'datasource': ['DATA_SOURCE'],
'all': ['REPORT', 'DATA_SOURCE'],
}
def initLookerStudioAssetSelectionParameters():
return ({'owner': None, 'title': None, 'includeTrashed': False}, {'assetTypes': ['REPORT']})
def getLookerStudioAssetSelectionParameters(myarg, parameters, assetTypes):
if myarg in {'assettype', 'assettypes'}:
assetTypes['assetTypes'] = _getMain().getChoice(LOOKERSTUDIO_ASSETTYPE_CHOICE_MAP, mapChoice=True)
elif myarg == 'title':
parameters['title'] = _getMain().getString(Cmd.OB_STRING)
elif myarg == 'owner':
parameters['owner'] = _getMain().getEmailAddress(noUid=True)
elif myarg == 'includetrashed':
parameters['includeTrashed'] = True
else:
return False
return True
def _validateUserGetLookerStudioAssetIds(user, i, count, entity):
if entity:
if entity['dict']:
entityList = [{'name': item, 'title': item} for item in entity['dict'][user]]
else:
entityList = [{'name': item, 'title': item} for item in entity['list']]
else:
entityList = []
user, ds = _getMain().buildGAPIServiceObject(API.LOOKERSTUDIO, user, i, count)
if not ds:
return (user, None, None, 0)
return (user, ds, entityList, len(entityList))
def _getLookerStudioAssetByID(ds, user, i, count, assetId):
_getMain().printGettingAllEntityItemsForWhom(Ent.LOOKERSTUDIO_ASSET, user, i, count)
try:
return _getMain().callGAPI(ds.assets(), 'get',
throwReasons=GAPI.LOOKERSTUDIO_THROW_REASONS,
name=f'assets/{assetId}')
except (GAPI.invalidArgument, GAPI.badRequest, GAPI.notFound, GAPI.permissionDenied, GAPI.internalError) as e:
_getMain().entityActionFailedWarning([Ent.USER, user], str(e), i, count)
except GAPI.serviceNotAvailable:
_getMain().userLookerStudioServiceNotEnabledWarning(user, i, count)
return None
def _getLookerStudioAssets(ds, user, i, count, parameters, assetTypes, fields, orderBy=None):
assets = []
for assetType in assetTypes['assetTypes']:
entityType = Ent.LOOKERSTUDIO_ASSET_REPORT if assetType == 'REPORT' else Ent.LOOKERSTUDIO_ASSET_DATASOURCE
_getMain().printGettingAllEntityItemsForWhom(entityType, user, i, count)
parameters['assetTypes'] = assetType
try:
assets.extend(_getMain().callGAPIpages(ds.assets(), 'search', 'assets',
pageMessage=_getMain().getPageMessage(),
throwReasons=GAPI.LOOKERSTUDIO_THROW_REASONS,
**parameters, orderBy=orderBy, fields=fields))
except (GAPI.invalidArgument, GAPI.badRequest, GAPI.notFound, GAPI.permissionDenied, GAPI.internalError) as e:
_getMain().entityActionFailedWarning([Ent.USER, user], str(e), i, count)
return (None, 0)
except GAPI.serviceNotAvailable:
_getMain().userLookerStudioServiceNotEnabledWarning(user, i, count)
return (None, 0)
return (assets, len(assets))
LOOKERSTUDIO_ASSETS_ORDERBY_CHOICE_MAP = {
'title': 'title'
}
LOOKERSTUDIO_ASSETS_TIME_OBJECTS = {'updateTime', 'updateByMeTime', 'createTime', 'lastViewByMeTime'}
# gam <UserTypeEntity> print lookerstudioassets [todrive <ToDriveAttribute>*]
# [([assettype report|datasource|all] [title <String>]
# [owner <Emailddress>] [includetrashed]
# [orderby title [ascending|descending]]) |
# (assetids <LookerStudioAssetIDEntity>)]
# [stripcrsfromtitle]
# [formatjson [quotechar <Character>]]
# gam <UserTypeEntity> show lookerstudioassets
# [([assettype report|datasource|all] [title <String>]
# [owner <Emailddress>] [includetrashed]
# [orderby title [ascending|descending]]) |
# (assetids <LookerStudioAssetIDEntity>)]
# [stripcrsfromtitle]
# [formatjson]
def printShowLookerStudioAssets(users):
def _printAsset(asset, user):
if stripCRsFromTitle:
asset['title'] = _stripControlCharsFromName(asset['title'])
row = _getMain().flattenJSON(asset, flattened={'User': user})
if not FJQC.formatJSON:
csvPF.WriteRowTitles(row)
elif csvPF.CheckRowTitles(row):
csvPF.WriteRowNoFilter({'User': user, 'title': asset['title'],
'JSON': json.dumps(_getMain().cleanJSON(asset, timeObjects=LOOKERSTUDIO_ASSETS_TIME_OBJECTS), ensure_ascii=False, sort_keys=True)})
def _showAsset(asset):
if stripCRsFromTitle:
asset['title'] = _stripControlCharsFromName(asset['title'])
if FJQC.formatJSON:
_getMain().printLine(json.dumps(_getMain().cleanJSON(asset, timeObjects=LOOKERSTUDIO_ASSETS_TIME_OBJECTS), ensure_ascii=False, sort_keys=False))
return
_getMain().printEntity([Ent.LOOKERSTUDIO_ASSET, asset['title']], j, jcount)
Ind.Increment()
_getMain().showJSON(None, asset, timeObjects=LOOKERSTUDIO_ASSETS_TIME_OBJECTS)
Ind.Decrement()
csvPF = _getMain().CSVPrintFile(['User', 'title']) if Act.csvFormat() else None
FJQC = _getMain().FormatJSONQuoteChar(csvPF)
OBY = _getMain().OrderBy(LOOKERSTUDIO_ASSETS_ORDERBY_CHOICE_MAP, ascendingKeyword='ascending', descendingKeyword='')
parameters, assetTypes = initLookerStudioAssetSelectionParameters()
assetIdEntity = None
stripCRsFromTitle = False
while Cmd.ArgumentsRemaining():
myarg = _getMain().getArgument()
if csvPF and myarg == 'todrive':
csvPF.GetTodriveParameters()
elif getLookerStudioAssetSelectionParameters(myarg, parameters, assetTypes):
pass
elif myarg in {'assetid', 'assetids'}:
assetIdEntity = _getMain().getUserObjectEntity(Cmd.OB_USER_ENTITY, Ent.LOOKERSTUDIO_ASSETID)
elif myarg == 'stripcrsfromtitle':
stripCRsFromTitle = True
elif myarg == 'orderby':
OBY.GetChoice()
else:
FJQC.GetFormatJSONQuoteChar(myarg, True)
i, count, users = _getMain().getEntityArgument(users)
for user in users:
i += 1
user, ds, assets, jcount = _validateUserGetLookerStudioAssetIds(user, i, count, assetIdEntity)
if not ds:
continue
if assetIdEntity is None:
assets, jcount = _getLookerStudioAssets(ds, user, i, count, parameters, assetTypes, 'nextPageToken,assets', OBY.orderBy)
if assets is None:
continue
if not csvPF:
if not FJQC.formatJSON:
_getMain().entityPerformActionNumItems([Ent.USER, user], jcount, Ent.LOOKERSTUDIO_ASSET, i, count)
Ind.Increment()
j = 0
for asset in assets:
j += 1
if assetIdEntity:
asset = _getLookerStudioAssetByID(ds, user, i, count, asset['name'])
if asset:
_showAsset(asset)
Ind.Decrement()
elif assets:
for asset in assets:
if assetIdEntity:
asset = _getLookerStudioAssetByID(ds, user, i, count, asset['name'])
if asset:
_printAsset(asset, user)
elif GC.Values[GC.CSV_OUTPUT_USERS_AUDIT]:
csvPF.WriteRowNoFilter({'User': user})
if csvPF:
csvPF.writeCSVfile('Looker Studio Assets')
def _showLookerStudioPermissions(user, asset, permissions, j, jcount, FJQC):
if FJQC is not None and FJQC.formatJSON:
permissions['User'] = user
permissions['assetId'] = asset['name']
_getMain().printLine(json.dumps(_getMain().cleanJSON(permissions), ensure_ascii=False, sort_keys=False))
return
permissions = permissions['permissions']
if permissions:
_getMain().printEntity([Ent.LOOKERSTUDIO_ASSET, asset['title'], Ent.LOOKERSTUDIO_PERMISSION, ''], j, jcount)
for role in ['OWNER', 'EDITOR', 'VIEWER']:
members = permissions.get(role, {}).get('members', [])
if members:
lrole = role.lower()
Ind.Increment()
for member in members:
_getMain().printKeyValueList([lrole, member])
Ind.Decrement()
LOOKERSTUDIO_VIEW_PERMISSION_ROLE_CHOICE_MAP = {
'editor': 'EDITOR',
'owner': 'OWNER',
'viewer': 'VIEWER',
}
LOOKERSTUDIO_ADD_UPDATE_PERMISSION_ROLE_CHOICE_MAP = {
'editor': 'EDITOR',
'viewer': 'VIEWER',
}
LOOKERSTUDIO_DELETE_PERMISSION_ROLE_CHOICE_MAP = {
'any': None,
'editor': None,
'owner': None,
'viewer': None,
}
LOOKERSTUDIO_PERMISSION_MODIFIER_MAP = {
Act.ADD: Act.MODIFIER_TO,
Act.DELETE: Act.MODIFIER_FROM,
Act.UPDATE: Act.MODIFIER_FOR
}
# gam <UserTypeEntity> add lookerstudiopermissions
# [([assettype report|datasource|all] [title <String>]
# [owner <Emailddress>] [includetrashed]
# [orderby title [ascending|descending]]) |
# (assetids <LookerStudioAssetIDEntity>)]
# (role editor|viewer <LookerStudioPermissionEntity>)+
# [nodetails]
# gam <UserTypeEntity> delete lookerstudiopermissions
# ([[assettype report|datasource|all] [title <String>]
# [owner <Emailddress>] [includetrashed]
# [orderby title [ascending|descending]]) |
# (assetids <LookerStudioAssetIDEntity>)]
# (role any <LookerStudioPermissionEntity>)+
# [nodetails]
# gam <UserTypeEntity> update lookerstudiopermissions
# [([assettype report|datasource|all] [title <String>]
# [owner <Emailddress>] [includetrashed]
# [orderby title [ascending|descending]]) |
# (assetids <LookerStudioAssetIDEntity>)]
# (role editor|viewer <LookerStudioPermissionEntity>)+
# [nodetails]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,580 @@
"""File revision management.
Part of the drive sub-package, extracted from drive.py."""
"""GAM Google Drive file, permission, shared drive, and label management."""
import re
import sys
from gamlib import glaction
from gamlib import glapi as API
from gamlib import glcfg as GC
from gamlib import glclargs
from gamlib import glentity
from gamlib import glgapi as GAPI
from gamlib import glglobals as GM
from gamlib import glindent
from gamlib import glmsgs as Msg
Act = glaction.GamAction()
Ent = glentity.GamEntity()
Ind = glindent.GamIndent()
Cmd = glclargs.GamCLArgs()
APPLICATION_VND_GOOGLE_APPS = 'application/vnd.google-apps.'
MIMETYPE_GA_DOCUMENT = f'{APPLICATION_VND_GOOGLE_APPS}document'
MIMETYPE_GA_DRAWING = f'{APPLICATION_VND_GOOGLE_APPS}drawing'
MIMETYPE_GA_FILE = f'{APPLICATION_VND_GOOGLE_APPS}file'
MIMETYPE_GA_FOLDER = f'{APPLICATION_VND_GOOGLE_APPS}folder'
MIMETYPE_GA_FORM = f'{APPLICATION_VND_GOOGLE_APPS}form'
MIMETYPE_GA_FUSIONTABLE = f'{APPLICATION_VND_GOOGLE_APPS}fusiontable'
MIMETYPE_GA_JAM = f'{APPLICATION_VND_GOOGLE_APPS}jam'
MIMETYPE_GA_MAP = f'{APPLICATION_VND_GOOGLE_APPS}map'
MIMETYPE_GA_PRESENTATION = f'{APPLICATION_VND_GOOGLE_APPS}presentation'
MIMETYPE_GA_SCRIPT = f'{APPLICATION_VND_GOOGLE_APPS}script'
MIMETYPE_GA_SCRIPT_JSON = f'{APPLICATION_VND_GOOGLE_APPS}script+json'
MIMETYPE_GA_SHORTCUT = f'{APPLICATION_VND_GOOGLE_APPS}shortcut'
MIMETYPE_GA_3P_SHORTCUT = f'{APPLICATION_VND_GOOGLE_APPS}drive-sdk'
MIMETYPE_GA_SITE = f'{APPLICATION_VND_GOOGLE_APPS}site'
MIMETYPE_GA_SPREADSHEET = f'{APPLICATION_VND_GOOGLE_APPS}spreadsheet'
ME_IN_OWNERS = "'me' in owners"
ME_IN_OWNERS_AND = ME_IN_OWNERS + " and "
NOT_ME_IN_OWNERS = "not " + ME_IN_OWNERS
NOT_ME_IN_OWNERS_AND = NOT_ME_IN_OWNERS + " and "
WITH_ANY_FILE_NAME = "name = '{0}'"
WITH_MY_FILE_NAME = ME_IN_OWNERS_AND + WITH_ANY_FILE_NAME
WITH_OTHER_FILE_NAME = NOT_ME_IN_OWNERS_AND + WITH_ANY_FILE_NAME
ROOT = 'root'
ORPHANS = 'Orphans'
SHARED_WITHME = 'SharedWithMe'
SHARED_DRIVES = 'SharedDrives'
def _getMain():
return sys.modules['gam']
def __getattr__(name):
"""Fall back to gam module for any undefined names."""
main = _getMain()
try:
return getattr(main, name)
except AttributeError:
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
def getRevisionsEntity():
revisionsEntity = {'list': [], 'dict': None, 'count': None, 'time': None, 'range': None}
startEndTime = _getMain().StartEndTime()
entitySelector = _getMain().getEntitySelector()
if entitySelector:
entityList = _getMain().getEntitySelection(entitySelector, False)
if isinstance(entityList, dict):
revisionsEntity['dict'] = entityList
else:
revisionsEntity['list'] = entityList
else:
myarg = _getMain().getString(Cmd.OB_DRIVE_FILE_REVISION_ID, checkBlank=True)
mycmd = myarg.lower()
if mycmd == 'id':
revisionsEntity['list'] = _getMain().getStringReturnInList(Cmd.OB_DRIVE_FILE_REVISION_ID)
elif mycmd[:3] == 'id:':
revisionsEntity['list'] = [myarg[3:]]
elif mycmd == 'ids':
revisionsEntity['list'] = getString(Cmd.OB_DRIVE_FILE_REVISION_ID).replace(',', ' ').split()
elif mycmd[:4] == 'ids:':
revisionsEntity['list'] = myarg[4:].replace(',', ' ').split()
elif mycmd in {'first', 'last', 'allexceptfirst', 'allexceptlast'}:
revisionsEntity['count'] = (mycmd, _getMain().getInteger(minVal=1))
elif mycmd in {'before', 'after'}:
dateTime, _, _ = _getMain().getTimeOrDeltaFromNow(True)
revisionsEntity['time'] = (mycmd, dateTime)
elif mycmd == 'range':
startEndTime.Get(mycmd)
revisionsEntity['range'] = (mycmd, startEndTime.startDateTime, startEndTime.endDateTime)
else:
revisionsEntity['list'] = [myarg]
return revisionsEntity
def _selectRevisionIds(drive, fileId, origUser, user, i, count, j, jcount, revisionsEntity):
if revisionsEntity['list']:
return revisionsEntity['list']
if revisionsEntity['dict']:
if not GM.Globals[GM.CSV_SUBKEY_FIELD]:
return revisionsEntity['dict'][fileId]
return revisionsEntity['dict'][origUser][fileId]
try:
results = _getMain().callGAPIpages(drive.revisions(), 'list', 'revisions',
throwReasons=GAPI.DRIVE_ACCESS_THROW_REASONS+[GAPI.BAD_REQUEST, GAPI.REVISIONS_NOT_SUPPORTED],
fileId=fileId, fields='nextPageToken,revisions(id,modifiedTime)',
pageSize=GC.Values[GC.DRIVE_MAX_RESULTS])
except (GAPI.fileNotFound, GAPI.forbidden, GAPI.internalError, GAPI.insufficientFilePermissions, GAPI.unknownError,
GAPI.badRequest, GAPI.revisionsNotSupported) as e:
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.DRIVE_FILE_OR_FOLDER_ID, fileId], str(e), j, jcount)
return []
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
return []
numRevisions = len(results)
if numRevisions == 0:
return []
if revisionsEntity['count']:
countType = revisionsEntity['count'][0]
count = revisionsEntity['count'][1]
revisionIds = [revision['id'] for revision in results]
if countType == 'first':
if count >= numRevisions:
return revisionIds
return revisionIds[:count]
if countType == 'last':
if count >= numRevisions:
return revisionIds
return revisionIds[-count:]
if countType == 'allexceptfirst':
if count >= numRevisions:
return []
return revisionIds[count:]
# count: allexceptlast
if count >= numRevisions:
return []
return revisionIds[:-count]
revisionIds = []
if revisionsEntity['time']:
dateTime = revisionsEntity['time'][1]
count = 0
if revisionsEntity['time'][0] == 'before':
for revision in results:
modifiedDateTime = arrow.get(revision['modifiedTime'])
if modifiedDateTime >= dateTime:
break
revisionIds.append(revision['id'])
count += 1
if count >= numRevisions:
return revisionIds[:-1]
return revisionIds
# time: after
for revision in results:
modifiedDateTime = arrow.get(revision['modifiedTime'])
if modifiedDateTime >= dateTime:
revisionIds.append(revision['id'])
count += 1
if count >= numRevisions:
return revisionIds[1:]
return revisionIds
# range
startDateTime = revisionsEntity['range'][1]
endDateTime = revisionsEntity['range'][2]
count = 0
for revision in results:
modifiedDateTime = arrow.get(revision['modifiedTime'])
if modifiedDateTime >= startDateTime:
if modifiedDateTime >= endDateTime:
break
revisionIds.append(revision['id'])
count += 1
if count >= numRevisions:
return revisionIds[1:]
return revisionIds
# gam <UserTypeEntity> delete filerevisions <DriveFileEntity> select <DriveFileRevisionIdEntity> [previewdelete]
# [showtitles] [doit] [max_to_delete <Number>]
def deleteFileRevisions(users):
fileIdEntity = getDriveFileEntity()
revisionsEntity = None
previewDelete = showTitles = doIt = False
maxToProcess = 1
while Cmd.ArgumentsRemaining():
myarg = _getMain().getArgument()
if myarg == 'select':
revisionsEntity = getRevisionsEntity()
elif myarg == 'previewdelete':
previewDelete = True
elif myarg == 'showtitles':
showTitles = True
elif myarg == 'doit':
doIt = True
elif myarg in {'maxtodelete', 'maxtoprocess'}:
maxToProcess = _getMain().getInteger(minVal=0)
else:
_getMain().unknownArgumentExit()
if not revisionsEntity:
_getMain().missingArgumentExit('select <DriveFileRevisionIdEntity>')
i, count, users = _getMain().getEntityArgument(users)
for user in users:
i += 1
origUser = user
user, drive, jcount = _validateUserGetFileIDs(user, i, count, fileIdEntity)
if jcount == 0:
continue
j = 0
for fileId in fileIdEntity['list']:
j += 1
fileName = fileId
entityType = Ent.DRIVE_FILE_OR_FOLDER_ID
if showTitles:
fileName, entityType, _ = _getDriveFileNameFromId(drive, fileId)
revisionIds = _selectRevisionIds(drive, fileId, origUser, user, i, count, j, jcount, revisionsEntity)
kcount = len(revisionIds)
if kcount == 0:
_getMain().entityNumEntitiesActionNotPerformedWarning([Ent.USER, user, entityType, fileName], Ent.DRIVE_FILE_REVISION, kcount,
Msg.NO_ENTITIES_MATCHED.format(Ent.Plural(Ent.DRIVE_FILE_REVISION)), j, jcount)
_getMain().setSysExitRC(_getMain().NO_ENTITIES_FOUND_RC)
continue
if not previewDelete:
if maxToProcess and kcount > maxToProcess:
_getMain().entityNumEntitiesActionNotPerformedWarning([Ent.USER, user, entityType, fileName], Ent.DRIVE_FILE_REVISION, kcount,
Msg.COUNT_N_EXCEEDS_MAX_TO_PROCESS_M.format(kcount, Act.ToPerform(), maxToProcess), j, jcount)
continue
if not doIt:
_getMain().entityNumEntitiesActionNotPerformedWarning([Ent.USER, user, entityType, fileName], Ent.DRIVE_FILE_REVISION, kcount,
Msg.USE_DOIT_ARGUMENT_TO_PERFORM_ACTION, j, jcount)
continue
_getMain().entityPerformActionNumItems([Ent.USER, user, entityType, fileName], kcount, Ent.DRIVE_FILE_REVISION, j, jcount)
else:
_getMain().entityPerformActionNumItemsModifier([Ent.USER, user, entityType, fileName], kcount, Ent.DRIVE_FILE_REVISION, Msg.PREVIEW_ONLY, j, jcount)
Ind.Increment()
k = 0
for revisionId in revisionIds:
k += 1
if not previewDelete:
try:
_getMain().callGAPI(drive.revisions(), 'delete',
throwReasons=GAPI.DRIVE_ACCESS_THROW_REASONS+[GAPI.BAD_REQUEST, GAPI.REVISION_NOT_FOUND, GAPI.REVISION_DELETION_NOT_SUPPORTED,
GAPI.CANNOT_DELETE_ONLY_REVISION, GAPI.REVISIONS_NOT_SUPPORTED],
fileId=fileId, revisionId=revisionId)
_getMain().entityActionPerformed([Ent.USER, user, entityType, fileName, Ent.DRIVE_FILE_REVISION, revisionId], k, kcount)
except (GAPI.fileNotFound, GAPI.forbidden, GAPI.internalError, GAPI.insufficientFilePermissions, GAPI.unknownError,
GAPI.badRequest, GAPI.revisionDeletionNotSupported, GAPI.cannotDeleteOnlyRevision, GAPI.revisionsNotSupported) as e:
_getMain().entityActionFailedWarning([Ent.USER, user, entityType, fileName], str(e), j, jcount)
except GAPI.revisionNotFound:
_getMain().entityDoesNotHaveItemWarning([Ent.USER, user, entityType, fileName, Ent.DRIVE_FILE_REVISION, revisionId], k, kcount)
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
break
else:
_getMain().entityActionNotPerformedWarning([Ent.USER, user, entityType, fileName, Ent.DRIVE_FILE_REVISION, revisionId], Msg.PREVIEW_ONLY, k, kcount)
Ind.Decrement()
REVISIONS_FIELDS_CHOICE_MAP = {
'keepforever': 'keepForever',
'published': 'published',
'publishauto': 'publishAuto',
'publishedoutsidedomain': 'publishedOutsideDomain'
}
# gam <UserTypeEntity> update filerevisions <DriveFileEntity> select <DriveFileRevisionIdEntity> [previewupdate]
# [published [<Boolean>]] [publishauto [<Boolean>]] [publishedoutsidedomain [<Boolean>]]
# [keepforever [<Boolean>]}
# [showtitles] [doit] [max_to_update <Number>]
def updateFileRevisions(users):
fileIdEntity = getDriveFileEntity()
revisionsEntity = None
previewUpdate = showTitles = doIt = False
maxToProcess = 1
body = {}
while Cmd.ArgumentsRemaining():
myarg = _getMain().getArgument()
if myarg == 'select':
revisionsEntity = getRevisionsEntity()
elif myarg in REVISIONS_FIELDS_CHOICE_MAP:
body[REVISIONS_FIELDS_CHOICE_MAP[myarg]] = _getMain().getBoolean()
elif myarg == 'previewupdate':
previewUpdate = True
elif myarg == 'showtitles':
showTitles = True
elif myarg == 'doit':
doIt = True
elif myarg in {'maxtoupdate', 'maxtoprocess'}:
maxToProcess = _getMain().getInteger(minVal=0)
else:
_getMain().unknownArgumentExit()
if not revisionsEntity:
_getMain().missingArgumentExit('select <DriveFileRevisionIdEntity>')
i, count, users = _getMain().getEntityArgument(users)
for user in users:
i += 1
origUser = user
user, drive, jcount = _validateUserGetFileIDs(user, i, count, fileIdEntity)
if jcount == 0:
continue
j = 0
for fileId in fileIdEntity['list']:
j += 1
fileName = fileId
entityType = Ent.DRIVE_FILE_OR_FOLDER_ID
if showTitles:
fileName, entityType, _ = _getDriveFileNameFromId(drive, fileId)
revisionIds = _selectRevisionIds(drive, fileId, origUser, user, i, count, j, jcount, revisionsEntity)
kcount = len(revisionIds)
if kcount == 0:
_getMain().entityNumEntitiesActionNotPerformedWarning([Ent.USER, user, entityType, fileName], Ent.DRIVE_FILE_REVISION, kcount,
Msg.NO_ENTITIES_MATCHED.format(Ent.Plural(Ent.DRIVE_FILE_REVISION)), j, jcount)
_getMain().setSysExitRC(_getMain().NO_ENTITIES_FOUND_RC)
continue
if not previewUpdate:
if maxToProcess and kcount > maxToProcess:
_getMain().entityNumEntitiesActionNotPerformedWarning([Ent.USER, user, entityType, fileName], Ent.DRIVE_FILE_REVISION, kcount,
Msg.COUNT_N_EXCEEDS_MAX_TO_PROCESS_M.format(kcount, Act.ToPerform(), maxToProcess), j, jcount)
continue
if not doIt:
_getMain().entityNumEntitiesActionNotPerformedWarning([Ent.USER, user, entityType, fileName], Ent.DRIVE_FILE_REVISION, kcount,
Msg.USE_DOIT_ARGUMENT_TO_PERFORM_ACTION, j, jcount)
continue
_getMain().entityPerformActionNumItems([Ent.USER, user, entityType, fileName], kcount, Ent.DRIVE_FILE_REVISION, j, jcount)
else:
_getMain().entityPerformActionNumItemsModifier([Ent.USER, user, entityType, fileName], kcount, Ent.DRIVE_FILE_REVISION, Msg.PREVIEW_ONLY, j, jcount)
Ind.Increment()
k = 0
for revisionId in revisionIds:
k += 1
if not previewUpdate:
try:
_getMain().callGAPI(drive.revisions(), 'update',
throwReasons=GAPI.DRIVE_ACCESS_THROW_REASONS+[GAPI.BAD_REQUEST, GAPI.REVISION_NOT_FOUND, GAPI.REVISIONS_NOT_SUPPORTED],
fileId=fileId, revisionId=revisionId, body=body)
_getMain().entityActionPerformed([Ent.USER, user, entityType, fileName, Ent.DRIVE_FILE_REVISION, revisionId], k, kcount)
except (GAPI.fileNotFound, GAPI.forbidden, GAPI.internalError, GAPI.insufficientFilePermissions, GAPI.unknownError,
GAPI.badRequest, GAPI.revisionsNotSupported) as e:
_getMain().entityActionFailedWarning([Ent.USER, user, entityType, fileName], str(e), j, jcount)
except GAPI.revisionNotFound:
_getMain().entityDoesNotHaveItemWarning([Ent.USER, user, entityType, fileName, Ent.DRIVE_FILE_REVISION, revisionId], k, kcount)
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
break
else:
_getMain().entityActionNotPerformedWarning([Ent.USER, user, entityType, fileName, Ent.DRIVE_FILE_REVISION, revisionId], Msg.PREVIEW_ONLY, k, kcount)
Ind.Decrement()
def _selectRevisionResults(results, fileId, origUser, revisionsEntity, previewDelete):
numRevisions = len(results)
if numRevisions == 0:
return results
if revisionsEntity['count']:
countType = revisionsEntity['count'][0]
count = revisionsEntity['count'][1]
if countType == 'first':
if count >= numRevisions:
if previewDelete:
return results[:-1]
return results
return results[:count]
if countType == 'last':
if count >= numRevisions:
if previewDelete:
return results[1:]
return results
return results[-count:]
if countType == 'allexceptfirst':
if count >= numRevisions:
return []
return results[count:]
# count: allexceptlast
if count >= numRevisions:
return []
return results[:-count]
if revisionsEntity['time']:
dateTime = revisionsEntity['time'][1]
count = 0
if revisionsEntity['time'][0] == 'before':
for revision in results:
modifiedDateTime = arrow.get(revision['modifiedTime'])
if modifiedDateTime >= dateTime:
break
count += 1
if count >= numRevisions:
if previewDelete:
return results[:-1]
return results
return results[:count]
# time: after
for revision in results:
modifiedDateTime = arrow.get(revision['modifiedTime'])
if modifiedDateTime >= dateTime:
break
count += 1
if count == 0:
if previewDelete:
return results[1:]
return results
if count >= numRevisions:
return []
return results[count:]
if revisionsEntity['range']:
startDateTime = revisionsEntity['range'][1]
endDateTime = revisionsEntity['range'][2]
count = 0
selectedResults = []
for revision in results:
modifiedDateTime = arrow.get(revision['modifiedTime'])
if modifiedDateTime >= startDateTime:
if modifiedDateTime >= endDateTime:
break
selectedResults.append(revision)
count += 1
if count >= numRevisions:
if previewDelete:
return selectedResults[1:]
return selectedResults
# revisionsIds
selectedResults = []
if revisionsEntity['dict']:
if not GM.Globals[GM.CSV_SUBKEY_FIELD]:
revisionIds = revisionsEntity['dict'][fileId]
else:
revisionIds = revisionsEntity['dict'][origUser][fileId]
else:
revisionIds = revisionsEntity['list']
return [revision for revision in results if revision['id'] in revisionIds]
FILEREVISIONS_FIELDS_CHOICE_MAP = {
'filesize': 'size',
'id': 'id',
'keepforever': 'keepForever',
'lastmodifyinguser': 'lastModifyingUser',
'lastmodifyingusername': 'lastModifyingUser.displayName',
'md5checksum': 'md5Checksum',
'mimetype': 'mimeType',
'modifieddate': 'modifiedTime',
'modifiedtime': 'modifiedTime',
'originalfilename': 'originalFilename',
'pinned': 'keepForever',
'published': 'published',
'publishauto': 'publishAuto',
'publishedoutsidedomain': 'publishedOutsideDomain',
'size': 'size',
}
FILEREVISIONS_TIME_OBJECTS = {'modifiedTime'}
def _showRevision(revision, i=0, count=0):
_getMain().printEntity([Ent.DRIVE_FILE_REVISION, revision['id']], i, count)
Ind.Increment()
_getMain().showJSON(None, revision, ['id'], timeObjects=FILEREVISIONS_TIME_OBJECTS)
Ind.Decrement()
DRIVE_REVISIONS_INDEXED_TITLES = ['revisions']
# gam <UserTypeEntity> show filerevisions <DriveFileEntity>
# [select <DriveFileRevisionIDEntity>]
# [previewdelete] [showtitles]
# [<RevisionsFieldName>*|(fields <RevisionsFieldNameList>)]
# (orderby <DriveFileOrderByFieldName> [ascending|descending])*
# [stripcrsfromname]
# gam <UserTypeEntity> print filerevisions <DriveFileEntity> [todrive <ToDriveAttribute>*]
# [select <DriveFileRevisionIDEntity>]
# [previewdelete] [showtitles] [oneitemperrow]
# [<RevisionsFieldName>*|(fields <RevisionsFieldNameList>)]
# (orderby <DriveFileOrderByFieldName> [ascending|descending])*
# [stripcrsfromname]
def printShowFileRevisions(users):
csvPF = _getMain().CSVPrintFile(['Owner', 'id']) if Act.csvFormat() else None
fieldsList = []
fileIdEntity = getDriveFileEntity()
revisionsEntity = None
oneItemPerRow = previewDelete = showTitles = stripCRsFromName = False
OBY = _getMain().OrderBy(DRIVEFILE_ORDERBY_CHOICE_MAP)
while Cmd.ArgumentsRemaining():
myarg = _getMain().getArgument()
if csvPF and myarg == 'todrive':
csvPF.GetTodriveParameters()
elif myarg == 'select':
revisionsEntity = getRevisionsEntity()
elif csvPF and myarg == 'oneitemperrow':
oneItemPerRow = True
if csvPF:
csvPF.AddTitles('revision.id')
elif myarg == 'orderby':
OBY.GetChoice()
elif myarg == 'previewdelete':
previewDelete = True
elif myarg == 'showtitles':
showTitles = True
if csvPF:
csvPF.AddTitles('name')
elif myarg == 'stripcrsfromname':
stripCRsFromName = True
elif _getMain().getFieldsList(myarg, FILEREVISIONS_FIELDS_CHOICE_MAP, fieldsList, initialField='id'):
pass
else:
_getMain().unknownArgumentExit()
if fieldsList:
fields = _getMain().getItemFieldsFromFieldsList('revisions', fieldsList)
else:
fields = '*'
i, count, users = _getMain().getEntityArgument(users)
for user in users:
i += 1
origUser = user
user, drive, jcount = _validateUserGetFileIDs(user, i, count, fileIdEntity,
entityType=[Ent.DRIVE_FILE_OR_FOLDER, None][csvPF is not None],
orderBy=OBY.orderBy)
if jcount == 0:
continue
Ind.Increment()
j = 0
for fileId in fileIdEntity['list']:
j += 1
fileName = fileId
entityType = Ent.DRIVE_FILE_OR_FOLDER_ID
if showTitles:
fileName, entityType, _ = _getDriveFileNameFromId(drive, fileId, not csvPF)
if stripCRsFromName:
fileName = _getMain()._stripControlCharsFromName(fileName)
try:
results = _getMain().callGAPIpages(drive.revisions(), 'list', 'revisions',
throwReasons=GAPI.DRIVE_ACCESS_THROW_REASONS+[GAPI.BAD_REQUEST, GAPI.REVISIONS_NOT_SUPPORTED],
fileId=fileId, fields=fields, pageSize=GC.Values[GC.DRIVE_MAX_RESULTS])
except (GAPI.fileNotFound, GAPI.forbidden, GAPI.internalError, GAPI.insufficientFilePermissions, GAPI.unknownError,
GAPI.badRequest, GAPI.revisionsNotSupported) as e:
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.DRIVE_FILE_OR_FOLDER_ID, fileId], str(e), j, jcount)
continue
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
break
if revisionsEntity:
results = _selectRevisionResults(results, fileId, origUser, revisionsEntity, previewDelete)
if not csvPF:
kcount = len(results)
_getMain().entityPerformActionNumItems([entityType, fileName], kcount, Ent.DRIVE_FILE_REVISION, j, jcount)
Ind.Increment()
k = 0
for revision in results:
k += 1
_showRevision(revision, k, kcount)
Ind.Decrement()
elif results:
if oneItemPerRow:
for revision in results:
row = {'Owner': user, 'id': fileId}
if showTitles:
row['name'] = fileName
csvPF.WriteRowTitles(_getMain().flattenJSON({'revision': revision}, flattened=row, timeObjects=FILEREVISIONS_TIME_OBJECTS))
else:
if showTitles:
csvPF.WriteRowTitles(_getMain().flattenJSON({'revisions': results}, flattened={'Owner': user, 'id': fileId, 'name': fileName}, timeObjects=FILEREVISIONS_TIME_OBJECTS))
else:
csvPF.WriteRowTitles(_getMain().flattenJSON({'revisions': results}, flattened={'Owner': user, 'id': fileId}, timeObjects=FILEREVISIONS_TIME_OBJECTS))
Ind.Decrement()
if csvPF:
if oneItemPerRow:
csvPF.SetSortTitles(['Owner', 'id', 'name', 'revision.id'])
else:
csvPF.SetSortTitles(['Owner', 'id', 'revisions'])
csvPF.SetIndexedTitles(DRIVE_REVISIONS_INDEXED_TITLES)
csvPF.writeCSVfile('Drive File Revisions')
def _stripMeInOwners(query):
if not query:
return query
query = query.replace(ME_IN_OWNERS_AND, '')
query = query.replace(_getMain().AND_ME_IN_OWNERS, '')
return query.replace(ME_IN_OWNERS, '').strip()
def _stripNotMeInOwners(query):
if not query:
return query
query = query.replace(NOT_ME_IN_OWNERS_AND, '')
query = query.replace(_getMain().AND_NOT_ME_IN_OWNERS, '')
return query.replace(NOT_ME_IN_OWNERS, '').strip()
def _updateAnyOwnerQuery(query):
query = _stripNotMeInOwners(query)
return _stripMeInOwners(query)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,30 @@
"""Transfer sub-package.
Re-exports all symbols from sub-modules for backward compatibility."""
from gam.cmd.drive.transfer.fileops import ( # noqa: F401
DOCUMENT_FORMATS_MAP,
GOOGLEDOC_VALID_EXTENSIONS_MAP,
HTTP_ERROR_PATTERN,
MICROSOFT_FORMATS_LIST,
MIMETYPE_EXTENSION_MAP,
NON_DOWNLOADABLE_MIMETYPES,
OPENOFFICE_FORMATS_LIST,
SUGGESTIONS_VIEW_MODE_CHOICE_MAP,
TRANSFER_DRIVEFILE_ACL_ROLES_MAP,
collectOrphans,
deleteDriveFile,
getDriveFile,
getGoogleDocument,
purgeDriveFile,
trashDriveFile,
untrashDriveFile,
updateGoogleDocument,
)
from gam.cmd.drive.transfer.ownership import ( # noqa: F401
claimOwnership,
getPermissionIdForEmail,
transferDrive,
transferOwnership,
validateUserGetPermissionId,
)

View File

@@ -0,0 +1,793 @@
"""Drive file delete, trash, download, and document operations.
Part of the transfer sub-package."""
"""File delete/trash/download/transfer/claim operations.
Part of the drive sub-package, extracted from drive.py."""
"""GAM Google Drive file, permission, shared drive, and label management."""
import re
import json
import sys
from gamlib import glaction
from gamlib import glapi as API
from gamlib import glcfg as GC
from gamlib import glclargs
from gamlib import glentity
from gamlib import glgapi as GAPI
from gamlib import glglobals as GM
from gamlib import glindent
from gamlib import glmsgs as Msg
Act = glaction.GamAction()
Ent = glentity.GamEntity()
Ind = glindent.GamIndent()
Cmd = glclargs.GamCLArgs()
APPLICATION_VND_GOOGLE_APPS = 'application/vnd.google-apps.'
MIMETYPE_GA_DOCUMENT = f'{APPLICATION_VND_GOOGLE_APPS}document'
MIMETYPE_GA_DRAWING = f'{APPLICATION_VND_GOOGLE_APPS}drawing'
MIMETYPE_GA_FILE = f'{APPLICATION_VND_GOOGLE_APPS}file'
MIMETYPE_GA_FOLDER = f'{APPLICATION_VND_GOOGLE_APPS}folder'
MIMETYPE_GA_FORM = f'{APPLICATION_VND_GOOGLE_APPS}form'
MIMETYPE_GA_FUSIONTABLE = f'{APPLICATION_VND_GOOGLE_APPS}fusiontable'
MIMETYPE_GA_JAM = f'{APPLICATION_VND_GOOGLE_APPS}jam'
MIMETYPE_GA_MAP = f'{APPLICATION_VND_GOOGLE_APPS}map'
MIMETYPE_GA_PRESENTATION = f'{APPLICATION_VND_GOOGLE_APPS}presentation'
MIMETYPE_GA_SCRIPT = f'{APPLICATION_VND_GOOGLE_APPS}script'
MIMETYPE_GA_SCRIPT_JSON = f'{APPLICATION_VND_GOOGLE_APPS}script+json'
MIMETYPE_GA_SHORTCUT = f'{APPLICATION_VND_GOOGLE_APPS}shortcut'
MIMETYPE_GA_3P_SHORTCUT = f'{APPLICATION_VND_GOOGLE_APPS}drive-sdk'
MIMETYPE_GA_SITE = f'{APPLICATION_VND_GOOGLE_APPS}site'
MIMETYPE_GA_SPREADSHEET = f'{APPLICATION_VND_GOOGLE_APPS}spreadsheet'
ME_IN_OWNERS = "'me' in owners"
ME_IN_OWNERS_AND = ME_IN_OWNERS + " and "
NOT_ME_IN_OWNERS = "not " + ME_IN_OWNERS
NOT_ME_IN_OWNERS_AND = NOT_ME_IN_OWNERS + " and "
WITH_ANY_FILE_NAME = "name = '{0}'"
WITH_MY_FILE_NAME = ME_IN_OWNERS_AND + WITH_ANY_FILE_NAME
WITH_OTHER_FILE_NAME = NOT_ME_IN_OWNERS_AND + WITH_ANY_FILE_NAME
ROOT = 'root'
ORPHANS = 'Orphans'
SHARED_WITHME = 'SharedWithMe'
SHARED_DRIVES = 'SharedDrives'
def _getMain():
return sys.modules['gam']
def __getattr__(name):
"""Fall back to gam module for any undefined names."""
main = _getMain()
try:
return getattr(main, name)
except AttributeError:
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
UNKNOWN = 'Unknown'
ORPHANS_COLLECTED_RC = 30
def deleteDriveFile(users, function=None):
fileIdEntity = getDriveFileEntity()
if not function:
function = _getMain().getChoice(DELETE_DRIVEFILE_CHOICE_MAP, defaultChoice='trash', mapChoice=True)
if _getMain().checkArgumentPresent('shortcutandtarget'):
shortcutAndTarget = _getMain().getBoolean()
else:
shortcutAndTarget = False
_getMain().checkForExtraneousArguments()
Act.Set(DELETE_DRIVEFILE_FUNCTION_TO_ACTION_MAP[function])
if function != 'delete':
trash_body = {'trashed': function == 'trash'}
i, count, users = _getMain().getEntityArgument(users)
for user in users:
i += 1
user, drive, jcount = _validateUserGetFileIDs(user, i, count, fileIdEntity, entityType=Ent.DRIVE_FILE_OR_FOLDER)
if jcount == 0:
continue
Ind.Increment()
j = 0
for fileId in fileIdEntity['list']:
j += 1
try:
fileInfoList = []
if shortcutAndTarget:
capability = DELETE_DRIVEFILE_FUNCTION_TO_CAPABILITY_MAP[function]
fileInfo = (fileId, Ent.DRIVE_FILE_OR_FOLDER, Ent.DRIVE_FILE_OR_FOLDER_ID)
result = _getMain().callGAPI(drive.files(), 'get',
throwReasons=GAPI.DRIVE_GET_THROW_REASONS,
fileId=fileId, fields=f'name,mimeType,shortcutDetails,capabilities({capability})', supportsAllDrives=True)
if result['mimeType'] == MIMETYPE_GA_SHORTCUT:
if not result['capabilities'][capability]:
_getMain().entityActionNotPerformedWarning([Ent.USER, user, Ent.DRIVE_SHORTCUT, result['name']],
Msg.SHORTCUT_TARGET_CAPABILITY_IS_FALSE.format('Shortcut', capability), j, jcount)
continue
fileInfoList.append((fileId, Ent.DRIVE_SHORTCUT, Ent.DRIVE_SHORTCUT_ID))
fileId = result['shortcutDetails']['targetId']
fileInfo = (fileId, Ent.DRIVE_FILE_OR_FOLDER, Ent.DRIVE_FILE_OR_FOLDER_ID)
tresult = _getMain().callGAPI(drive.files(), 'get',
throwReasons=GAPI.DRIVE_GET_THROW_REASONS,
fileId=fileId, fields=f'name,capabilities({capability})', supportsAllDrives=True)
if not tresult['capabilities'][capability]:
_getMain().entityActionNotPerformedWarning([Ent.USER, user, Ent.DRIVE_SHORTCUT, result['name'], Ent.DRIVE_FILE_OR_FOLDER, tresult['name']],
Msg.SHORTCUT_TARGET_CAPABILITY_IS_FALSE.format('Target', capability), j, jcount)
continue
fileInfoList.append((fileId, Ent.DRIVE_FILE_OR_FOLDER, Ent.DRIVE_FILE_OR_FOLDER_ID))
for fileInfo in fileInfoList:
fileId = fileInfo[0]
if function != 'delete':
result = _getMain().callGAPI(drive.files(), 'update',
throwReasons=GAPI.DRIVE_ACCESS_THROW_REASONS+[GAPI.FILE_NEVER_WRITABLE],
fileId=fileId, body=trash_body, fields='name', supportsAllDrives=True)
if result and 'name' in result:
fileName = result['name']
else:
fileName = fileId
else:
_getMain().callGAPI(drive.files(), function,
throwReasons=GAPI.DRIVE_ACCESS_THROW_REASONS+[GAPI.FILE_NEVER_WRITABLE],
fileId=fileId, supportsAllDrives=True)
fileName = fileId
_getMain().entityActionPerformed([Ent.USER, user, fileInfo[1], fileName], j, jcount)
except (GAPI.fileNotFound, GAPI.forbidden, GAPI.internalError, GAPI.insufficientFilePermissions, GAPI.unknownError, GAPI.fileNeverWritable) as e:
_getMain().entityActionFailedWarning([Ent.USER, user, fileInfo[2], fileId], str(e), j, jcount)
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
break
Ind.Decrement()
# gam <UserTypeEntity> purge drivefile <DriveFileEntity> [shortcutandtarget [<Boolean>]]
def purgeDriveFile(users):
deleteDriveFile(users, 'delete')
# gam <UserTypeEntity> trash drivefile <DriveFileEntity> [shortcutandtarget [<Boolean>]]
def trashDriveFile(users):
deleteDriveFile(users, 'trash')
# gam <UserTypeEntity> untrash drivefile <DriveFileEntity> [shortcutandtarget [<Boolean>]]
def untrashDriveFile(users):
deleteDriveFile(users, 'untrash')
NON_DOWNLOADABLE_MIMETYPES = [MIMETYPE_GA_FORM, MIMETYPE_GA_FUSIONTABLE, MIMETYPE_GA_MAP, MIMETYPE_GA_FOLDER, MIMETYPE_GA_SHORTCUT]
GOOGLEDOC_VALID_EXTENSIONS_MAP = {
MIMETYPE_GA_DRAWING: ['.jpeg', '.jpg', '.pdf', '.png', '.svg'],
MIMETYPE_GA_DOCUMENT: ['.docx', '.epub', '.html', '.odt', '.pdf', '.rtf', '.txt', '.zip'],
MIMETYPE_GA_JAM: ['.pdf'],
MIMETYPE_GA_PRESENTATION: ['.pdf', '.pptx', '.odp', '.txt'],
MIMETYPE_GA_SCRIPT: ['.json'],
MIMETYPE_GA_SPREADSHEET: ['.csv', '.ods', '.pdf', '.tsv', '.xlsx', '.zip'],
}
MICROSOFT_FORMATS_LIST = [
{'mime': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'ext': '.docx'},
{'mime': 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', 'ext': '.dotx'},
{'mime': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'ext': '.pptx'},
{'mime': 'application/vnd.openxmlformats-officedocument.presentationml.template', 'ext': '.potx'},
{'mime': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'ext': '.xlsx'},
{'mime': 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', 'ext': '.xltx'},
{'mime': 'application/msword', 'ext': '.doc'},
{'mime': 'application/msword', 'ext': '.dot'},
{'mime': 'application/vnd.ms-powerpoint', 'ext': '.ppt'},
{'mime': 'application/vnd.ms-powerpoint', 'ext': '.pot'},
{'mime': 'application/vnd.ms-excel', 'ext': '.xls'},
{'mime': 'application/vnd.ms-excel', 'ext': '.xlt'},
]
OPENOFFICE_FORMATS_LIST = [
{'mime': 'application/vnd.oasis.opendocument.presentation', 'ext': '.odp'},
{'mime': 'application/x-vnd.oasis.opendocument.spreadsheet', 'ext': '.ods'},
{'mime': 'application/vnd.oasis.opendocument.spreadsheet', 'ext': '.ods'},
{'mime': 'application/vnd.oasis.opendocument.text', 'ext': '.odt'},
]
DOCUMENT_FORMATS_MAP = {
'csv': [{'mime': 'text/csv', 'ext': '.csv'}],
'doc': [{'mime': 'application/msword', 'ext': '.doc'}],
'dot': [{'mime': 'application/msword', 'ext': '.dot'}],
'docx': [{'mime': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'ext': '.docx'}],
'dotx': [{'mime': 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', 'ext': '.dotx'}],
'epub': [{'mime': 'application/epub+zip', 'ext': '.epub'}],
'html': [{'mime': 'text/html', 'ext': '.html'}],
'jpeg': [{'mime': 'image/jpeg', 'ext': '.jpeg'}],
'jpg': [{'mime': 'image/jpeg', 'ext': '.jpg'}],
'json': [{'mime': MIMETYPE_GA_SCRIPT_JSON, 'ext': '.json'}],
'mht': [{'mime': 'message/rfc822', 'ext': 'mht'}],
'odp': [{'mime': 'application/vnd.oasis.opendocument.presentation', 'ext': '.odp'}],
'ods': [{'mime': 'application/x-vnd.oasis.opendocument.spreadsheet', 'ext': '.ods'},
{'mime': 'application/vnd.oasis.opendocument.spreadsheet', 'ext': '.ods'}],
'odt': [{'mime': 'application/vnd.oasis.opendocument.text', 'ext': '.odt'}],
'pdf': [{'mime': 'application/pdf', 'ext': '.pdf'}],
'png': [{'mime': 'image/png', 'ext': '.png'}],
'ppt': [{'mime': 'application/vnd.ms-powerpoint', 'ext': '.ppt'}],
'pot': [{'mime': 'application/vnd.ms-powerpoint', 'ext': '.pot'}],
'potx': [{'mime': 'application/vnd.openxmlformats-officedocument.presentationml.template', 'ext': '.potx'}],
'pptx': [{'mime': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'ext': '.pptx'}],
'rtf': [{'mime': 'application/rtf', 'ext': '.rtf'}],
'svg': [{'mime': 'image/svg+xml', 'ext': '.svg'}],
'tsv': [{'mime': 'text/tab-separated-values', 'ext': '.tsv'},
{'mime': 'text/tsv', 'ext': '.tsv'}],
'txt': [{'mime': 'text/plain', 'ext': '.txt'}],
'xls': [{'mime': 'application/vnd.ms-excel', 'ext': '.xls'}],
'xlt': [{'mime': 'application/vnd.ms-excel', 'ext': '.xlt'}],
'xlsx': [{'mime': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'ext': '.xlsx'}],
'xltx': [{'mime': 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', 'ext': '.xltx'}],
'zip': [{'mime': 'application/zip', 'ext': '.zip'}],
}
MIMETYPE_EXTENSION_MAP = {
'application/epub+zip': '.epub',
'application/msword': '.doc',
'application/octet-stream': '',
'application/pdf': '.pdf',
'application/rtf': '.rtf',
'application/vnd.ms-excel': '.xls',
'application/vnd.ms-powerpoint': '.ppt',
'application/vnd.oasis.opendocument.presentation': '.odp',
'application/vnd.oasis.opendocument.spreadsheet': '.ods',
'application/vnd.oasis.opendocument.text': '.odt',
'application/vnd.openxmlformats-officedocument.presentationml.presentation': '.pptx',
'application/vnd.openxmlformats-officedocument.presentationml.template': '.potx',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': '.xlsx',
'application/vnd.openxmlformats-officedocument.spreadsheetml.template': '.xltx',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': '.docx',
'application/vnd.openxmlformats-officedocument.wordprocessingml.template': '.dotx',
'application/x-vnd.oasis.opendocument.spreadsheet': '.ods',
'application/zip': '.zip',
'image/gif': '.gif',
'image/jpeg': '.jpg',
'image/jpg': '.jpg',
'image/png': '.png',
'image/svg+xml': '.svg',
'image/webp': '.webp',
'message/rfc822': 'mht',
'text/csv': '.csv',
'text/html': '.html',
'text/plain': '.txt',
'text/rtf': '.rtf',
'text/tab-separated-values': '.tsv',
'text/tsv': '.tsv',
}
HTTP_ERROR_PATTERN = re.compile(r'^.*returned "(.*)">$')
# gam <UserTypeEntity> get drivefile <DriveFileEntity> [revision <DriveFileRevisionID>]
# [(format <FileFormatList>)|(gsheet|csvsheet <SheetEntity>)] [exportsheetaspdf <String>]
# [targetfolder <FilePath>] [targetname -|<FileName>]
# [donotfollowshortcuts [<Boolean>]] [overwrite [<Boolean>]] [showprogress [<Boolean>]]
# [acknowledgeabuse [<Boolean>]]
def getDriveFile(users):
def closeRemoveTargetFile(f):
if f and not targetStdout:
_getMain().closeFile(f)
os.remove(filename)
fileIdEntity = getDriveFileEntity()
sheetEntity = None
exportSheetAsPDF = revisionId = ''
exportFormatName = 'openoffice'
exportFormatChoices = [exportFormatName]
exportFormats = OPENOFFICE_FORMATS_LIST
defaultFormats = True
targetFolderPattern = GC.Values[GC.DRIVE_DIR]
targetNamePattern = None
acknowledgeAbuse = donotFollowShortcuts = overwrite = showProgress = suppressStdoutMsgs = targetStdout = False
while Cmd.ArgumentsRemaining():
myarg = _getMain().getArgument()
if myarg == 'format':
exportFormatChoices = _getMain().getString(Cmd.OB_FORMAT_LIST).replace(',', ' ').lower().split()
exportFormats = []
for exportFormat in exportFormatChoices:
if exportFormat in {'ms', 'microsoft', 'micro$oft'}:
exportFormats.extend(MICROSOFT_FORMATS_LIST)
elif exportFormat == 'openoffice':
exportFormats.extend(OPENOFFICE_FORMATS_LIST)
elif exportFormat in DOCUMENT_FORMATS_MAP:
exportFormats.extend(DOCUMENT_FORMATS_MAP[exportFormat])
else:
_getMain().invalidChoiceExit(exportFormat, DOCUMENT_FORMATS_MAP, True)
defaultFormats = False
elif myarg == 'targetfolder':
targetFolderPattern = _getMain().setFilePath(_getMain().getString(Cmd.OB_FILE_PATH), GC.DRIVE_DIR)
elif myarg == 'targetname':
targetNamePattern = _getMain().getString(Cmd.OB_FILE_NAME)
targetStdout = targetNamePattern == '-'
suppressStdoutMsgs = False if not targetStdout else GM.Globals[GM.STDOUT][GM.REDIRECT_STD]
elif myarg == 'donotfollowshortcuts':
donotFollowShortcuts = _getMain().getBoolean()
elif myarg == 'overwrite':
overwrite = _getMain().getBoolean()
elif myarg == 'revision':
revisionId = _getMain().getString(Cmd.OB_DRIVE_FILE_REVISION_ID)
elif myarg in {'gsheet', 'csvsheet'}:
sheetEntity = _getMain().getSheetEntity(False)
elif myarg == 'exportsheetaspdf':
exportSheetAsPDF = _getMain().getString(Cmd.OB_STRING, minLen=0)
elif myarg == 'nocache':
pass
elif myarg == 'showprogress':
showProgress = _getMain().getBoolean()
elif myarg == 'acknowledgeabuse':
acknowledgeAbuse = _getMain().getBoolean()
else:
_getMain().unknownArgumentExit()
if exportSheetAsPDF:
exportFormatName = 'pdf'
exportFormatChoices = [exportFormatName]
exportFormats = DOCUMENT_FORMATS_MAP[exportFormatName]
elif sheetEntity:
if defaultFormats:
exportFormatName = 'csv'
else:
exportFormatName = exportFormats[0]['ext'][1:]
exportFormatChoices = [exportFormatName]
exportFormats = DOCUMENT_FORMATS_MAP[exportFormatName]
i, count, users = _getMain().getEntityArgument(users)
for user in users:
i += 1
user, drive, jcount = _validateUserGetFileIDs(user, i, count, fileIdEntity, entityType=Ent.DRIVE_FILE if not suppressStdoutMsgs else None)
if jcount == 0:
continue
_, userName, _ = _getMain().splitEmailAddressOrUID(user)
if exportSheetAsPDF or sheetEntity:
_, sheet = _getMain().buildGAPIServiceObject(API.SHEETS, user, i, count)
if not sheet:
continue
targetFolder = _getMain()._substituteForUser(targetFolderPattern, user, userName)
if not os.path.isdir(targetFolder):
os.makedirs(targetFolder)
targetName = _getMain()._substituteForUser(targetNamePattern, user, userName) if targetNamePattern else None
Ind.Increment()
j = 0
for fileId in fileIdEntity['list']:
j += 1
fileExtension = None
try:
result = _getMain().callGAPI(drive.files(), 'get',
throwReasons=GAPI.DRIVE_GET_THROW_REASONS,
fileId=fileId, fields='name,fullFileExtension,mimeType,quotaBytesUsed,shortcutDetails', supportsAllDrives=True)
mimeType = result['mimeType']
if (mimeType == MIMETYPE_GA_SHORTCUT) and not donotFollowShortcuts:
fileId = result['shortcutDetails']['targetId']
result = _getMain().callGAPI(drive.files(), 'get',
throwReasons=GAPI.DRIVE_GET_THROW_REASONS,
fileId=fileId, fields='name,fullFileExtension,mimeType,size', supportsAllDrives=True)
mimeType = result['mimeType']
entityValueList = [Ent.USER, user, _getMain()._getEntityMimeType(result), result['name']]
if mimeType in NON_DOWNLOADABLE_MIMETYPES:
_getMain().entityActionNotPerformedWarning(entityValueList, Msg.FORMAT_NOT_DOWNLOADABLE, j, jcount)
continue
if revisionId:
_getMain().callGAPI(drive.revisions(), 'get',
throwReasons=GAPI.DRIVE_GET_THROW_REASONS+[GAPI.REVISION_NOT_FOUND],
fileId=fileId, revisionId=revisionId, fields='id')
fileExtension = result.get('fullFileExtension')
googleDocExtensions = GOOGLEDOC_VALID_EXTENSIONS_MAP.get(mimeType)
if googleDocExtensions:
my_line = ['Type', 'Google Doc']
googleDoc = True
for exportFormat in exportFormats:
if exportFormat['ext'] in googleDocExtensions:
exportMimeType = exportFormat['mime']
if fileExtension:
extension = '.'+fileExtension
else:
extension = exportFormat['ext']
break
else:
_getMain().entityActionNotPerformedWarning(entityValueList, Msg.FORMAT_NOT_AVAILABLE.format(','.join(exportFormatChoices)), j, jcount)
continue
else:
if 'quotaBytesUsed' in result:
my_line = ['Size', formatFileSize(int(result['quotaBytesUsed']))]
else:
my_line = ['Size', UNKNOWN]
googleDoc = False
if fileExtension:
extension = '.'+fileExtension
else:
extension = MIMETYPE_EXTENSION_MAP.get(mimeType, '')
while True:
if targetStdout:
filename = 'stdout'
else:
filename, _ = _getMain().uniqueFilename(targetFolder, targetName or _getMain().cleanFilename(result['name']), overwrite, extension)
spreadsheetUrl = None
f = None
try:
if googleDoc:
if (not exportSheetAsPDF and not sheetEntity) or mimeType != MIMETYPE_GA_SPREADSHEET:
request = drive.files().export_media(fileId=fileId, mimeType=exportMimeType)
if revisionId:
request.uri = f'{request.uri}&revision={revisionId}'
else:
spreadsheet = _getMain().callGAPI(sheet.spreadsheets(), 'get',
throwReasons=GAPI.SHEETS_ACCESS_THROW_REASONS,
spreadsheetId=fileId, fields='spreadsheetUrl,sheets(properties(sheetId,title))')
# spreadsheetUrl = f'{re.sub("/edit.*$", "/export", spreadsheet["spreadsheetUrl"])}?exportFormat={exportFormatName}&format={exportFormatName}&id={fileId}'
spreadsheetUrl = f'{re.sub("/edit.*$", "/export", spreadsheet["spreadsheetUrl"])}?format={exportFormatName}&id={fileId}'
if sheetEntity:
entityValueList.extend([sheetEntity['sheetType'], sheetEntity['sheetValue']])
sheetId = _getMain().getSheetIdFromSheetEntity(spreadsheet, sheetEntity)
if sheetId is None:
_getMain().entityActionNotPerformedWarning(entityValueList, Msg.NOT_FOUND, j, jcount)
break
spreadsheetUrl += f'&gid={sheetId}'
spreadsheetUrl += exportSheetAsPDF
else:
if revisionId:
entityValueList.extend([Ent.DRIVE_FILE_REVISION, revisionId])
request = drive.revisions().get_media(fileId=fileId, revisionId=revisionId)
else:
request = drive.files().get_media(fileId=fileId, acknowledgeAbuse=acknowledgeAbuse)
if not targetStdout:
f = open(filename, 'wb')
else:
f = os.fdopen(os.dup(sys.stdout.fileno()), 'wb')
if not spreadsheetUrl:
downloader = googleapiclient.http.MediaIoBaseDownload(f, request)
done = False
while not done:
status, done = downloader.next_chunk()
if showProgress and not suppressStdoutMsgs and status.progress() < 1.0:
_getMain().entityActionPerformedMessage(entityValueList, f'{status.progress():>7.2%}', j, jcount)
else:
if GC.Values[GC.DEBUG_LEVEL] > 0:
sys.stderr.write(f'Debug: spreadsheetUrl: {spreadsheetUrl}\n')
maxRetries = 10
sleepTime = 5
for retry in range(1, maxRetries+1):
status, content = drive._http.request(uri=spreadsheetUrl, method='GET')
if status['status'] != '429':
break
_getMain().writeStderr(Msg.RETRYING_GOOGLE_SHEET_EXPORT_SLEEPING.format(retry, maxRetries, sleepTime))
time.sleep(sleepTime)
if status['status'] == '200':
f.write(content)
if targetStdout and content[-1] != '\n':
f.write(bytes('\n', _getMain().UTF8))
else:
_getMain().entityModifierNewValueActionFailedWarning(entityValueList, Act.MODIFIER_TO, filename, f'HTTP Error: {status["status"]}', j, jcount)
closeRemoveTargetFile(f)
break
if not targetStdout:
_getMain().closeFile(f)
if not suppressStdoutMsgs:
_getMain().entityModifierNewValueKeyValueActionPerformed(entityValueList, Act.MODIFIER_TO, filename, my_line[0], my_line[1], j, jcount)
break
except (IOError, httplib2.HttpLib2Error) as e:
_getMain().entityModifierNewValueActionFailedWarning(entityValueList, Act.MODIFIER_TO, filename, str(e), j, jcount)
except googleapiclient.http.HttpError as e:
mg = HTTP_ERROR_PATTERN.match(str(e))
if mg:
_getMain().entityModifierNewValueActionFailedWarning(entityValueList, Act.MODIFIER_TO, filename, mg.group(1), j, jcount)
else:
_getMain().entityModifierNewValueActionFailedWarning(entityValueList, Act.MODIFIER_TO, filename, str(e), j, jcount)
closeRemoveTargetFile(f)
break
except GAPI.fileNotFound:
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.DRIVE_FILE_OR_FOLDER_ID, fileId], Msg.DOES_NOT_EXIST, j, jcount)
except GAPI.revisionNotFound:
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.DRIVE_FILE_OR_FOLDER_ID, fileId, Ent.DRIVE_FILE_REVISION, revisionId], Msg.DOES_NOT_EXIST, j, jcount)
except (GAPI.notFound, GAPI.forbidden, GAPI.permissionDenied,
GAPI.internalError, GAPI.insufficientFilePermissions, GAPI.badRequest,
GAPI.invalid, GAPI.invalidArgument, GAPI.failedPrecondition) as e:
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.SPREADSHEET, fileId], str(e), j, jcount)
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
break
Ind.Decrement()
SUGGESTIONS_VIEW_MODE_CHOICE_MAP = {
'default': 'DEFAULT_FOR_CURRENT_ACCESS',
'suggestionsinline': 'SUGGESTIONS_INLINE',
'previewsuggestionsaccepted': 'PREVIEW_SUGGESTIONS_ACCEPTED',
'previewwithoutsuggestions': 'PREVIEW_WITHOUT_SUGGESTIONS'
}
# gam <UserTypeEntity> get document <DriveFileEntity>
# [viewmode default|suggestions_inline|preview_suggestions_accepted|preview_without_suggestions]
# [targetfolder <FilePath>] [targetname <FileName>]
# [donotfollowshortcuts [<Boolean>]] [overwrite [<Boolean>]]
def getGoogleDocument(users):
fileIdEntity = getDriveFileEntity()
suggestionsViewMode = SUGGESTIONS_VIEW_MODE_CHOICE_MAP['default']
targetFolderPattern = GC.Values[GC.DRIVE_DIR]
targetNamePattern = None
donotFollowShortcuts = overwrite = False
while Cmd.ArgumentsRemaining():
myarg = _getMain().getArgument()
if myarg == 'viewmode':
suggestionsViewMode = _getMain().getChoice(SUGGESTIONS_VIEW_MODE_CHOICE_MAP, mapChoice=True)
elif myarg == 'targetfolder':
targetFolderPattern = _getMain().setFilePath(_getMain().getString(Cmd.OB_FILE_PATH), GC.DRIVE_DIR)
elif myarg == 'targetname':
targetNamePattern = _getMain().getString(Cmd.OB_FILE_NAME)
elif myarg == 'donotfollowshortcuts':
donotFollowShortcuts = _getMain().getBoolean()
elif myarg == 'overwrite':
overwrite = _getMain().getBoolean()
else:
_getMain().unknownArgumentExit()
i, count, users = _getMain().getEntityArgument(users)
for user in users:
i += 1
user, drive, jcount = _validateUserGetFileIDs(user, i, count, fileIdEntity, entityType=Ent.DOCUMENT)
if jcount == 0:
continue
_, docs = _getMain().buildGAPIServiceObject(API.DOCS, user, i, count)
if not docs:
continue
_, userName, _ = _getMain().splitEmailAddressOrUID(user)
targetFolder = _getMain()._substituteForUser(targetFolderPattern, user, userName)
if not os.path.isdir(targetFolder):
os.makedirs(targetFolder)
targetName = _getMain()._substituteForUser(targetNamePattern, user, userName) if targetNamePattern else None
Ind.Increment()
j = 0
for fileId in fileIdEntity['list']:
j += 1
try:
result = _getMain().callGAPI(drive.files(), 'get',
throwReasons=GAPI.DRIVE_GET_THROW_REASONS,
fileId=fileId, fields='name,mimeType,shortcutDetails', supportsAllDrives=True)
mimeType = result['mimeType']
if (mimeType == MIMETYPE_GA_SHORTCUT) and not donotFollowShortcuts:
fileId = result['shortcutDetails']['targetId']
result = _getMain().callGAPI(drive.files(), 'get',
throwReasons=GAPI.DRIVE_GET_THROW_REASONS,
fileId=fileId, fields='name,mimeType', supportsAllDrives=True)
mimeType = result['mimeType']
docName = result['name']
if mimeType != MIMETYPE_GA_DOCUMENT:
_getMain().entityActionNotPerformedWarning([Ent.USER, user, Ent.DRIVE_FILE, docName],
Msg.INVALID_MIMETYPE.format(mimeType, MIMETYPE_GA_DOCUMENT), j, jcount)
continue
filename, _ = _getMain().uniqueFilename(targetFolder, targetName or _getMain().cleanFilename(docName), overwrite)
result = _getMain().callGAPI(docs.documents(), 'get',
throwReasons=GAPI.DRIVE_GET_THROW_REASONS,
documentId=fileId, suggestionsViewMode=suggestionsViewMode)
if _getMain().writeFile(filename, json.dumps(result, indent=2, sort_keys=True)+'\n', continueOnError=True):
_getMain().entityModifierNewValueActionPerformed([Ent.USER, user, Ent.DOCUMENT, f'{docName}({fileId})'], Act.MODIFIER_TO, filename, j, jcount)
except GAPI.fileNotFound:
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.DOCUMENT, fileId], Msg.DOES_NOT_EXIST, j, jcount)
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
break
Ind.Decrement()
# gam <UserTypeEntity> update docuument <DriveFileEntity>
# ((json [charset <Charset>] <SpreadsheetJSONUpdateRequest>) |
# (json file <FileName> [charset <Charset>]))
# [formatjson]
def updateGoogleDocument(users):
fileIdEntity = getDriveFileEntity()
body = {}
FJQC = _getMain().FormatJSONQuoteChar()
while Cmd.ArgumentsRemaining():
myarg = _getMain().getArgument()
if myarg == 'json':
body = _getMain().getJSON([])
else:
FJQC.GetFormatJSON(myarg)
i, count, users = _getMain().getEntityArgument(users)
for user in users:
i += 1
user, _, jcount = _validateUserGetFileIDs(user, i, count, fileIdEntity, entityType=Ent.DOCUMENT if not FJQC.formatJSON else None)
if jcount == 0:
continue
_, docs = _getMain().buildGAPIServiceObject(API.DOCS, user, i, count)
if not docs:
continue
Ind.Increment()
j = 0
for documentId in fileIdEntity['list']:
j += 1
try:
result = _getMain().callGAPI(docs.documents(), 'batchUpdate',
throwReasons=GAPI.DOCS_ACCESS_THROW_REASONS,
documentId=documentId, body=body)
if FJQC.formatJSON:
_getMain().printLine('{'+f'"User": "{user}", "documentId": "{documentId}", "JSON": {json.dumps(result, ensure_ascii=False, sort_keys=False)}'+'}')
continue
_getMain().entityActionPerformed([Ent.USER, user, Ent.DOCUMENT, documentId], j, jcount)
Ind.Increment()
for field in ['replies', 'writeControl']:
if field in result:
_getMain().showJSON(field, result[field])
Ind.Decrement()
except (GAPI.fileNotFound, GAPI.forbidden, GAPI.permissionDenied,
GAPI.internalError, GAPI.insufficientFilePermissions, GAPI.badRequest,
GAPI.invalid, GAPI.invalidArgument, GAPI.failedPrecondition) as e:
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.DOCUMENT, documentId], str(e), j, jcount)
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
break
Ind.Decrement()
# gam <UserTypeEntity> collect orphans
# [(targetuserfoldername <DriveFolderName>)(targetuserfolderid <DriveFolderID>)]
# [useshortcuts [<Boolean>]]
# (orderby <DriveFileOrderByFieldName> [ascending|descending])*
# [preview] [todrive <ToDriveAttribute>*]
def collectOrphans(users):
OBY = _getMain().OrderBy(DRIVEFILE_ORDERBY_CHOICE_MAP)
csvPF = None
targetParms = initDriveFileAttributes()
targetUserFolderId = None
targetUserFolderPattern = '#user# orphaned files'
targetParentBody = {}
query = ME_IN_OWNERS_AND+'trashed = false'
useShortcuts = False
while Cmd.ArgumentsRemaining():
myarg = _getMain().getArgument()
if myarg == 'orderby':
OBY.GetChoice()
elif myarg == 'targetuserfoldername':
targetUserFolderPattern = _getMain().getString(Cmd.OB_DRIVE_FOLDER_NAME)
targetUserFolderId = None
elif myarg == 'targetuserfolderid':
targetUserFolderId = _getMain().getString(Cmd.OB_DRIVE_FOLDER_ID)
targetUserFolderPattern = None
elif myarg == 'useshortcuts':
useShortcuts = _getMain().getBoolean()
elif myarg == 'preview':
csvPF = _getMain().CSVPrintFile(['Owner', 'type', 'id', 'name', 'action'])
elif csvPF and myarg == 'todrive':
csvPF.GetTodriveParameters()
else:
_getMain().unknownArgumentExit()
i, count, users = _getMain().getEntityArgument(users)
for user in users:
i += 1
user, drive = _getMain().buildGAPIServiceObject(API.DRIVE3, user, i, count)
if not drive:
continue
userName, _ = _getMain().splitEmailAddress(user)
try:
_getMain().printGettingAllEntityItemsForWhom(Ent.DRIVE_FILE_OR_FOLDER, Ent.TypeName(Ent.USER, user), i, count, query=query)
feed = _getMain().callGAPIpages(drive.files(), 'list', 'files',
pageMessage=_getMain().getPageMessageForWhom(),
throwReasons=GAPI.DRIVE_USER_THROW_REASONS,
retryReasons=[GAPI.UNKNOWN_ERROR],
q=query, orderBy=OBY.orderBy,
fields='nextPageToken,files(id,name,parents,mimeType,sharedWithMeTime,capabilities(canMoveItemWithinDrive))',
pageSize=GC.Values[GC.DRIVE_MAX_RESULTS])
if targetUserFolderPattern:
trgtUserFolderName = _getMain()._substituteForUser(targetUserFolderPattern, user, userName)
targetParms[DFA_PARENTQUERY] = _getMain().MY_NON_TRASHED_FOLDER_NAME.format(escapeDriveFileName(trgtUserFolderName))
else:
targetParms[DFA_PARENTID] = targetUserFolderId
trgtUserFolderName = targetUserFolderId
if not _getDriveFileParentInfo(drive, user, i, count, targetParentBody, targetParms, True, False):
continue
orphanDriveFiles = []
for fileEntry in feed:
if not fileEntry.get('parents') and 'sharedWithMeTime' not in fileEntry:
orphanDriveFiles.append(fileEntry)
jcount = len(orphanDriveFiles)
_getMain().entityPerformActionNumItemsModifier([Ent.USER, user], jcount, Ent.DRIVE_ORPHAN_FILE_OR_FOLDER,
f'{Act.MODIFIER_INTO} {Ent.Singular(Ent.DRIVE_FOLDER)}: {trgtUserFolderName}', i, count)
if jcount == 0:
continue
if not csvPF:
if 'parents' not in targetParentBody or not targetParentBody['parents']:
try:
newParentId = _getMain().callGAPI(drive.files(), 'create',
throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.FORBIDDEN, GAPI.INSUFFICIENT_PERMISSIONS, GAPI.INSUFFICIENT_PARENT_PERMISSIONS,
GAPI.UNKNOWN_ERROR, GAPI.STORAGE_QUOTA_EXCEEDED,
GAPI. TEAMDRIVE_FILE_LIMIT_EXCEEDED, GAPI.TEAMDRIVE_HIERARCHY_TOO_DEEP],
body={'name': trgtUserFolderName, 'mimeType': MIMETYPE_GA_FOLDER}, fields='id')['id']
except (GAPI.forbidden, GAPI.insufficientPermissions, GAPI.insufficientParentPermissions,
GAPI.unknownError, GAPI.storageQuotaExceeded, GAPI.teamDriveFileLimitExceeded, GAPI.teamDriveHierarchyTooDeep) as e:
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.DRIVE_FOLDER, trgtUserFolderName], str(e), i, count)
continue
else:
newParentId = targetParentBody['parents'][0]
_getMain().setSysExitRC(ORPHANS_COLLECTED_RC)
Ind.Increment()
j = 0
for fileEntry in orphanDriveFiles:
j += 1
fileId = fileEntry['id']
fileName = fileEntry['name']
fileType = _getMain()._getEntityMimeType(fileEntry)
# Deleted 6.26.16
# if fileType == Ent.DRIVE_FOLDER and not fileEntry['capabilities']['canAddMyDriveParent']:
# # Typically Google Backup & Sync images of laptops
# continue
if not useShortcuts and fileEntry['capabilities']['canMoveItemWithinDrive']:
if csvPF:
csvPF.WriteRow({'Owner': user, 'type': Ent.Singular(fileType), 'id': fileId, 'name': fileName, 'action': 'changeParent'})
continue
try:
_getMain().callGAPI(drive.files(), 'update',
bailOnInternalError=True,
throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.BAD_REQUEST, GAPI.FILE_NOT_FOUND,
GAPI.INTERNAL_ERROR, GAPI.INSUFFICIENT_PARENT_PERMISSIONS],
retryReasons=[GAPI.FILE_NOT_FOUND],
fileId=fileId, body={}, addParents=newParentId, fields='')
_getMain().entityModifierNewValueItemValueListActionPerformed([Ent.USER, user, fileType, fileName],
Act.MODIFIER_INTO, None, [Ent.DRIVE_FOLDER, trgtUserFolderName], j, jcount)
except (GAPI.badRequest, GAPI.fileNotFound, GAPI.internalError, GAPI.insufficientParentPermissions,) as e:
_getMain().entityActionFailedWarning([Ent.USER, user, fileType, fileName], str(e), j, jcount)
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
break
else:
if csvPF:
csvPF.WriteRow({'Owner': user, 'type': Ent.Singular(fileType), 'id': fileId, 'name': fileName, 'action': 'createShortcut'})
continue
try:
# Check for existing shortcut, do not duplicate
files = _getMain().callGAPIitems(drive.files(), 'list', 'files',
throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.INVALID_QUERY, GAPI.INVALID],
q=f"'me' in owners and name = '{escapeDriveFileName(fileName)}' and mimeType = '{MIMETYPE_GA_SHORTCUT}' and '{newParentId}' in parents and trashed = false",
fields='files(id,shortcutDetails(targetId))')
existingShortcut = False
for f_file in files:
if f_file['shortcutDetails']['targetId'] == fileId:
_getMain().entityActionNotPerformedWarning([Ent.USER, user, fileType, fileName, Ent.DRIVE_FILE_SHORTCUT, f"{fileName}({f_file['id']})"],
Msg.ALREADY_EXISTS_IN_TARGET_FOLDER.format(Ent.Singular(Ent.DRIVE_FOLDER), trgtUserFolderName), j, jcount)
existingShortcut = True
break
if existingShortcut:
continue
body = {'name': fileName, 'mimeType': MIMETYPE_GA_SHORTCUT, 'parents': [newParentId], 'shortcutDetails': {'targetId': fileId}}
result = _getMain().callGAPI(drive.files(), 'create',
throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.INVALID_QUERY, 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.TEAMDRIVE_FILE_LIMIT_EXCEEDED, GAPI.TEAMDRIVE_HIERARCHY_TOO_DEEP,
GAPI.SHORTCUT_TARGET_INVALID],
body=body, fields='id,name', supportsAllDrives=True)
_getMain().entityModifierNewValueItemValueListActionPerformed([Ent.USER, user, fileType, fileName, Ent.DRIVE_FILE_SHORTCUT, f'{result["name"]}({result["id"]})'],
Act.MODIFIER_INTO, None, [Ent.DRIVE_FOLDER, trgtUserFolderName], j, jcount)
except GAPI.invalidQuery:
_getMain().entityActionFailedWarning([Ent.USER, user, fileType, fileName], _getMain().invalidQuery(query), j, jcount)
except (GAPI.forbidden, GAPI.insufficientFilePermissions, GAPI.insufficientParentPermissions, GAPI.invalid, GAPI.badRequest,
GAPI.fileNotFound, GAPI.unknownError, GAPI.storageQuotaExceeded, GAPI.teamDriveFileLimitExceeded, GAPI.teamDriveHierarchyTooDeep,
GAPI.shortcutTargetInvalid) as e:
_getMain().entityActionFailedWarning([Ent.USER, user, fileType, fileName, Ent.DRIVE_FILE_SHORTCUT, body['name']], str(e), j, jcount)
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
break
Ind.Decrement()
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
_getMain().userDriveServiceNotEnabledWarning(user, str(e), i, count)
if csvPF:
csvPF.writeCSVfile('Orphans to Collect')
TRANSFER_DRIVEFILE_ACL_ROLES_MAP = {
'commenter': 'commenter',
'contentmanager': 'fileOrganizer',
'contributor': 'writer',
'editor': 'writer',
'fileorganizer': 'fileOrganizer',
'fileorganiser': 'fileOrganizer',
'manager': 'organizer',
'organizer': 'organizer',
'organiser': 'organizer',
'owner': 'organizer',
'read': 'reader',
'reader': 'reader',
'viewer': 'reader',
'writer': 'writer',
'current': 'current',
'none': 'none',
'source': 'source',
}
# gam <UserTypeEntity> transfer drive <UserItem> [select <DriveFileEntity>]
# [(targetfolderid <DriveFolderID>)|(targetfoldername <DriveFolderName>)]
# [targetuserfoldername <DriveFolderName>] [targetuserorphansfoldername <DriveFolderName>]
# [mergewithtarget [<Boolean>]]
# [createshortcutsfornonmovablefiles [<Boolean>]]
# [skipids <DriveFileEntity>]
# [keepuser | (retainrole reader|commenter|writer|editor|fileorganizer|none)] [noretentionmessages]
# [nonowner_retainrole reader|commenter|writer|editor|fileorganizer|current|none]
# [nonowner_targetrole reader|commenter|writer|editor|fileorganizer|current|none|source]
# (orderby <DriveFileOrderByFieldName> [ascending|descending])*
# [preview] [todrive <ToDriveAttribute>*]

File diff suppressed because it is too large Load Diff