mirror of
https://github.com/GAM-team/GAM.git
synced 2026-07-04 12:51:36 +00:00
Initial commit of a new experimental modular GAM.
This commit is contained in:
433
src/gam/cmd/drive/__init__.py
Normal file
433
src/gam/cmd/drive/__init__.py
Normal 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,
|
||||
)
|
||||
496
src/gam/cmd/drive/activity.py
Normal file
496
src/gam/cmd/drive/activity.py
Normal 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()])
|
||||
|
||||
56
src/gam/cmd/drive/copymove/__init__.py
Normal file
56
src/gam/cmd/drive/copymove/__init__.py
Normal 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,
|
||||
)
|
||||
834
src/gam/cmd/drive/copymove/copymove_move.py
Normal file
834
src/gam/cmd/drive/copymove/copymove_move.py
Normal 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>]]
|
||||
1608
src/gam/cmd/drive/copymove/copymove_util.py
Normal file
1608
src/gam/cmd/drive/copymove/copymove_util.py
Normal file
File diff suppressed because it is too large
Load Diff
1147
src/gam/cmd/drive/core.py
Normal file
1147
src/gam/cmd/drive/core.py
Normal file
File diff suppressed because it is too large
Load Diff
1070
src/gam/cmd/drive/fileinfo.py
Normal file
1070
src/gam/cmd/drive/fileinfo.py
Normal file
File diff suppressed because it is too large
Load Diff
1257
src/gam/cmd/drive/filelist.py
Normal file
1257
src/gam/cmd/drive/filelist.py
Normal file
File diff suppressed because it is too large
Load Diff
838
src/gam/cmd/drive/filepaths.py
Normal file
838
src/gam/cmd/drive/filepaths.py
Normal 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
915
src/gam/cmd/drive/files.py
Normal 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
|
||||
|
||||
836
src/gam/cmd/drive/filetree.py
Normal file
836
src/gam/cmd/drive/filetree.py
Normal 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
584
src/gam/cmd/drive/labels.py
Normal 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
287
src/gam/cmd/drive/looker.py
Normal 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]
|
||||
1330
src/gam/cmd/drive/permissions.py
Normal file
1330
src/gam/cmd/drive/permissions.py
Normal file
File diff suppressed because it is too large
Load Diff
580
src/gam/cmd/drive/revisions.py
Normal file
580
src/gam/cmd/drive/revisions.py
Normal 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)
|
||||
|
||||
1530
src/gam/cmd/drive/shareddrives.py
Normal file
1530
src/gam/cmd/drive/shareddrives.py
Normal file
File diff suppressed because it is too large
Load Diff
30
src/gam/cmd/drive/transfer/__init__.py
Normal file
30
src/gam/cmd/drive/transfer/__init__.py
Normal 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,
|
||||
)
|
||||
793
src/gam/cmd/drive/transfer/fileops.py
Normal file
793
src/gam/cmd/drive/transfer/fileops.py
Normal 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>*]
|
||||
1592
src/gam/cmd/drive/transfer/ownership.py
Normal file
1592
src/gam/cmd/drive/transfer/ownership.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user