├── .husky └── pre-commit ├── google-drive ├── GoogleCloudException.cls ├── classes │ ├── auth │ │ ├── GoogleCredential.cls │ │ ├── GoogleCredential.cls-meta.xml │ │ ├── GoogleAuthorizationCodeFlow.cls-meta.xml │ │ └── GoogleAuthorizationCodeFlow.cls │ ├── entities │ │ ├── GoogleDriveSearchResult.cls │ │ ├── GoogleFileSearchResult.cls │ │ ├── GooglePermissionSearchResult.cls │ │ ├── GoogleFileEntity.cls-meta.xml │ │ ├── GoogleBigFileEntity.cls │ │ ├── GoogleDriveEntity.cls-meta.xml │ │ ├── GoogleBigFileEntity.cls-meta.xml │ │ ├── GoogleDriveSearchResult.cls-meta.xml │ │ ├── GoogleFileSearchResult.cls-meta.xml │ │ ├── GooglePermissionEntity.cls-meta.xml │ │ ├── GooglePermissionSearchResult.cls-meta.xml │ │ ├── GooglePermissionEntity.cls │ │ ├── GoogleDriveEntity.cls │ │ └── GoogleFileEntity.cls │ ├── GoogleDrive.cls-meta.xml │ ├── GoogleDriveTest.cls-meta.xml │ ├── GoogleDriveHttpMockGenerator.cls-meta.xml │ ├── builders │ │ ├── GoogleRequestBuilder.cls-meta.xml │ │ ├── GoogleCloneFileBuilder.cls-meta.xml │ │ ├── GoogleDownloadFileBuilder.cls-meta.xml │ │ ├── GoogleDriveSearchBuilder.cls-meta.xml │ │ ├── GoogleExportFileBuilder.cls-meta.xml │ │ ├── GoogleFileSearchBuilder.cls-meta.xml │ │ ├── GoogleMultipartFileBuilder.cls-meta.xml │ │ ├── GoogleSimpleFileBuilder.cls-meta.xml │ │ ├── GoogleDeleteFileBuilder.cls-meta.xml │ │ ├── GoogleFileMetadataBuilder.cls-meta.xml │ │ ├── GoogleResumableFileBuilder.cls-meta.xml │ │ ├── GoogleTrashFileBuilder.cls-meta.xml │ │ ├── GoogleCreatePermissionFileBuilder.cls-meta.xml │ │ ├── GooglePermissionSearchBuilder.cls-meta.xml │ │ ├── GoogleDeletePermissionFileBuilder.cls-meta.xml │ │ ├── GoogleFileMetadataBuilder.cls │ │ ├── GoogleDeleteFileBuilder.cls │ │ ├── GoogleTrashFileBuilder.cls │ │ ├── GoogleDeletePermissionFileBuilder.cls │ │ ├── GoogleExportFileBuilder.cls │ │ ├── GoogleDriveSearchBuilder.cls │ │ ├── GoogleSimpleFileBuilder.cls │ │ ├── GooglePermissionSearchBuilder.cls │ │ ├── GoogleFileSearchBuilder.cls │ │ ├── GoogleCloneFileBuilder.cls │ │ ├── GoogleCreatePermissionFileBuilder.cls │ │ ├── GoogleDownloadFileBuilder.cls │ │ ├── GoogleRequestBuilder.cls │ │ ├── GoogleMultipartFileBuilder.cls │ │ └── GoogleResumableFileBuilder.cls │ ├── factories │ │ ├── GoogleRetrieveFileFactory.cls-meta.xml │ │ ├── GooglePermissionFileFactory.cls-meta.xml │ │ ├── GoogleRetrieveFileFactory.cls │ │ └── GooglePermissionFileFactory.cls │ ├── GoogleDriveHttpMockGenerator.cls │ ├── GoogleDrive.cls │ └── GoogleDriveTest.cls ├── GoogleConstants.cls-meta.xml ├── GoogleCloudException.cls-meta.xml ├── interfaces │ ├── GoogleAuthorizer.cls-meta.xml │ ├── GoogleFileCreator.cls-meta.xml │ ├── GoogleFileCreator.cls │ └── GoogleAuthorizer.cls └── GoogleConstants.cls ├── .vscode ├── settings.json ├── extensions.json └── launch.json ├── jest.config.js ├── .prettierignore ├── config └── project-scratch-def.json ├── .prettierrc ├── .forceignore ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── sfdx-project.json ├── LICENSE ├── package.json └── README.md /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run precommit -------------------------------------------------------------------------------- /google-drive/GoogleCloudException.cls: -------------------------------------------------------------------------------- 1 | public class GoogleCloudException extends Exception {} -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "search.exclude": { 3 | "**/node_modules": true, 4 | "**/bower_components": true, 5 | "**/.sfdx": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /google-drive/classes/auth/GoogleCredential.cls: -------------------------------------------------------------------------------- 1 | public class GoogleCredential { 2 | @AuraEnabled 3 | public String accessToken; 4 | @AuraEnabled 5 | public String tokenType; 6 | } -------------------------------------------------------------------------------- /google-drive/classes/entities/GoogleDriveSearchResult.cls: -------------------------------------------------------------------------------- 1 | public class GoogleDriveSearchResult { 2 | @AuraEnabled public String nextPageToken; 3 | public List drives; 4 | } -------------------------------------------------------------------------------- /google-drive/classes/entities/GoogleFileSearchResult.cls: -------------------------------------------------------------------------------- 1 | public class GoogleFileSearchResult { 2 | @AuraEnabled public String nextPageToken; 3 | public List files; 4 | } -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const { jestConfig } = require('@salesforce/sfdx-lwc-jest/config'); 2 | 3 | module.exports = { 4 | ...jestConfig, 5 | modulePathIgnorePatterns: ['/.localdevserver'] 6 | }; 7 | -------------------------------------------------------------------------------- /google-drive/GoogleConstants.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64.0 4 | Active 5 | -------------------------------------------------------------------------------- /google-drive/classes/GoogleDrive.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64.0 4 | Active 5 | -------------------------------------------------------------------------------- /google-drive/classes/GoogleDriveTest.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64.0 4 | Active 5 | -------------------------------------------------------------------------------- /google-drive/GoogleCloudException.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 65.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /google-drive/classes/entities/GooglePermissionSearchResult.cls: -------------------------------------------------------------------------------- 1 | public class GooglePermissionSearchResult { 2 | @AuraEnabled public String nextPageToken; 3 | public String kind; 4 | public List permissions; 5 | } -------------------------------------------------------------------------------- /google-drive/interfaces/GoogleAuthorizer.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64.0 4 | Active 5 | -------------------------------------------------------------------------------- /google-drive/interfaces/GoogleFileCreator.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64.0 4 | Active 5 | -------------------------------------------------------------------------------- /google-drive/classes/auth/GoogleCredential.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64.0 4 | Active 5 | -------------------------------------------------------------------------------- /google-drive/classes/entities/GoogleFileEntity.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64.0 4 | Active 5 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "salesforce.salesforcedx-vscode", 4 | "redhat.vscode-xml", 5 | "dbaeumer.vscode-eslint", 6 | "esbenp.prettier-vscode", 7 | "financialforce.lana" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /google-drive/classes/GoogleDriveHttpMockGenerator.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64.0 4 | Active 5 | -------------------------------------------------------------------------------- /google-drive/classes/builders/GoogleRequestBuilder.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64.0 4 | Active 5 | -------------------------------------------------------------------------------- /google-drive/classes/entities/GoogleBigFileEntity.cls: -------------------------------------------------------------------------------- 1 | public class GoogleBigFileEntity { 2 | @AuraEnabled public String resumableSessionId; 3 | @AuraEnabled public Long resumableLatestByte; 4 | @AuraEnabled public GoogleFileEntity file; 5 | } -------------------------------------------------------------------------------- /google-drive/classes/entities/GoogleDriveEntity.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64.0 4 | Active 5 | -------------------------------------------------------------------------------- /google-drive/classes/auth/GoogleAuthorizationCodeFlow.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64.0 4 | Active 5 | -------------------------------------------------------------------------------- /google-drive/classes/builders/GoogleCloneFileBuilder.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64.0 4 | Active 5 | -------------------------------------------------------------------------------- /google-drive/classes/builders/GoogleDownloadFileBuilder.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64.0 4 | Active 5 | -------------------------------------------------------------------------------- /google-drive/classes/builders/GoogleDriveSearchBuilder.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64.0 4 | Active 5 | -------------------------------------------------------------------------------- /google-drive/classes/builders/GoogleExportFileBuilder.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64.0 4 | Active 5 | -------------------------------------------------------------------------------- /google-drive/classes/builders/GoogleFileSearchBuilder.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64.0 4 | Active 5 | -------------------------------------------------------------------------------- /google-drive/classes/builders/GoogleMultipartFileBuilder.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64.0 4 | Active 5 | -------------------------------------------------------------------------------- /google-drive/classes/builders/GoogleSimpleFileBuilder.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64.0 4 | Active 5 | -------------------------------------------------------------------------------- /google-drive/classes/entities/GoogleBigFileEntity.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /google-drive/classes/entities/GoogleDriveSearchResult.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64.0 4 | Active 5 | -------------------------------------------------------------------------------- /google-drive/classes/entities/GoogleFileSearchResult.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64.0 4 | Active 5 | -------------------------------------------------------------------------------- /google-drive/classes/entities/GooglePermissionEntity.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64.0 4 | Active 5 | -------------------------------------------------------------------------------- /google-drive/classes/factories/GoogleRetrieveFileFactory.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64.0 4 | Active 5 | -------------------------------------------------------------------------------- /google-drive/classes/builders/GoogleDeleteFileBuilder.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /google-drive/classes/builders/GoogleFileMetadataBuilder.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /google-drive/classes/builders/GoogleResumableFileBuilder.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /google-drive/classes/builders/GoogleTrashFileBuilder.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 65.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /google-drive/classes/factories/GooglePermissionFileFactory.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64.0 4 | Active 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # List files or directories below to ignore them when running prettier 2 | # More information: https://prettier.io/docs/en/ignore.html 3 | # 4 | 5 | **/staticresources/** 6 | .localdevserver 7 | .sfdx 8 | .sf 9 | .vscode 10 | 11 | coverage/ -------------------------------------------------------------------------------- /google-drive/classes/builders/GoogleCreatePermissionFileBuilder.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64.0 4 | Active 5 | -------------------------------------------------------------------------------- /google-drive/classes/builders/GooglePermissionSearchBuilder.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /google-drive/classes/entities/GooglePermissionSearchResult.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /google-drive/classes/builders/GoogleDeletePermissionFileBuilder.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /google-drive/interfaces/GoogleFileCreator.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * GoogleFileCreator organizes builders focused on creating files in Google Drive. 3 | * This interface promotes uniformity and clarity across various builder implementations. 4 | */ 5 | public interface GoogleFileCreator { 6 | GoogleFileEntity execute(); 7 | } -------------------------------------------------------------------------------- /config/project-scratch-def.json: -------------------------------------------------------------------------------- 1 | { 2 | "orgName": "Andrii Sukhetskyi", 3 | "edition": "Developer", 4 | "features": ["EnableSetPasswordInApi"], 5 | "settings": { 6 | "lightningExperienceSettings": { 7 | "enableS1DesktopEnabled": true 8 | }, 9 | "mobileSettings": { 10 | "enableS1EncryptedStoragePref2": false 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "none", 3 | "plugins": [ 4 | "prettier-plugin-apex", 5 | "@prettier/plugin-xml" 6 | ], 7 | "overrides": [ 8 | { 9 | "files": "**/lwc/**/*.html", 10 | "options": { "parser": "lwc" } 11 | }, 12 | { 13 | "files": "*.{cmp,page,component}", 14 | "options": { "parser": "html" } 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.forceignore: -------------------------------------------------------------------------------- 1 | # List files or directories below to ignore them when running force:source:push, force:source:pull, and force:source:status 2 | # More information: https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_exclude_source.htm 3 | # 4 | 5 | package.xml 6 | 7 | # LWC configuration files 8 | **/jsconfig.json 9 | **/.eslintrc.json 10 | 11 | # LWC Jest 12 | **/__tests__/** -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Problems with existing library methods 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Describe the bug 11 | A clear and concise description of what the bug is. 12 | 13 | ### Steps to reproduce 14 | Steps to reproduce the behavior. 15 | 16 | ### Expected behavior 17 | A clear and concise description of what you expected to happen. 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this library 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Summary 11 | Brief explanation of the feature. 12 | 13 | ### Basic example 14 | Include a basic example or links here. 15 | 16 | ### Motivation 17 | Why are we doing this? What use cases does it support? What is the expected outcome? 18 | -------------------------------------------------------------------------------- /google-drive/interfaces/GoogleAuthorizer.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * The GoogleAuthorizer manages custom control of credentials, 3 | * working in conjunction with GoogleAuthorizationCodeFlow. 4 | */ 5 | public interface GoogleAuthorizer { 6 | /** 7 | * A method to be implemented outside of the library to obtain 8 | * an access token for operating with the Google Drive API. 9 | */ 10 | String retrieveAccessToken(); 11 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch Apex Replay Debugger", 9 | "type": "apex-replay", 10 | "request": "launch", 11 | "logFile": "${command:AskForLogFileName}", 12 | "stopOnEntry": true, 13 | "trace": true 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used for Git repositories to specify intentionally untracked files that Git should ignore. 2 | # If you are not using git, you can delete this file. For more information see: https://git-scm.com/docs/gitignore 3 | # For useful gitignore templates see: https://github.com/github/gitignore 4 | 5 | # Salesforce cache 6 | .sf/ 7 | .sfdx/ 8 | .localdevserver/ 9 | deploy-options.json 10 | 11 | # LWC VSCode autocomplete 12 | **/lwc/jsconfig.json 13 | 14 | # LWC Jest coverage reports 15 | coverage/ 16 | 17 | # Logs 18 | logs 19 | *.log 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | 24 | # Dependency directories 25 | node_modules/ 26 | 27 | # Eslint cache 28 | .eslintcache 29 | 30 | # MacOS system files 31 | .DS_Store 32 | 33 | # Windows system files 34 | Thumbs.db 35 | ehthumbs.db 36 | [Dd]esktop.ini 37 | $RECYCLE.BIN/ 38 | 39 | # Local environment variables 40 | .env 41 | 42 | # Python Salesforce Functions 43 | **/__pycache__/ 44 | **/.venv/ 45 | **/venv/ 46 | -------------------------------------------------------------------------------- /sfdx-project.json: -------------------------------------------------------------------------------- 1 | { 2 | "packageDirectories": [ 3 | { 4 | "path": "google-drive", 5 | "default": true, 6 | "package": "apex-google-drive", 7 | "versionName": "Version 1.2", 8 | "versionNumber": "1.2.0.NEXT", 9 | "versionDescription": "Added Drive v2 file trash support (files.trash), fixed missing supportsAllDrives handling across multiple builders, and improved stability and performance for cached access-token usage.", 10 | "releaseNotesUrl": "https://github.com/sandriiy/salesforce-google-drive-library/releases" 11 | } 12 | ], 13 | "name": "google-drive-library", 14 | "namespace": "", 15 | "sfdcLoginUrl": "https://login.salesforce.com", 16 | "sourceApiVersion": "64.0", 17 | "packageAliases": { 18 | "apex-google-drive": "0HoJ8000000TN1yKAG", 19 | "apex-google-drive@1.0.0-1": "04tJ80000000RfaIAE", 20 | "apex-google-drive@1.0.0-2": "04tJ80000000RffIAE", 21 | "apex-google-drive@1.1.0-1": "04tJ80000000SBtIAM", 22 | "apex-google-drive@1.2.0-1": "04tJ80000011MDEIA2" 23 | } 24 | } -------------------------------------------------------------------------------- /google-drive/classes/entities/GooglePermissionEntity.cls: -------------------------------------------------------------------------------- 1 | public class GooglePermissionEntity { 2 | public String id; 3 | public String displayName; 4 | public String type; 5 | public String kind; 6 | public List permissionDetails; 7 | public String photoLink; 8 | public String emailAddress; 9 | public String role; 10 | public Boolean allowFileDiscovery; 11 | public String domain; 12 | public String expirationTime; 13 | public List teamDrivePermissionDetails; 14 | public Boolean deleted; 15 | public String view; 16 | public Boolean pendingOwner; 17 | 18 | public class PermissionDetail { 19 | public String permissionType; 20 | public String inheritedFrom; 21 | public String role; 22 | public Boolean inherited; 23 | } 24 | 25 | public class TeamDrivePermissionDetail { 26 | public String teamDrivePermissionType; 27 | public String inheritedFrom; 28 | public String role; 29 | public Boolean inherited; 30 | } 31 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Andrii Sukhetskyi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /google-drive/classes/builders/GoogleFileMetadataBuilder.cls: -------------------------------------------------------------------------------- 1 | public class GoogleFileMetadataBuilder { 2 | private Map fileMetadata = new Map(); 3 | 4 | public GoogleFileMetadataBuilder setFileProperty(String filePropertyName, String filePropertyValue) { 5 | this.addPairIfNotEmpty(filePropertyName, filePropertyValue); 6 | return this; 7 | } 8 | 9 | public GoogleFileMetadataBuilder setFileProperty(String filePropertyName, List filePropertyValues) { 10 | this.addPairIfNotEmpty(filePropertyName, filePropertyValues); 11 | return this; 12 | } 13 | 14 | public String build() { 15 | return this.fileMetadata.isEmpty() 16 | ? '' 17 | : JSON.serialize(this.fileMetadata, true); 18 | } 19 | 20 | private void addPairIfNotEmpty(String key, String value) { 21 | if (String.isNotBlank(value)) { 22 | this.fileMetadata.put(key, value); 23 | } 24 | } 25 | 26 | private void addPairIfNotEmpty(String key, List values) { 27 | if (values != null && !values.isEmpty()) { 28 | this.fileMetadata.put(key, values); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /google-drive/classes/builders/GoogleDeleteFileBuilder.cls: -------------------------------------------------------------------------------- 1 | public class GoogleDeleteFileBuilder { 2 | private GoogleRequestBuilder requestGoogleBuilder; 3 | 4 | public GoogleDeleteFileBuilder(GoogleDrive googleDriveInstance, String fileId, String endpoint, String method) { 5 | this.requestGoogleBuilder = new GoogleRequestBuilder(googleDriveInstance); 6 | 7 | this.requestGoogleBuilder.setEndpoint(this.buildDeleteFileEndpoint(endpoint, fileId)); 8 | this.requestGoogleBuilder.setMethod(method); 9 | this.requestGoogleBuilder.setHeader('User-Agent', googleDriveInstance.userAgentName); 10 | } 11 | 12 | public GoogleDeleteFileBuilder setSupportsAllDrives(Boolean includeAllDrives) { 13 | this.requestGoogleBuilder.setParameter('supportsAllDrives', includeAllDrives); 14 | return this; 15 | } 16 | 17 | public void execute() { 18 | HTTPResponse deleteResponse = this.requestGoogleBuilder.send(); 19 | this.validateDeletionPermissionRequest(deleteResponse); 20 | } 21 | 22 | private void validateDeletionPermissionRequest(HTTPResponse deleteResponse) { 23 | if (!GoogleConstants.HTTP_SUCCESS_STATUS_CODES.contains(deleteResponse.getStatusCode())) { 24 | throw new GoogleCloudException(deleteResponse.getBody()); 25 | } 26 | } 27 | 28 | private String buildDeleteFileEndpoint(String baseEndpoint, String fileId) { 29 | return String.format(baseEndpoint, new List{fileId}); 30 | } 31 | } -------------------------------------------------------------------------------- /google-drive/classes/factories/GoogleRetrieveFileFactory.cls: -------------------------------------------------------------------------------- 1 | public class GoogleRetrieveFileFactory { 2 | private GoogleDrive googleDriveInstance; 3 | 4 | public GoogleRetrieveFileFactory(GoogleDrive googleDriveInstance) { 5 | this.googleDriveInstance = googleDriveInstance; 6 | } 7 | 8 | /** 9 | * Gets a file's metadata or content by ID. 10 | * To download Google Docs, Sheets, and Slides use export() instead. 11 | * For more information, see https://developers.google.com/drive/api/guides/manage-downloads 12 | */ 13 | public GoogleDownloadFileBuilder download(String fileId) { 14 | return new GoogleDownloadFileBuilder( 15 | this.googleDriveInstance, 16 | this.buildRetrieveFileEndpoint(GoogleConstants.GET_FILE_ENDPOINT, fileId), 17 | 'GET' 18 | ); 19 | } 20 | 21 | /** 22 | * Exports a Google Workspace document to the requested MIME type and returns a GoogleFileEntity record. 23 | * Note that the exported content is limited to 10MB. 24 | */ 25 | public GoogleExportFileBuilder export(String fileId) { 26 | return new GoogleExportFileBuilder( 27 | this.googleDriveInstance, 28 | this.buildRetrieveFileEndpoint(GoogleConstants.EXPORT_FILE_ENDPOINT, fileId), 29 | 'GET' 30 | ); 31 | } 32 | 33 | private String buildRetrieveFileEndpoint(String endpoint, String fileId) { 34 | return String.format(endpoint, new List{fileId}); 35 | } 36 | } -------------------------------------------------------------------------------- /google-drive/classes/builders/GoogleTrashFileBuilder.cls: -------------------------------------------------------------------------------- 1 | public class GoogleTrashFileBuilder { 2 | private GoogleRequestBuilder requestGoogleBuilder; 3 | 4 | public GoogleTrashFileBuilder(GoogleDrive googleDriveInstance, String fileId, String endpoint) { 5 | this.requestGoogleBuilder = new GoogleRequestBuilder(googleDriveInstance); 6 | 7 | this.requestGoogleBuilder.setHeader('Content-Length', 0); 8 | this.requestGoogleBuilder.setEndpoint(this.buildTrashFileEndpoint(endpoint, fileId)); 9 | this.requestGoogleBuilder.setMethod('POST'); 10 | this.requestGoogleBuilder.setHeader('User-Agent', googleDriveInstance.userAgentName); 11 | } 12 | 13 | public GoogleTrashFileBuilder setSupportsAllDrives(Boolean includeAllDrives) { 14 | this.requestGoogleBuilder.setParameter('supportsAllDrives', includeAllDrives); 15 | return this; 16 | } 17 | 18 | public void execute() { 19 | HTTPResponse trashResponse = this.requestGoogleBuilder.send(); 20 | this.validateTrashingPermissionRequest(trashResponse); 21 | } 22 | 23 | private void validateTrashingPermissionRequest(HTTPResponse trashResponse) { 24 | if (!GoogleConstants.HTTP_SUCCESS_STATUS_CODES.contains(trashResponse.getStatusCode())) { 25 | throw new GoogleCloudException(trashResponse.getBody()); 26 | } 27 | } 28 | 29 | private String buildTrashFileEndpoint(String baseEndpoint, String fileId) { 30 | return String.format(baseEndpoint, new List{fileId}); 31 | } 32 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "salesforce-app", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "Salesforce App", 6 | "scripts": { 7 | "lint": "eslint **/{aura,lwc}/**/*.js", 8 | "test": "npm run test:unit", 9 | "test:unit": "sfdx-lwc-jest", 10 | "test:unit:watch": "sfdx-lwc-jest --watch", 11 | "test:unit:debug": "sfdx-lwc-jest --debug", 12 | "test:unit:coverage": "sfdx-lwc-jest --coverage", 13 | "prettier": "prettier --write \"**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}\"", 14 | "prettier:verify": "prettier --check \"**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}\"", 15 | "postinstall": "husky install", 16 | "precommit": "lint-staged" 17 | }, 18 | "devDependencies": { 19 | "@lwc/eslint-plugin-lwc": "^1.1.2", 20 | "@prettier/plugin-xml": "^3.2.2", 21 | "@salesforce/eslint-config-lwc": "^3.2.3", 22 | "@salesforce/eslint-plugin-aura": "^2.0.0", 23 | "@salesforce/eslint-plugin-lightning": "^1.0.0", 24 | "@salesforce/sfdx-lwc-jest": "^3.1.0", 25 | "eslint": "^8.11.0", 26 | "eslint-plugin-import": "^2.25.4", 27 | "eslint-plugin-jest": "^27.6.0", 28 | "husky": "^8.0.3", 29 | "lint-staged": "^15.1.0", 30 | "prettier": "^3.1.0", 31 | "prettier-plugin-apex": "^2.0.1" 32 | }, 33 | "lint-staged": { 34 | "**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}": [ 35 | "prettier --write" 36 | ], 37 | "**/{aura,lwc}/**/*.js": [ 38 | "eslint" 39 | ] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /google-drive/classes/builders/GoogleDeletePermissionFileBuilder.cls: -------------------------------------------------------------------------------- 1 | public class GoogleDeletePermissionFileBuilder { 2 | private GoogleRequestBuilder requestGoogleBuilder; 3 | 4 | public GoogleDeletePermissionFileBuilder(GoogleDrive googleDriveInstance, String endpoint, String method) { 5 | this.requestGoogleBuilder = new GoogleRequestBuilder(googleDriveInstance); 6 | 7 | this.requestGoogleBuilder.setEndpoint(endpoint); 8 | this.requestGoogleBuilder.setMethod(method); 9 | this.requestGoogleBuilder.setHeader('User-Agent', googleDriveInstance.userAgentName); 10 | } 11 | 12 | public GoogleDeletePermissionFileBuilder setSupportsAllDrives(Boolean includeAllDrives) { 13 | this.requestGoogleBuilder.setParameter('supportsAllDrives', includeAllDrives); 14 | return this; 15 | } 16 | 17 | public GoogleDeletePermissionFileBuilder setDomainAdminAccess(Boolean isDomainAdminAccess) { 18 | this.requestGoogleBuilder.setParameter('useDomainAdminAccess', isDomainAdminAccess); 19 | return this; 20 | } 21 | 22 | public void execute() { 23 | HTTPResponse permissionResponse = this.requestGoogleBuilder.send(); 24 | this.validateDeletionPermissionRequest(permissionResponse); 25 | } 26 | 27 | private void validateDeletionPermissionRequest(HTTPResponse permissionResponse) { 28 | if (!GoogleConstants.HTTP_SUCCESS_STATUS_CODES.contains(permissionResponse.getStatusCode())) { 29 | throw new GoogleCloudException(permissionResponse.getBody()); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /google-drive/classes/factories/GooglePermissionFileFactory.cls: -------------------------------------------------------------------------------- 1 | public class GooglePermissionFileFactory { 2 | private GoogleDrive googleDriveInstance; 3 | 4 | public GooglePermissionFileFactory(GoogleDrive googleDriveInstance) { 5 | this.googleDriveInstance = googleDriveInstance; 6 | } 7 | 8 | public GoogleCreatePermissionFileBuilder create(String fileId) { 9 | return new GoogleCreatePermissionFileBuilder( 10 | this.googleDriveInstance, 11 | this.buildCreatePermissionFileEndpoint(fileId), 12 | 'POST' 13 | ); 14 | } 15 | 16 | public GoogleDeletePermissionFileBuilder remove(String fileId, String permissionId) { 17 | return new GoogleDeletePermissionFileBuilder( 18 | this.googleDriveInstance, 19 | this.buildDeletePermissionFileEndpoint(fileId, permissionId), 20 | 'DELETE' 21 | ); 22 | } 23 | 24 | public GooglePermissionSearchBuilder search(String fileId) { 25 | return new GooglePermissionSearchBuilder( 26 | this.googleDriveInstance, 27 | this.buildSearchPermissionsFileEndpoint(fileId), 28 | 'GET' 29 | ); 30 | } 31 | 32 | private String buildCreatePermissionFileEndpoint(String fileId) { 33 | return String.format(GoogleConstants.NEW_PERMISSION_FILE_ENDPOINT, new List{fileId}); 34 | } 35 | 36 | private String buildSearchPermissionsFileEndpoint(String fileId) { 37 | return String.format(GoogleConstants.LIST_PERMISSIONS_FILE_ENDPOINT, new List{fileId}); 38 | } 39 | 40 | private String buildDeletePermissionFileEndpoint(String fileId, String permissionId) { 41 | return String.format( 42 | GoogleConstants.DELETE_PERMISSION_FILE_ENDPOINT, 43 | new List{ 44 | fileId, 45 | permissionId 46 | } 47 | ); 48 | } 49 | } -------------------------------------------------------------------------------- /google-drive/GoogleConstants.cls: -------------------------------------------------------------------------------- 1 | public class GoogleConstants { 2 | public static final String SEARCH_DRIVES_ENDPOINT = 'https://www.googleapis.com/drive/v3/drives'; 3 | public static final String SEARCH_FILES_ENDPOINT = 'https://www.googleapis.com/drive/v3/files'; 4 | public static final String UPLOAD_FILES_ENDPOINT = 'https://www.googleapis.com/upload/drive/v3/files'; 5 | public static final String CLONE_FILE_ENDPOINT = 'https://www.googleapis.com/drive/v3/files/{0}/copy'; 6 | public static final String DELETE_FILE_ENDPOINT = 'https://www.googleapis.com/drive/v3/files/{0}'; 7 | public static final String TRASH_FILE_ENDPOINT = 'https://www.googleapis.com/drive/v2/files/{0}/trash'; 8 | public static final String NEW_PERMISSION_FILE_ENDPOINT = 'https://www.googleapis.com/drive/v3/files/{0}/permissions'; 9 | public static final String LIST_PERMISSIONS_FILE_ENDPOINT = 'https://www.googleapis.com/drive/v3/files/{0}/permissions'; 10 | public static final String DELETE_PERMISSION_FILE_ENDPOINT = 'https://www.googleapis.com/drive/v3/files/{0}/permissions/{1}'; 11 | public static final String EXPORT_FILE_ENDPOINT = 'https://www.googleapis.com/drive/v3/files/{0}/export'; 12 | public static final String GET_FILE_ENDPOINT = 'https://www.googleapis.com/drive/v3/files/{0}'; 13 | 14 | public static final List HTTP_SUCCESS_STATUS_CODES = new List{200, 201, 204, 206, 304, 308}; 15 | public static final List HTTP_INTERRUPTED_STATUS_CODES = new List{503, 308}; 16 | public static final Integer HTTP_UNAUTHORIZED_STATUS_CODE = 401; 17 | 18 | public static final Integer UPLOAD_DEFAULT_INITIAL_BYTE = 0; 19 | public static final Integer SEARCH_DEFAULT_PAGE_SIZE = 100; 20 | public static final Integer DEFAULT_TOKEN_EXPIRATION_SECS = 3600; 21 | 22 | public static final String MULTIPART_DEFAULT_TYPE = 'related'; 23 | public static final String MULTIPART_REQUEST_BOUNDARY = 'multipart-gdrive-boundary'; 24 | } -------------------------------------------------------------------------------- /google-drive/classes/builders/GoogleExportFileBuilder.cls: -------------------------------------------------------------------------------- 1 | public class GoogleExportFileBuilder { 2 | private GoogleRequestBuilder requestGoogleBuilder; 3 | 4 | public GoogleExportFileBuilder(GoogleDrive googleDriveInstance, String endpoint, String method) { 5 | this.requestGoogleBuilder = new GoogleRequestBuilder(googleDriveInstance); 6 | 7 | this.requestGoogleBuilder.setEndpoint(endpoint); 8 | this.requestGoogleBuilder.setMethod(method); 9 | this.requestGoogleBuilder.setHeader('User-Agent', googleDriveInstance.userAgentName); 10 | this.requestGoogleBuilder.setHeader('Content-Type', 'application/json'); 11 | } 12 | 13 | public GoogleExportFileBuilder setMimeType(String mimeType) { 14 | this.requestGoogleBuilder.setParameter('mimeType', mimeType); 15 | return this; 16 | } 17 | 18 | public GoogleExportFileBuilder setFields(String fields) { 19 | this.requestGoogleBuilder.setParameter('fields', fields); 20 | return this; 21 | } 22 | 23 | public GoogleExportFileBuilder setSearchOnAllDrives(Boolean includeAllDrives) { 24 | this.requestGoogleBuilder.setParameter('supportsAllDrives', includeAllDrives); 25 | return this; 26 | } 27 | 28 | public GoogleFileEntity execute() { 29 | HTTPResponse exportResponse = this.requestGoogleBuilder.send(); 30 | return this.retrieveRequestExportWrapper(exportResponse); 31 | } 32 | 33 | private GoogleFileEntity retrieveRequestExportWrapper(HTTPResponse exportResponse) { 34 | if (GoogleConstants.HTTP_SUCCESS_STATUS_CODES.contains(exportResponse.getStatusCode())) { 35 | GoogleFileEntity contentFileEntity = new GoogleFileEntity(); 36 | contentFileEntity.body = exportResponse.getBody(); 37 | contentFileEntity.bodyAsBlob = exportResponse.getBodyAsBlob(); 38 | return contentFileEntity; 39 | } else { 40 | throw new GoogleCloudException(exportResponse.getBody()); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /google-drive/classes/GoogleDriveHttpMockGenerator.cls: -------------------------------------------------------------------------------- 1 | public class GoogleDriveHttpMockGenerator implements HttpCalloutMock { 2 | private String mockEndpoint; 3 | private Integer mockStatusCode; 4 | private String mockBody; 5 | private Map mockHeaders; 6 | private HttpResponse mockResponse; 7 | 8 | public GoogleDriveHttpMockGenerator(String endpoint, Integer statusCode, String body) { 9 | this.mockEndpoint = endpoint; 10 | this.mockStatusCode = statusCode; 11 | this.mockBody = body; 12 | } 13 | 14 | public GoogleDriveHttpMockGenerator(String endpoint, Integer statusCode, String body, Map headers) { 15 | this.mockEndpoint = endpoint; 16 | this.mockStatusCode = statusCode; 17 | this.mockBody = body; 18 | this.mockHeaders = headers; 19 | } 20 | 21 | public HTTPResponse respond(HttpRequest req) { 22 | return this.createMockResponse(); 23 | } 24 | 25 | private HttpResponse createMockResponse() { 26 | HttpResponse mockResponse = new HttpResponse(); 27 | this.buildMockResponseHeaders(mockResponse); 28 | this.buildMockResponseBody(mockResponse); 29 | this.buildMockResponseCode(mockResponse); 30 | return mockResponse; 31 | } 32 | 33 | private void buildMockResponseHeaders(HttpResponse mockResponse) { 34 | if (this.mockHeaders != null && this.mockHeaders.size() > 0) { 35 | for (String headerKey : this.mockHeaders.keySet()) { 36 | mockResponse.setHeader(headerKey, this.mockHeaders.get(headerKey)); 37 | } 38 | } 39 | } 40 | 41 | private void buildMockResponseBody(HttpResponse mockResponse) { 42 | if (String.isNotBlank(this.mockBody)) { 43 | mockResponse.setBody(this.mockBody); 44 | } 45 | } 46 | 47 | private void buildMockResponseCode(HttpResponse mockResponse) { 48 | if (this.mockStatusCode != null) { 49 | mockResponse.setStatusCode(this.mockStatusCode); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /google-drive/classes/entities/GoogleDriveEntity.cls: -------------------------------------------------------------------------------- 1 | public class GoogleDriveEntity { 2 | public String id; 3 | public String name; 4 | public String colorRgb; 5 | public String kind; 6 | public String backgroundImageLink; 7 | public Capabilities capabilities; 8 | public String themeId; 9 | public BackgroundImageFile backgroundImageFile; 10 | public String createdTime; 11 | public Boolean hidden; 12 | public Restrictions restrictions; 13 | public String orgUnitId; 14 | 15 | public class Capabilities { 16 | public Boolean canAddChildren; 17 | public Boolean canComment; 18 | public Boolean canCopy; 19 | public Boolean canDeleteDrive; 20 | public Boolean canDownload; 21 | public Boolean canEdit; 22 | public Boolean canListChildren; 23 | public Boolean canManageMembers; 24 | public Boolean canReadRevisions; 25 | public Boolean canRename; 26 | public Boolean canRenameDrive; 27 | public Boolean canChangeDriveBackground; 28 | public Boolean canShare; 29 | public Boolean canChangeCopyRequiresWriterPermissionRestriction; 30 | public Boolean canChangeDomainUsersOnlyRestriction; 31 | public Boolean canChangeDriveMembersOnlyRestriction; 32 | public Boolean canChangeSharingFoldersRequiresOrganizerPermissionRestriction; 33 | public Boolean canResetDriveRestrictions; 34 | public Boolean canDeleteChildren; 35 | public Boolean canTrashChildren; 36 | } 37 | 38 | public class BackgroundImageFile { 39 | public String id; 40 | public Double xCoordinate; 41 | public Double yCoordinate; 42 | public Double width; 43 | } 44 | 45 | public class Restrictions { 46 | public Boolean copyRequiresWriterPermission; 47 | public Boolean domainUsersOnly; 48 | public Boolean driveMembersOnly; 49 | public Boolean adminManagedRestrictions; 50 | public Boolean sharingFoldersRequiresOrganizerPermission; 51 | } 52 | } -------------------------------------------------------------------------------- /google-drive/classes/builders/GoogleDriveSearchBuilder.cls: -------------------------------------------------------------------------------- 1 | public class GoogleDriveSearchBuilder { 2 | private GoogleRequestBuilder requestGoogleBuilder; 3 | 4 | public GoogleDriveSearchBuilder(GoogleDrive googleDriveInstance, String endpoint, String method) { 5 | this.requestGoogleBuilder = new GoogleRequestBuilder(googleDriveInstance); 6 | this.requestGoogleBuilder.setEndpoint(endpoint); 7 | this.requestGoogleBuilder.setMethod(method); 8 | this.requestGoogleBuilder.setHeader('User-Agent', googleDriveInstance.userAgentName); 9 | this.requestGoogleBuilder.setParameter('pageSize', GoogleConstants.SEARCH_DEFAULT_PAGE_SIZE); 10 | } 11 | 12 | public GoogleDriveSearchBuilder setMaxResult(Integer maxResult) { 13 | this.requestGoogleBuilder.setParameter('pageSize', maxResult); 14 | return this; 15 | } 16 | 17 | public GoogleDriveSearchBuilder setSearchQuery(String query) { 18 | this.requestGoogleBuilder.setParameter('q', query); 19 | return this; 20 | } 21 | 22 | public GoogleDriveSearchBuilder setDomainAdminAccess(Boolean isDomainAdminAccess) { 23 | this.requestGoogleBuilder.setParameter('useDomainAdminAccess', isDomainAdminAccess); 24 | return this; 25 | } 26 | 27 | public GoogleDriveSearchBuilder setNextPageToken(String pageToken) { 28 | this.requestGoogleBuilder.setParameter('pageToken', pageToken); 29 | return this; 30 | } 31 | 32 | public GoogleDriveSearchResult execute() { 33 | HTTPResponse searchResponse = this.requestGoogleBuilder.send(); 34 | return this.retrieveRequestSearchWrapper(searchResponse); 35 | } 36 | 37 | private GoogleDriveSearchResult retrieveRequestSearchWrapper(HTTPResponse searchResponse) { 38 | if (GoogleConstants.HTTP_SUCCESS_STATUS_CODES.contains(searchResponse.getStatusCode())) { 39 | return (GoogleDriveSearchResult) JSON.deserialize(searchResponse.getBody(), GoogleDriveSearchResult.class); 40 | } else { 41 | throw new GoogleCloudException(searchResponse.getBody()); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /google-drive/classes/builders/GoogleSimpleFileBuilder.cls: -------------------------------------------------------------------------------- 1 | public class GoogleSimpleFileBuilder implements GoogleFileCreator { 2 | private GoogleRequestBuilder requestGoogleBuilder; 3 | 4 | public GoogleSimpleFileBuilder(GoogleDrive googleDriveInstance, String endpoint, String method) { 5 | this.requestGoogleBuilder = new GoogleRequestBuilder(googleDriveInstance); 6 | this.requestGoogleBuilder.setEndpoint(endpoint); 7 | this.requestGoogleBuilder.setMethod(method); 8 | this.requestGoogleBuilder.setHeader('User-Agent', googleDriveInstance.userAgentName); 9 | this.requestGoogleBuilder.setParameter('uploadType', 'media'); 10 | } 11 | 12 | public GoogleSimpleFileBuilder setContentType(String contentType) { 13 | this.requestGoogleBuilder.setHeader('Content-Type', contentType); 14 | return this; 15 | } 16 | 17 | public GoogleSimpleFileBuilder setContentLength(Integer contentLength) { 18 | this.requestGoogleBuilder.setHeader('Content-Length', contentLength); 19 | return this; 20 | } 21 | 22 | public GoogleSimpleFileBuilder setBody(Blob body) { 23 | this.requestGoogleBuilder.setBody(body); 24 | return this; 25 | } 26 | 27 | public GoogleSimpleFileBuilder setFields(String fields) { 28 | this.requestGoogleBuilder.setParameter('fields', fields); 29 | return this; 30 | } 31 | 32 | public GoogleSimpleFileBuilder setSupportsAllDrives(Boolean includeAllDrives) { 33 | this.requestGoogleBuilder.setParameter('supportsAllDrives', includeAllDrives); 34 | return this; 35 | } 36 | 37 | public GoogleFileEntity execute() { 38 | HTTPResponse uploadResponse = this.requestGoogleBuilder.send(); 39 | return this.retrieveRequestCreateWrapper(uploadResponse); 40 | } 41 | 42 | private GoogleFileEntity retrieveRequestCreateWrapper(HTTPResponse uploadResponse) { 43 | if (GoogleConstants.HTTP_SUCCESS_STATUS_CODES.contains(uploadResponse.getStatusCode())) { 44 | return (GoogleFileEntity) JSON.deserialize(uploadResponse.getBody(), GoogleFileEntity.class); 45 | } else { 46 | throw new GoogleCloudException(uploadResponse.getBody()); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /google-drive/classes/builders/GooglePermissionSearchBuilder.cls: -------------------------------------------------------------------------------- 1 | public with sharing class GooglePermissionSearchBuilder { 2 | private GoogleRequestBuilder requestGoogleBuilder; 3 | 4 | public GooglePermissionSearchBuilder(GoogleDrive googleDriveInstance, String endpoint, String method) { 5 | this.requestGoogleBuilder = new GoogleRequestBuilder(googleDriveInstance); 6 | this.requestGoogleBuilder.setEndpoint(endpoint); 7 | this.requestGoogleBuilder.setMethod(method); 8 | this.requestGoogleBuilder.setHeader('User-Agent', googleDriveInstance.userAgentName); 9 | this.requestGoogleBuilder.setParameter('pageSize', GoogleConstants.SEARCH_DEFAULT_PAGE_SIZE); 10 | } 11 | 12 | public GooglePermissionSearchBuilder setMaxResult(Integer maxResult) { 13 | this.requestGoogleBuilder.setParameter('pageSize', maxResult); 14 | return this; 15 | } 16 | 17 | public GooglePermissionSearchBuilder setNextPageToken(String pageToken) { 18 | this.requestGoogleBuilder.setParameter('pageToken', pageToken); 19 | return this; 20 | } 21 | 22 | public GooglePermissionSearchBuilder setFields(String fields) { 23 | String fieldsQuery = fields.contains('permissions') 24 | ? fields 25 | : 'permissions(' + fields + ')'; 26 | 27 | this.requestGoogleBuilder.setParameter('fields', fieldsQuery); 28 | return this; 29 | } 30 | 31 | public GooglePermissionSearchBuilder setSearchOnAllDrives(Boolean includeAllDrives) { 32 | this.requestGoogleBuilder.setParameter('supportsAllDrives', includeAllDrives); 33 | return this; 34 | } 35 | 36 | public GooglePermissionSearchResult execute() { 37 | HTTPResponse searchResponse = this.requestGoogleBuilder.send(); 38 | return this.retrieveRequestPermissionWrapper(searchResponse); 39 | } 40 | 41 | private GooglePermissionSearchResult retrieveRequestPermissionWrapper(HTTPResponse searchResponse) { 42 | if (GoogleConstants.HTTP_SUCCESS_STATUS_CODES.contains(searchResponse.getStatusCode())) { 43 | return (GooglePermissionSearchResult) JSON.deserialize(searchResponse.getBody(), GooglePermissionSearchResult.class); 44 | } else { 45 | throw new GoogleCloudException(searchResponse.getBody()); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /google-drive/classes/builders/GoogleFileSearchBuilder.cls: -------------------------------------------------------------------------------- 1 | public class GoogleFileSearchBuilder { 2 | private GoogleRequestBuilder requestGoogleBuilder; 3 | 4 | public GoogleFileSearchBuilder(GoogleDrive googleDriveInstance, String endpoint, String method) { 5 | this.requestGoogleBuilder = new GoogleRequestBuilder(googleDriveInstance); 6 | this.requestGoogleBuilder.setEndpoint(endpoint); 7 | this.requestGoogleBuilder.setMethod(method); 8 | this.requestGoogleBuilder.setHeader('User-Agent', googleDriveInstance.userAgentName); 9 | this.requestGoogleBuilder.setParameter('pageSize', GoogleConstants.SEARCH_DEFAULT_PAGE_SIZE); 10 | } 11 | 12 | public GoogleFileSearchBuilder setMaxResult(Integer maxResult) { 13 | this.requestGoogleBuilder.setParameter('pageSize', maxResult); 14 | return this; 15 | } 16 | 17 | public GoogleFileSearchBuilder setSearchQuery(String query) { 18 | this.requestGoogleBuilder.setParameter('q', query); 19 | return this; 20 | } 21 | 22 | public GoogleFileSearchBuilder setNextPageToken(String pageToken) { 23 | this.requestGoogleBuilder.setParameter('pageToken', pageToken); 24 | return this; 25 | } 26 | 27 | public GoogleFileSearchBuilder setFields(String fields) { 28 | this.requestGoogleBuilder.setParameter('fields', fields); 29 | return this; 30 | } 31 | 32 | public GoogleFileSearchBuilder setOrderBy(String orderBy) { 33 | this.requestGoogleBuilder.setParameter('orderBy', orderBy); 34 | return this; 35 | } 36 | 37 | public GoogleFileSearchBuilder setDriveId(String driveId) { 38 | this.requestGoogleBuilder.setParameter('driveId', driveId); 39 | return this; 40 | } 41 | 42 | public GoogleFileSearchBuilder setSearchOnAllDrives(Boolean includeAllDrives) { 43 | this.requestGoogleBuilder.setParameter('supportsAllDrives', includeAllDrives); 44 | this.requestGoogleBuilder.setParameter('includeItemsFromAllDrives', includeAllDrives); 45 | return this; 46 | } 47 | 48 | public GoogleFileSearchResult execute() { 49 | HTTPResponse searchResponse = this.requestGoogleBuilder.send(); 50 | return this.retrieveRequestSearchWrapper(searchResponse); 51 | } 52 | 53 | private GoogleFileSearchResult retrieveRequestSearchWrapper(HTTPResponse searchResponse) { 54 | if (GoogleConstants.HTTP_SUCCESS_STATUS_CODES.contains(searchResponse.getStatusCode())) { 55 | return (GoogleFileSearchResult) JSON.deserialize(searchResponse.getBody(), GoogleFileSearchResult.class); 56 | } else { 57 | throw new GoogleCloudException(searchResponse.getBody()); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /google-drive/classes/builders/GoogleCloneFileBuilder.cls: -------------------------------------------------------------------------------- 1 | public class GoogleCloneFileBuilder implements GoogleFileCreator { 2 | private GoogleRequestBuilder requestGoogleBuilder; 3 | private GoogleFileMetadataBuilder fileMetadataBuilder; 4 | 5 | public GoogleCloneFileBuilder(GoogleDrive googleDriveInstance, String fileId, String endpoint, String method) { 6 | this.requestGoogleBuilder = new GoogleRequestBuilder(googleDriveInstance); 7 | this.fileMetadataBuilder = new GoogleFileMetadataBuilder(); 8 | 9 | this.requestGoogleBuilder.setEndpoint(this.buildCloneFileEndpoint(endpoint, fileId)); 10 | this.requestGoogleBuilder.setMethod(method); 11 | this.requestGoogleBuilder.setHeader('User-Agent', googleDriveInstance.userAgentName); 12 | this.requestGoogleBuilder.setHeader('Content-Type', 'application/json'); 13 | } 14 | 15 | public GoogleCloneFileBuilder setFields(String fields) { 16 | this.requestGoogleBuilder.setParameter('fields', fields); 17 | return this; 18 | } 19 | 20 | public GoogleCloneFileBuilder setFileName(String fileName) { 21 | this.fileMetadataBuilder.setFileProperty('name', fileName); 22 | return this; 23 | } 24 | 25 | public GoogleCloneFileBuilder setMimeType(String mimeType) { 26 | this.fileMetadataBuilder.setFileProperty('mimeType', mimeType); 27 | return this; 28 | } 29 | 30 | public GoogleCloneFileBuilder setParentFolders(List folderIds) { 31 | this.fileMetadataBuilder.setFileProperty('parents', folderIds); 32 | return this; 33 | } 34 | 35 | public GoogleCloneFileBuilder setSupportsAllDrives(Boolean includeAllDrives) { 36 | this.requestGoogleBuilder.setParameter('supportsAllDrives', includeAllDrives); 37 | return this; 38 | } 39 | 40 | public GoogleFileEntity execute() { 41 | String cloneBody = this.fileMetadataBuilder.build(); 42 | this.requestGoogleBuilder.setHeader('Content-Length', cloneBody.length()); 43 | this.requestGoogleBuilder.setBody(cloneBody); 44 | 45 | HTTPResponse cloneResponse = this.requestGoogleBuilder.send(); 46 | return this.retrieveRequestClonedWrapper(cloneResponse); 47 | } 48 | 49 | private GoogleFileEntity retrieveRequestClonedWrapper(HTTPResponse cloneResponse) { 50 | if (GoogleConstants.HTTP_SUCCESS_STATUS_CODES.contains(cloneResponse.getStatusCode())) { 51 | return (GoogleFileEntity) JSON.deserialize(cloneResponse.getBody(), GoogleFileEntity.class); 52 | } else { 53 | throw new GoogleCloudException(cloneResponse.getBody()); 54 | } 55 | } 56 | 57 | private String buildCloneFileEndpoint(String baseEndpoint, String fileId) { 58 | return String.format(baseEndpoint, new List{fileId}); 59 | } 60 | } -------------------------------------------------------------------------------- /google-drive/classes/builders/GoogleCreatePermissionFileBuilder.cls: -------------------------------------------------------------------------------- 1 | public class GoogleCreatePermissionFileBuilder { 2 | private GoogleRequestBuilder requestGoogleBuilder; 3 | private Map newPermission; 4 | 5 | public GoogleCreatePermissionFileBuilder(GoogleDrive googleDriveInstance, String endpoint, String method) { 6 | this.requestGoogleBuilder = new GoogleRequestBuilder(googleDriveInstance); 7 | this.newPermission = new Map(); 8 | 9 | this.requestGoogleBuilder.setEndpoint(endpoint); 10 | this.requestGoogleBuilder.setMethod(method); 11 | this.requestGoogleBuilder.setHeader('User-Agent', googleDriveInstance.userAgentName); 12 | this.requestGoogleBuilder.setHeader('Content-Type', 'application/json'); 13 | } 14 | 15 | public GoogleCreatePermissionFileBuilder setSendNotificationEmail(Boolean isSendEmail) { 16 | this.requestGoogleBuilder.setParameter('sendNotificationEmail', isSendEmail); 17 | return this; 18 | } 19 | 20 | public GoogleCreatePermissionFileBuilder setTransferOwnership(Boolean isTransferOwnership) { 21 | this.requestGoogleBuilder.setParameter('transferOwnership', isTransferOwnership); 22 | return this; 23 | } 24 | 25 | public GoogleCreatePermissionFileBuilder setPrincipalType(String type) { 26 | this.newPermission.put('type', type); 27 | return this; 28 | } 29 | 30 | public GoogleCreatePermissionFileBuilder setPrincipalRole(String role) { 31 | this.newPermission.put('role', role); 32 | return this; 33 | } 34 | 35 | public GoogleCreatePermissionFileBuilder setPrincipalEmailAddress(String emailAddress) { 36 | this.newPermission.put('emailAddress', emailAddress); 37 | return this; 38 | } 39 | 40 | public GoogleCreatePermissionFileBuilder setSupportsAllDrives(Boolean includeAllDrives) { 41 | this.requestGoogleBuilder.setParameter('supportsAllDrives', includeAllDrives); 42 | return this; 43 | } 44 | 45 | public GooglePermissionEntity execute() { 46 | String permissionBody = this.buildPermissionFileRequestBody(); 47 | this.requestGoogleBuilder.setHeader('Content-Length', permissionBody.length()); 48 | this.requestGoogleBuilder.setBody(permissionBody); 49 | 50 | HTTPResponse permissionResponse = this.requestGoogleBuilder.send(); 51 | return this.retrieveRequestPermissionWrapper(permissionResponse); 52 | } 53 | 54 | private GooglePermissionEntity retrieveRequestPermissionWrapper(HTTPResponse permissionResponse) { 55 | if (GoogleConstants.HTTP_SUCCESS_STATUS_CODES.contains(permissionResponse.getStatusCode())) { 56 | return (GooglePermissionEntity) JSON.deserialize(permissionResponse.getBody(), GooglePermissionEntity.class); 57 | } else { 58 | throw new GoogleCloudException(permissionResponse.getBody()); 59 | } 60 | } 61 | 62 | private String buildPermissionFileRequestBody() { 63 | return this.newPermission.isEmpty() ? '' : JSON.serialize(this.newPermission, true); 64 | } 65 | } -------------------------------------------------------------------------------- /google-drive/classes/auth/GoogleAuthorizationCodeFlow.cls: -------------------------------------------------------------------------------- 1 | public inherited sharing class GoogleAuthorizationCodeFlow { 2 | public class Builder { 3 | private String apexAuthorizerName; 4 | private Cache.OrgPartition orgPartition; 5 | private Cache.SessionPartition sessionPartition; 6 | private String partitionKey; 7 | private String cachedAccessToken; 8 | 9 | public Builder setLocalGoogleAuthorizer(String apexClassName) { 10 | this.apexAuthorizerName = apexClassName; 11 | return this; 12 | } 13 | 14 | public Builder setLocalPlatformCache(Cache.OrgPartition orgPartition, String key) { 15 | this.orgPartition = orgPartition; 16 | this.partitionKey = key; 17 | 18 | if (orgPartition.contains(key)) { 19 | this.cachedAccessToken = (String) orgPartition.get(key); 20 | } 21 | 22 | return this; 23 | } 24 | 25 | public Builder setLocalPlatformCache(Cache.SessionPartition sessionPartition, String key) { 26 | this.sessionPartition = sessionPartition; 27 | this.partitionKey = key; 28 | 29 | if (!isAsync() && sessionPartition.contains(key)) { 30 | this.cachedAccessToken = (String) sessionPartition.get(key); 31 | } 32 | 33 | return this; 34 | } 35 | 36 | public GoogleCredential build() { 37 | return executeGoogleAuthFlow(); 38 | } 39 | 40 | private GoogleCredential executeGoogleAuthFlow() { 41 | if (String.isNotBlank(this.cachedAccessToken)) { 42 | return buildCredentialInfo(this.cachedAccessToken, 'Bearer'); 43 | } else { 44 | Type authorizerType = Type.forName(this.apexAuthorizerName); 45 | GoogleAuthorizer authorizerInstance = (GoogleAuthorizer) authorizerType.newInstance(); 46 | 47 | String accessToken = authorizerInstance.retrieveAccessToken(); 48 | resolvePlatformCacheStorage(accessToken); 49 | return buildCredentialInfo(accessToken, 'Bearer'); 50 | } 51 | } 52 | 53 | private void resolvePlatformCacheStorage(String accessToken) { 54 | if (String.isBlank(this.partitionKey)) return; 55 | 56 | if (this.orgPartition != null) { 57 | this.orgPartition.put(this.partitionKey, accessToken, GoogleConstants.DEFAULT_TOKEN_EXPIRATION_SECS); 58 | } else if (this.sessionPartition != null && !isAsync()) { 59 | this.sessionPartition.put(this.partitionKey, accessToken, GoogleConstants.DEFAULT_TOKEN_EXPIRATION_SECS); 60 | } 61 | } 62 | 63 | private GoogleCredential buildCredentialInfo(String accessToken, String tokenType) { 64 | GoogleCredential newCredential = new GoogleCredential(); 65 | newCredential.accessToken = accessToken; 66 | newCredential.tokenType = tokenType; 67 | 68 | return newCredential; 69 | } 70 | 71 | private Boolean isAsync() { 72 | return System.isBatch() || System.isQueueable() || System.isScheduled() || System.isFuture(); 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /google-drive/classes/builders/GoogleDownloadFileBuilder.cls: -------------------------------------------------------------------------------- 1 | public class GoogleDownloadFileBuilder { 2 | public enum DownloadType {METADATA, CONTENT} 3 | private GoogleRequestBuilder requestGoogleBuilder; 4 | private DownloadType selectedDownloadType; 5 | 6 | public GoogleDownloadFileBuilder(GoogleDrive googleDriveInstance, String endpoint, String method) { 7 | this.requestGoogleBuilder = new GoogleRequestBuilder(googleDriveInstance); 8 | this.selectedDownloadType = DownloadType.METADATA; // By default, only metadata is retrieved 9 | 10 | this.requestGoogleBuilder.setEndpoint(endpoint); 11 | this.requestGoogleBuilder.setMethod(method); 12 | this.requestGoogleBuilder.setHeader('User-Agent', googleDriveInstance.userAgentName); 13 | this.requestGoogleBuilder.setHeader('Content-Type', 'application/json'); 14 | } 15 | 16 | public GoogleDownloadFileBuilder setFileDownloadType(DownloadType downloadType) { 17 | // The default behavior is to return only the metadata of the file, 18 | // with the parameter "alt=media" the returned type is the body of the file. 19 | this.selectedDownloadType = downloadType; 20 | if (downloadType == GoogleDownloadFileBuilder.DownloadType.CONTENT) { 21 | this.requestGoogleBuilder.setParameter('alt', 'media'); 22 | } else if (downloadType == GoogleDownloadFileBuilder.DownloadType.METADATA) { 23 | this.requestGoogleBuilder.removeParameter('alt'); 24 | } 25 | 26 | return this; 27 | } 28 | 29 | public GoogleDownloadFileBuilder setSearchOnAllDrives(Boolean includeAllDrives) { 30 | this.requestGoogleBuilder.setParameter('supportsAllDrives', includeAllDrives); 31 | return this; 32 | } 33 | 34 | public GoogleDownloadFileBuilder setPartialRange(Integer startByte, Integer endByte) { 35 | String downloadRange = 'bytes=' + String.valueOf(startByte) + '-' + String.valueOf(endByte); 36 | this.requestGoogleBuilder.setHeader('Range', downloadRange); 37 | return this; 38 | } 39 | 40 | public GoogleDownloadFileBuilder setPartialRange(Long startByte, Long endByte) { 41 | String downloadRange = 'bytes=' + String.valueOf(startByte) + '-' + String.valueOf(endByte); 42 | this.requestGoogleBuilder.setHeader('Range', downloadRange); 43 | return this; 44 | } 45 | 46 | public GoogleDownloadFileBuilder setFields(String fields) { 47 | this.requestGoogleBuilder.setParameter('fields', fields); 48 | return this; 49 | } 50 | 51 | public GoogleFileEntity execute() { 52 | HTTPResponse downloadResponse = this.requestGoogleBuilder.send(); 53 | return this.retrieveRequestDownloadWrapper(downloadResponse); 54 | } 55 | 56 | private GoogleFileEntity retrieveRequestDownloadWrapper(HTTPResponse downloadResponse) { 57 | if (GoogleConstants.HTTP_SUCCESS_STATUS_CODES.contains(downloadResponse.getStatusCode())) { 58 | if (this.selectedDownloadType == GoogleDownloadFileBuilder.DownloadType.METADATA) { 59 | return (GoogleFileEntity) JSON.deserialize(downloadResponse.getBody(), GoogleFileEntity.class); 60 | } else { 61 | GoogleFileEntity contentFileEntity = new GoogleFileEntity(); 62 | contentFileEntity.body = downloadResponse.getBody(); 63 | contentFileEntity.bodyAsBlob = downloadResponse.getBodyAsBlob(); 64 | return contentFileEntity; 65 | } 66 | } else { 67 | throw new GoogleCloudException(downloadResponse.getBody()); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /google-drive/classes/builders/GoogleRequestBuilder.cls: -------------------------------------------------------------------------------- 1 | public class GoogleRequestBuilder { 2 | private GoogleDrive googleDriveInstance; 3 | 4 | private String endpoint; 5 | private String method; 6 | private Map headers; 7 | private Map parameters; 8 | private String bodyAsString; 9 | private Blob bodyAsBlob; 10 | 11 | public GoogleRequestBuilder(GoogleDrive googleDriveInstance) { 12 | this.googleDriveInstance = googleDriveInstance; 13 | this.headers = new Map(); 14 | this.parameters = new Map(); 15 | } 16 | 17 | public GoogleRequestBuilder duplicate() { 18 | GoogleRequestBuilder clonedGoogleRequest = this.clone(); 19 | clonedGoogleRequest.bodyAsString = null; 20 | clonedGoogleRequest.bodyAsBlob = null; 21 | clonedGoogleRequest.headers = new Map(); 22 | clonedGoogleRequest.parameters = new Map(); 23 | 24 | return clonedGoogleRequest; 25 | } 26 | 27 | public GoogleRequestBuilder setEndpoint(String endpoint) { 28 | this.endpoint = endpoint; 29 | return this; 30 | } 31 | 32 | public GoogleRequestBuilder setMethod(String method) { 33 | this.method = method; 34 | return this; 35 | } 36 | 37 | public GoogleRequestBuilder setHeader(String key, String value) { 38 | this.headers.put(key, value); 39 | return this; 40 | } 41 | 42 | public GoogleRequestBuilder setHeader(String key, Integer value) { 43 | this.headers.put(key, String.valueOf(value)); 44 | return this; 45 | } 46 | 47 | public GoogleRequestBuilder setParameter(String key, String value) { 48 | this.parameters.put(key, EncodingUtil.urlEncode(value, 'UTF-8')); 49 | return this; 50 | } 51 | 52 | public GoogleRequestBuilder setParameter(String key, Integer value) { 53 | this.parameters.put(key, EncodingUtil.urlEncode(String.valueOf(value), 'UTF-8')); 54 | return this; 55 | } 56 | 57 | public GoogleRequestBuilder setParameter(String key, Boolean value) { 58 | this.parameters.put(key, EncodingUtil.urlEncode(String.valueOf(value), 'UTF-8')); 59 | return this; 60 | } 61 | 62 | public GoogleRequestBuilder removeParameter(String key) { 63 | this.parameters.remove(key); 64 | return this; 65 | } 66 | 67 | public GoogleRequestBuilder setBody(String body) { 68 | this.bodyAsString = body; 69 | return this; 70 | } 71 | 72 | public GoogleRequestBuilder setBody(Blob body) { 73 | this.bodyAsBlob = body; 74 | return this; 75 | } 76 | 77 | public HTTPResponse send() { 78 | HttpRequest req = new HttpRequest(); 79 | req.setMethod(this.method); 80 | 81 | this.buildEndpointUrl(req); 82 | this.buildEndpointHeaders(req); 83 | this.buildEndpointBody(req); 84 | 85 | Http http = new Http(); 86 | return http.send(req); 87 | } 88 | 89 | private void buildEndpointUrl(HttpRequest req) { 90 | List parameterKeys = new List(this.parameters.keySet()); 91 | for (Integer i = 0; i < parameterKeys.size(); i++) { 92 | String parameterKey = parameterKeys.get(i); 93 | String parameterValue = this.parameters.get(parameterKey); 94 | 95 | if (i == 0) { 96 | this.endpoint += ('?' + parameterKey + '=' + parameterValue); 97 | } else { 98 | this.endpoint += ('&' + parameterKey + '=' + parameterValue); 99 | } 100 | } 101 | 102 | req.setEndpoint(this.endpoint); 103 | } 104 | 105 | private void buildEndpointHeaders(HttpRequest req) { 106 | for (String headerKey : this.headers.keySet()) { 107 | String headerParameter = this.headers.get(headerKey); 108 | req.setHeader(headerKey, headerParameter); 109 | } 110 | 111 | this.buildAuthorizationHeader(req); 112 | } 113 | 114 | private void buildAuthorizationHeader(HttpRequest req) { 115 | req.setHeader( 116 | 'Authorization', 117 | this.googleDriveInstance.credentials.tokenType + ' ' + this.googleDriveInstance.credentials.accessToken 118 | ); 119 | } 120 | 121 | private void buildEndpointBody(HttpRequest req) { 122 | if (String.isNotBlank(this.bodyAsString)) { 123 | req.setBody(this.bodyAsString); 124 | } else if (this.bodyAsBlob != null) { 125 | req.setBodyAsBlob(this.bodyAsBlob); 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /google-drive/classes/builders/GoogleMultipartFileBuilder.cls: -------------------------------------------------------------------------------- 1 | public class GoogleMultipartFileBuilder implements GoogleFileCreator { 2 | private GoogleRequestBuilder requestGoogleBuilder; 3 | private GoogleFileMetadataBuilder fileMetadataBuilder; 4 | 5 | private String dataContentType; 6 | private String dataTransferEncoding; 7 | private String dataContentBody; 8 | 9 | public GoogleMultipartFileBuilder(GoogleDrive googleDriveInstance, String endpoint, String method) { 10 | this.requestGoogleBuilder = new GoogleRequestBuilder(googleDriveInstance); 11 | this.fileMetadataBuilder = new GoogleFileMetadataBuilder(); 12 | 13 | this.requestGoogleBuilder.setEndpoint(endpoint); 14 | this.requestGoogleBuilder.setMethod(method); 15 | this.requestGoogleBuilder.setHeader('User-Agent', googleDriveInstance.userAgentName); 16 | this.requestGoogleBuilder.setHeader('Content-Type', this.buildMultipartContentType()); 17 | this.requestGoogleBuilder.setParameter('uploadType', 'multipart'); 18 | } 19 | 20 | public GoogleMultipartFileBuilder setContentLength(String contentLength) { 21 | this.requestGoogleBuilder.setHeader('Content-Length', contentLength); 22 | return this; 23 | } 24 | 25 | public GoogleMultipartFileBuilder setContentLength(Integer contentLength) { 26 | this.requestGoogleBuilder.setHeader('Content-Length', contentLength); 27 | return this; 28 | } 29 | 30 | public GoogleMultipartFileBuilder setFields(String fields) { 31 | this.requestGoogleBuilder.setParameter('fields', fields); 32 | return this; 33 | } 34 | 35 | public GoogleMultipartFileBuilder setSupportsAllDrives(Boolean includeAllDrives) { 36 | this.requestGoogleBuilder.setParameter('supportsAllDrives', includeAllDrives); 37 | return this; 38 | } 39 | 40 | public GoogleMultipartFileBuilder setFileName(String fileName) { 41 | this.fileMetadataBuilder.setFileProperty('name', fileName); 42 | return this; 43 | } 44 | 45 | public GoogleMultipartFileBuilder setMimeType(String mimeType) { 46 | this.fileMetadataBuilder.setFileProperty('mimeType', mimeType); 47 | return this; 48 | } 49 | 50 | public GoogleMultipartFileBuilder setParentFolders(List folderIds) { 51 | this.fileMetadataBuilder.setFileProperty('parents', folderIds); 52 | return this; 53 | } 54 | 55 | public GoogleMultipartFileBuilder setBody(String contentType, String transferEncoding, String body) { 56 | this.dataContentType = contentType; 57 | this.dataTransferEncoding = transferEncoding; 58 | this.dataContentBody = body; 59 | return this; 60 | } 61 | 62 | public GoogleMultipartFileBuilder setBody(String body) { 63 | this.dataContentType = 'application/octet-stream'; 64 | this.dataTransferEncoding = 'base64'; 65 | this.dataContentBody = body; 66 | return this; 67 | } 68 | 69 | public GoogleFileEntity execute() { 70 | this.requestGoogleBuilder.setBody(this.buildMultipartRequestBody()); 71 | 72 | HTTPResponse uploadResponse = this.requestGoogleBuilder.send(); 73 | return this.retrieveRequestCreateWrapper(uploadResponse); 74 | } 75 | 76 | private GoogleFileEntity retrieveRequestCreateWrapper(HTTPResponse uploadResponse) { 77 | if (GoogleConstants.HTTP_SUCCESS_STATUS_CODES.contains(uploadResponse.getStatusCode())) { 78 | return (GoogleFileEntity) JSON.deserialize(uploadResponse.getBody(), GoogleFileEntity.class); 79 | } else { 80 | throw new GoogleCloudException(uploadResponse.getBody()); 81 | } 82 | } 83 | 84 | private String buildMultipartContentType() { 85 | return String.format( 86 | 'multipart/{0};boundary={1}', 87 | new List{ 88 | GoogleConstants.MULTIPART_DEFAULT_TYPE, 89 | GoogleConstants.MULTIPART_REQUEST_BOUNDARY 90 | } 91 | ); 92 | } 93 | 94 | private String buildMultipartRequestBody() { 95 | return String.format( 96 | '--{0}\r\n' + 97 | 'Content-Type: application/json; charset=UTF-8\r\n\r\n' + 98 | '{1}\r\n' + 99 | '--{0}\r\n' + 100 | 'Content-Type: {2}\r\n' + 101 | 'Content-Transfer-Encoding: {3}\r\n\r\n' + 102 | '{4}\r\n' + 103 | '--{0}--', 104 | new List{ 105 | GoogleConstants.MULTIPART_REQUEST_BOUNDARY, 106 | this.fileMetadataBuilder.build(), 107 | this.dataContentType, 108 | this.dataTransferEncoding, 109 | this.dataContentBody 110 | } 111 | ); 112 | } 113 | } -------------------------------------------------------------------------------- /google-drive/classes/GoogleDrive.cls: -------------------------------------------------------------------------------- 1 | public without sharing class GoogleDrive { 2 | public GoogleCredential credentials; 3 | public String userAgentName; 4 | 5 | private DriveList driveStream; 6 | private FileList fileStream; 7 | 8 | public GoogleDrive(GoogleCredential credentials, String applicationName) { 9 | this.credentials = credentials; 10 | this.userAgentName = applicationName; 11 | 12 | this.driveStream = new DriveList(this); 13 | this.fileStream = new FileList(this); 14 | } 15 | 16 | public DriveList drives() { 17 | return driveStream; 18 | } 19 | 20 | public FileList files() { 21 | return fileStream; 22 | } 23 | 24 | public GooglePermissionFileFactory permissions() { 25 | return new GooglePermissionFileFactory(this); 26 | } 27 | 28 | 29 | public class DriveList { 30 | private GoogleDrive googleDriveInstance; 31 | public DriveList(GoogleDrive googleDriveInstance) { 32 | this.googleDriveInstance = googleDriveInstance; 33 | } 34 | 35 | public GoogleDriveSearchBuilder search() { 36 | return new GoogleDriveSearchBuilder( 37 | googleDriveInstance, 38 | GoogleConstants.SEARCH_DRIVES_ENDPOINT, 39 | 'GET' 40 | ); 41 | } 42 | 43 | public GoogleDriveSearchBuilder search(String nextPageToken) { 44 | GoogleDriveSearchBuilder searchBuilder = new GoogleDriveSearchBuilder( 45 | googleDriveInstance, 46 | GoogleConstants.SEARCH_DRIVES_ENDPOINT, 47 | 'GET' 48 | ); 49 | 50 | searchBuilder.setNextPageToken(nextPageToken); 51 | return searchBuilder; 52 | } 53 | } 54 | 55 | public class FileList { 56 | private GoogleDrive googleDriveInstance; 57 | public FileList(GoogleDrive googleDriveInstance) { 58 | this.googleDriveInstance = googleDriveInstance; 59 | } 60 | 61 | public GoogleFileSearchBuilder search() { 62 | return new GoogleFileSearchBuilder( 63 | googleDriveInstance, 64 | GoogleConstants.SEARCH_FILES_ENDPOINT, 65 | 'GET' 66 | ); 67 | } 68 | 69 | public GoogleFileSearchBuilder search(String nextPageToken) { 70 | GoogleFileSearchBuilder searchBuilder = new GoogleFileSearchBuilder( 71 | googleDriveInstance, 72 | GoogleConstants.SEARCH_FILES_ENDPOINT, 73 | 'GET' 74 | ); 75 | 76 | searchBuilder.setNextPageToken(nextPageToken); 77 | return searchBuilder; 78 | } 79 | 80 | /** 81 | * Use this upload type to transfer a small media file (5 MB or less) without supplying metadata. 82 | */ 83 | public GoogleSimpleFileBuilder simpleCreate() { 84 | return new GoogleSimpleFileBuilder( 85 | googleDriveInstance, 86 | GoogleConstants.UPLOAD_FILES_ENDPOINT, 87 | 'POST' 88 | ); 89 | } 90 | 91 | /** 92 | * Use this upload type to transfer a small file (5 MB or less) 93 | * along with metadata that describes the file, in a single request. 94 | */ 95 | public GoogleMultipartFileBuilder multipartCreate() { 96 | return new GoogleMultipartFileBuilder( 97 | googleDriveInstance, 98 | GoogleConstants.UPLOAD_FILES_ENDPOINT, 99 | 'POST' 100 | ); 101 | } 102 | 103 | /** 104 | * Use this upload type for large files (greater than 5 MB) that include metadata and are uploaded in chunks. 105 | * Each chunk must be represented as a split blob to be sent in the body of the HTTP request. 106 | */ 107 | public GoogleResumableFileBuilder resumableCreate() { 108 | return new GoogleResumableFileBuilder( 109 | googleDriveInstance, 110 | GoogleConstants.UPLOAD_FILES_ENDPOINT, 111 | 'POST' 112 | ); 113 | } 114 | 115 | public GoogleRetrieveFileFactory retrieve() { 116 | return new GoogleRetrieveFileFactory(googleDriveInstance); 117 | } 118 | 119 | public GoogleCloneFileBuilder clone(String fileId) { 120 | return new GoogleCloneFileBuilder( 121 | googleDriveInstance, 122 | fileId, 123 | GoogleConstants.CLONE_FILE_ENDPOINT, 124 | 'POST' 125 | ); 126 | } 127 | 128 | public GoogleDeleteFileBuilder remove(String fileId) { 129 | return new GoogleDeleteFileBuilder( 130 | googleDriveInstance, 131 | fileId, 132 | GoogleConstants.DELETE_FILE_ENDPOINT, 133 | 'DELETE' 134 | ); 135 | } 136 | 137 | // Uses Drive API v2. In v3, this is done via files.update with trashed=true 138 | public GoogleTrashFileBuilder trash(String fileId) { 139 | return new GoogleTrashFileBuilder( 140 | googleDriveInstance, 141 | fileId, 142 | GoogleConstants.TRASH_FILE_ENDPOINT 143 | ); 144 | } 145 | 146 | // Deprecated. Use GoogleDrive.permissions instead. 147 | public GooglePermissionFileFactory permission() { 148 | return new GooglePermissionFileFactory(googleDriveInstance); 149 | } 150 | } 151 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

3 | 4 | View Demo 5 | 6 | 7 | Report Bug 8 | 9 | 10 | Request Feature 11 | 12 |

13 | 14 | [![Watch on GitHub](https://img.shields.io/github/watchers/sandriiy/salesforce-google-drive-library.svg?style=social)](https://github.com/sandriiy/salesforce-google-drive-library/watchers) 15 | [![Star on GitHub](https://img.shields.io/github/stars/sandriiy/salesforce-google-drive-library.svg?style=social)](https://github.com/sandriiy/salesforce-google-drive-library/stargazers) 16 |
17 | 18 | ## Getting Started 19 | 20 | The Salesforce Apex Google Drive Library offers programmatic access to Google Drive through API methods. This library simplifies coding against these APIs by providing robust methods for creating, cloning, downloading, sharing, and searching files, drives and permissions. Its implementation is accompanied by a newer version of the Google Drive API v3. You can read about the benefits [here](https://developers.google.com/drive/api/guides/v3versusv2) 21 | 22 | You can find the integration configuration, including both the Google Cloud and Salesforce sides, along with all the methods, details, and challenges, in the Wiki of this repository at the [following link](https://github.com/sandriiy/salesforce-google-drive-library/wiki/Quick-Setup-Guide) 23 | 24 | To get started with the Apex Google Drive library, its code needs to be deployed to your environment. All the code can either be deployed directly, contained in the `google-drive` folder and fully self-contained, or the Unlocked Package can be installed for a more modular setup of the library code. If the Unlocked Package is of interest, the two buttons below, depending on the environment, can be used to install the latest version: 25 | 26 | 34 |
35 | 36 | > [!NOTE] 37 | > The complete library documentation is available on the GitHub Wiki, organized into separate pages by category. Each example includes a full set of methods available for use. 38 | 39 |
40 | 41 | ## Usage Guide 42 | 43 | To begin using this library, you first need to set up the Google Drive integration. This includes enabling the Google Drive API in the Google Admin Console, creating a Service Account, obtaining a key, and generating a certificate to upload into Salesforce. All steps are outlined in the Quick Setup Guide 44 | 45 | Once the setup is complete, the entry point is to instantiate the `GoogleDrive` class, which provides all builders, factories, and methods for interacting with the Google Drive API in an object-oriented manner. Detailed instructions for creating this instance are available on the Library Authorization Flow page 46 | 47 | Through this instance, you gain access to three main categories: `files`, `drives`, and `permissions`. Each category exposes a dedicated set of operations. The full hierarchy of available methods is shown below. 48 | 49 | - **.files()** 50 | - **.search()** – `GoogleFileSearchBuilder` 51 | - **.search(String nextPageToken)** – `GoogleFileSearchBuilder` 52 | - **.simpleCreate()** – `GoogleSimpleFileBuilder` 53 | - **.multipartCreate()** – `GoogleMultipartFileBuilder` 54 | - **.resumableCreate()** – `GoogleResumableFileBuilder` 55 | - **.retrieve()** – `GoogleRetrieveFileFactory` 56 | - **.download()** – `GoogleDownloadFileBuilder` 57 | - **.export()** – `GoogleExportFileBuilder` 58 | - **.clone(String fileId)** – `GoogleCloneFileBuilder` 59 | - **.remove(String fileId)** – `GoogleDeleteFileBuilder` 60 | - **.trash(String fileId)** – `GoogleTrashFileBuilder` 61 | - **.drives()** 62 | - **.search()** – `GoogleDriveSearchBuilder` 63 | - **.search(String nextPageToken)** – `GoogleDriveSearchBuilder` 64 | - **.permissions()** 65 | - **.create(String fileId)** – `GoogleCreatePermissionFileBuilder` 66 | - **.remove(String fileId, String permissionId)** – `GoogleDeletePermissionFileBuilder` 67 | - **.search(String fileId)** – `GooglePermissionSearchBuilder` 68 | 69 | ## Acknowledgments 70 | 71 | * https://github.com/sandriiy/salesforce-google-drive-library/wiki 72 | * https://developers.google.com/drive/api/reference/rest/v3 73 | * https://developers.google.com/api-client-library 74 | * https://www.oracle.com/corporate/features/library-in-java-best-practices.html 75 | 76 | -------------------------------------------------------------------------------- /google-drive/classes/entities/GoogleFileEntity.cls: -------------------------------------------------------------------------------- 1 | public class GoogleFileEntity { 2 | @AuraEnabled public String kind; 3 | public String driveId; 4 | @AuraEnabled public String fileExtension; 5 | public String body; 6 | public Blob bodyAsBlob; 7 | public Boolean copyRequiresWriterPermission; 8 | public String md5Checksum; 9 | public Boolean writersCanShare; 10 | public Boolean viewedByMe; 11 | @AuraEnabled public String mimeType; 12 | public Map exportLinks; 13 | @AuraEnabled public List parents; 14 | public String thumbnailLink; 15 | public String iconLink; 16 | public Boolean shared; 17 | public User lastModifyingUser; 18 | public List owners; 19 | public String headRevisionId; 20 | public User sharingUser; 21 | public String webViewLink; 22 | public String webContentLink; 23 | public String size; 24 | public Boolean viewersCanCopyContent; 25 | public List permissions; 26 | public Boolean hasThumbnail; 27 | public List spaces; 28 | public String folderColorRgb; 29 | @AuraEnabled public String id; 30 | @AuraEnabled public String name; 31 | @AuraEnabled public String description; 32 | public Boolean starred; 33 | public Boolean trashed; 34 | public Boolean explicitlyTrashed; 35 | public String createdTime; 36 | public String modifiedTime; 37 | public String modifiedByMeTime; 38 | public String viewedByMeTime; 39 | public String sharedWithMeTime; 40 | public String quotaBytesUsed; 41 | public String version; 42 | public String originalFilename; 43 | public Boolean ownedByMe; 44 | public String fullFileExtension; 45 | public Boolean isAppAuthorized; 46 | public String teamDriveId; 47 | public Capabilities capabilities; 48 | public Boolean hasAugmentedPermissions; 49 | public User trashingUser; 50 | public String thumbnailVersion; 51 | public String trashedTime; 52 | public Boolean modifiedByMe; 53 | public List permissionIds; 54 | public ImageMediaMetadata imageMediaMetadata; 55 | public VideoMediaMetadata videoMediaMetadata; 56 | public ShortcutDetails shortcutDetails; 57 | public String resourceKey; 58 | public LinkShareMetadata linkShareMetadata; 59 | public String sha1Checksum; 60 | public String sha256Checksum; 61 | 62 | public class User { 63 | public String displayName; 64 | public String kind; 65 | public Boolean me; 66 | public String permissionId; 67 | public String emailAddress; 68 | public String photoLink; 69 | } 70 | 71 | public class Permission { 72 | public String id; 73 | public String displayName; 74 | public String type; 75 | public String kind; 76 | public List permissionDetails; 77 | public String photoLink; 78 | public String emailAddress; 79 | public String role; 80 | public Boolean allowFileDiscovery; 81 | public String domain; 82 | public String expirationTime; 83 | public List teamDrivePermissionDetails; 84 | public Boolean deleted; 85 | public String view; 86 | public Boolean pendingOwner; 87 | } 88 | 89 | public class PermissionDetails { 90 | public String permissionType; 91 | public String inheritedFrom; 92 | public String role; 93 | public Boolean inherited; 94 | } 95 | 96 | public class TeamDrivePermissionDetails { 97 | public String teamDrivePermissionType; 98 | public String inheritedFrom; 99 | public String role; 100 | public Boolean inherited; 101 | } 102 | 103 | public class Capabilities { 104 | public Boolean canChangeViewersCanCopyContent; 105 | public Boolean canMoveChildrenOutOfDrive; 106 | public Boolean canReadDrive; 107 | public Boolean canEdit; 108 | public Boolean canCopy; 109 | public Boolean canComment; 110 | public Boolean canAddChildren; 111 | public Boolean canDelete; 112 | public Boolean canDownload; 113 | public Boolean canListChildren; 114 | public Boolean canRemoveChildren; 115 | public Boolean canRename; 116 | public Boolean canTrash; 117 | public Boolean canReadRevisions; 118 | public Boolean canReadTeamDrive; 119 | public Boolean canMoveTeamDriveItem; 120 | public Boolean canChangeCopyRequiresWriterPermission; 121 | public Boolean canMoveItemIntoTeamDrive; 122 | public Boolean canUntrash; 123 | public Boolean canModifyContent; 124 | public Boolean canMoveItemWithinTeamDrive; 125 | public Boolean canMoveItemOutOfTeamDrive; 126 | public Boolean canDeleteChildren; 127 | public Boolean canMoveChildrenOutOfTeamDrive; 128 | public Boolean canMoveChildrenWithinTeamDrive; 129 | public Boolean canTrashChildren; 130 | public Boolean canMoveItemOutOfDrive; 131 | public Boolean canAddMyDriveParent; 132 | public Boolean canRemoveMyDriveParent; 133 | public Boolean canMoveItemWithinDrive; 134 | public Boolean canShare; 135 | public Boolean canMoveChildrenWithinDrive; 136 | public Boolean canModifyContentRestriction; 137 | public Boolean canAddFolderFromAnotherDrive; 138 | public Boolean canChangeSecurityUpdateEnabled; 139 | public Boolean canAcceptOwnership; 140 | public Boolean canReadLabels; 141 | public Boolean canModifyLabels; 142 | public Boolean canModifyEditorContentRestriction; 143 | public Boolean canModifyOwnerContentRestriction; 144 | public Boolean canRemoveContentRestriction; 145 | } 146 | 147 | public class ImageMediaMetadata { 148 | public Boolean flashUsed; 149 | public String meteringMode; 150 | public String sensor; 151 | public String exposureMode; 152 | public String colorSpace; 153 | public String whiteBalance; 154 | public Integer width; 155 | public Integer height; 156 | public Location location; 157 | public Integer rotation; 158 | public String cameraMake; 159 | public String cameraModel; 160 | public Double exposureTime; 161 | public Double aperture; 162 | public Double focalLength; 163 | public Integer isoSpeed; 164 | public Double exposureBias; 165 | public Double maxApertureValue; 166 | public Integer subjectDistance; 167 | public String lens; 168 | } 169 | 170 | public class Location { 171 | public Double latitude; 172 | public Double longitude; 173 | public Double altitude; 174 | } 175 | 176 | public class VideoMediaMetadata { 177 | public Integer width; 178 | public Integer height; 179 | public String durationMillis; 180 | } 181 | 182 | public class ShortcutDetails { 183 | public String targetId; 184 | public String targetMimeType; 185 | public String targetResourceKey; 186 | } 187 | 188 | public class LinkShareMetadata { 189 | public Boolean securityUpdateEligible; 190 | public Boolean securityUpdateEnabled; 191 | } 192 | } -------------------------------------------------------------------------------- /google-drive/classes/builders/GoogleResumableFileBuilder.cls: -------------------------------------------------------------------------------- 1 | public with sharing class GoogleResumableFileBuilder { 2 | private GoogleRequestBuilder requestGoogleBuilder; 3 | private GoogleFileMetadataBuilder fileMetadataBuilder; 4 | 5 | private Blob bodyChunk; 6 | private String sessionUri; 7 | private Long initialByte; 8 | private Long totalBytes; 9 | 10 | public GoogleResumableFileBuilder(GoogleDrive googleDriveInstance, String endpoint, String method) { 11 | this.requestGoogleBuilder = new GoogleRequestBuilder(googleDriveInstance); 12 | this.fileMetadataBuilder = new GoogleFileMetadataBuilder(); 13 | 14 | this.requestGoogleBuilder.setEndpoint(endpoint); 15 | this.requestGoogleBuilder.setMethod(method); 16 | this.requestGoogleBuilder.setHeader('User-Agent', googleDriveInstance.userAgentName); 17 | this.requestGoogleBuilder.setHeader('Content-Type', 'application/json; charset=UTF-8'); 18 | this.requestGoogleBuilder.setParameter('uploadType', 'resumable'); 19 | } 20 | 21 | public GoogleResumableFileBuilder setStartByte(Integer initialByte) { 22 | this.initialByte = initialByte; 23 | return this; 24 | } 25 | 26 | public GoogleResumableFileBuilder setStartByte(Long initialByte) { 27 | this.initialByte = initialByte; 28 | return this; 29 | } 30 | 31 | public GoogleResumableFileBuilder setTotalBytes(Integer totalBytes) { 32 | this.totalBytes = totalBytes; 33 | return this; 34 | } 35 | 36 | public GoogleResumableFileBuilder setTotalBytes(Long totalBytes) { 37 | this.totalBytes = totalBytes; 38 | return this; 39 | } 40 | 41 | public GoogleResumableFileBuilder setExistingSessionUri(String sessionUri) { 42 | this.sessionUri = sessionUri; 43 | return this; 44 | } 45 | 46 | public GoogleResumableFileBuilder setFields(String fields) { 47 | this.requestGoogleBuilder.setParameter('fields', fields); 48 | return this; 49 | } 50 | 51 | public GoogleResumableFileBuilder setSupportsAllDrives(Boolean includeAllDrives) { 52 | this.requestGoogleBuilder.setParameter('supportsAllDrives', includeAllDrives); 53 | return this; 54 | } 55 | 56 | public GoogleResumableFileBuilder setFileName(String fileName) { 57 | this.fileMetadataBuilder.setFileProperty('name', fileName); 58 | return this; 59 | } 60 | 61 | public GoogleResumableFileBuilder setMimeType(String mimeType) { 62 | this.fileMetadataBuilder.setFileProperty('mimeType', mimeType); 63 | return this; 64 | } 65 | 66 | public GoogleResumableFileBuilder setParentFolders(List folderIds) { 67 | this.fileMetadataBuilder.setFileProperty('parents', folderIds); 68 | return this; 69 | } 70 | 71 | public GoogleResumableFileBuilder setBody(Blob bodyPart) { 72 | this.bodyChunk = bodyPart; 73 | return this; 74 | } 75 | 76 | public GoogleResumableFileBuilder setBody(String bodyPart) { 77 | this.bodyChunk = EncodingUtil.base64Decode(bodyPart); 78 | return this; 79 | } 80 | 81 | public GoogleBigFileEntity initialize() { 82 | String body = this.fileMetadataBuilder.build(); 83 | this.requestGoogleBuilder.setBody(body); 84 | this.requestGoogleBuilder.setHeader('Content-Length', body.length()); 85 | HTTPResponse uploadResponse = this.requestGoogleBuilder.send(); 86 | 87 | GoogleBigFileEntity initialBigFile = this.retrieveRequestInitializeWrapper(uploadResponse); 88 | this.sessionUri = initialBigFile.resumableSessionId; 89 | return initialBigFile; 90 | } 91 | 92 | public GoogleBigFileEntity execute() { 93 | this.assertValidState(); 94 | 95 | GoogleFileEntity uploadedFile = this.uploadFileChunk(); 96 | if (uploadedFile == null) { 97 | GoogleBigFileEntity partiallyUploadedBigFile = new GoogleBigFileEntity(); 98 | partiallyUploadedBigFile.resumableSessionId = this.sessionUri; 99 | partiallyUploadedBigFile.resumableLatestByte = this.initialByte; 100 | return partiallyUploadedBigFile; 101 | } else { 102 | GoogleBigFileEntity fullyUploadedBigFile = new GoogleBigFileEntity(); 103 | fullyUploadedBigFile.file = uploadedFile; 104 | return fullyUploadedBigFile; 105 | } 106 | } 107 | 108 | 109 | private GoogleFileEntity uploadFileChunk() { 110 | GoogleRequestBuilder partRequestGoogleBuilder = this.requestGoogleBuilder.duplicate(); 111 | partRequestGoogleBuilder.setEndpoint(this.sessionUri); 112 | partRequestGoogleBuilder.setMethod('PUT'); 113 | partRequestGoogleBuilder.setHeader('Content-Length', this.bodyChunk.size()); 114 | partRequestGoogleBuilder.setHeader('Content-Range', this.buildPartContentRange(this.bodyChunk.size())); 115 | partRequestGoogleBuilder.setBody(this.bodyChunk); 116 | 117 | HTTPResponse chunkUploadResponse = partRequestGoogleBuilder.send(); 118 | if (GoogleConstants.HTTP_INTERRUPTED_STATUS_CODES.contains(chunkUploadResponse.getStatusCode())) { 119 | String uploadedChunkRange = chunkUploadResponse.getHeader('Range'); 120 | this.calculateUploadedBytes(uploadedChunkRange); 121 | return null; 122 | } else { 123 | return this.retrieveRequestCreateWrapper(chunkUploadResponse); 124 | } 125 | } 126 | 127 | private GoogleBigFileEntity retrieveRequestInitializeWrapper(HTTPResponse uploadResponse) { 128 | if (GoogleConstants.HTTP_SUCCESS_STATUS_CODES.contains(uploadResponse.getStatusCode())) { 129 | GoogleBigFileEntity initializedBigFile = new GoogleBigFileEntity(); 130 | initializedBigFile.resumableSessionId = uploadResponse.getHeader('Location'); 131 | initializedBigFile.resumableLatestByte = GoogleConstants.UPLOAD_DEFAULT_INITIAL_BYTE; 132 | return initializedBigFile; 133 | } else { 134 | throw new GoogleCloudException(uploadResponse.getBody()); 135 | } 136 | } 137 | 138 | private GoogleFileEntity retrieveRequestCreateWrapper(HTTPResponse uploadResponse) { 139 | if (GoogleConstants.HTTP_SUCCESS_STATUS_CODES.contains(uploadResponse.getStatusCode())) { 140 | return (GoogleFileEntity) JSON.deserialize(uploadResponse.getBody(), GoogleFileEntity.class); 141 | } else { 142 | throw new GoogleCloudException(uploadResponse.getBody()); 143 | } 144 | } 145 | 146 | private String buildPartContentRange(Long partSize) { 147 | if (this.initialByte == null && this.totalBytes == null) { 148 | return '*/*'; 149 | } else if (this.initialByte == null) { 150 | return '*/' + this.totalBytes; 151 | } else { 152 | Long startByte = this.initialByte; 153 | Long endByte = this.initialByte + partSize - 1; 154 | 155 | String totalBytes = this.totalBytes != null ? String.valueOf(this.totalBytes) : '*'; 156 | return 'bytes ' + startByte + '-' + endByte + '/' + totalBytes; 157 | } 158 | } 159 | 160 | private void calculateUploadedBytes(String uploadedRange) { 161 | if (String.isNotBlank(uploadedRange)) { 162 | String pattern = '\\d+$'; // Matches one or more digits at the end of the string 163 | Pattern regex = System.Pattern.compile(pattern); 164 | Matcher matcher = regex.matcher(uploadedRange); 165 | 166 | String lastInteger = ''; 167 | if (matcher.find()) { 168 | lastInteger = matcher.group(0); 169 | } 170 | 171 | this.initialByte = String.isNotBlank(lastInteger) ? Long.valueOf(lastInteger) : 0; 172 | } 173 | } 174 | 175 | private void assertValidState() { 176 | // Initialize the upload if no unique resource identifier is specified 177 | if (String.isBlank(this.sessionUri)) { 178 | this.initialize(); 179 | } 180 | 181 | // Fail fast: How can you upload a file chunk if there is no body for it? 182 | if (this.bodyChunk == null || this.bodyChunk.size() == 0) { 183 | throw new IllegalArgumentException('File chunk body is missing. Set the body using `setBody()` before sending'); 184 | } 185 | } 186 | } -------------------------------------------------------------------------------- /google-drive/classes/GoogleDriveTest.cls: -------------------------------------------------------------------------------- 1 | @isTest 2 | private class GoogleDriveTest { 3 | private static GoogleCredential testCredentials; 4 | private static String userAgentName; 5 | 6 | @isTest 7 | private static void testSupportsAllDrivesFlagOnFileDelete() { 8 | String testFileId = '1lhu72ZrlfzRljhP4t12RE5GDGa8n7Yv8LHWy20_QBqw'; 9 | GoogleDriveHttpMockGenerator testCalloutMock = new GoogleDriveHttpMockGenerator( 10 | buildFileDependentEndpoint(GoogleConstants.DELETE_FILE_ENDPOINT, testFileId), 11 | 204, 12 | '' 13 | ); 14 | 15 | Test.startTest(); 16 | Test.setMock(HttpCalloutMock.class, testCalloutMock); 17 | buildGoogleDriveInfo(); 18 | 19 | GoogleDrive testGoogleDrive = new GoogleDrive(testCredentials, userAgentName); 20 | testGoogleDrive.files().remove(testFileId) 21 | .setSupportsAllDrives(true) // Lets this request target files residing in Shared Drives 22 | .execute(); 23 | Test.stopTest(); 24 | } 25 | 26 | @isTest 27 | private static void testSupportsAllDrivesFlagOnFileTrash() { 28 | String testFileId = '1lhu72ZrlfzRljhP4t12RE5GDGa8n7Yv8LHWy20_QBqw'; 29 | GoogleDriveHttpMockGenerator testCalloutMock = new GoogleDriveHttpMockGenerator( 30 | buildFileDependentEndpoint(GoogleConstants.TRASH_FILE_ENDPOINT, testFileId), 31 | 200, 32 | '' 33 | ); 34 | 35 | Test.startTest(); 36 | Test.setMock(HttpCalloutMock.class, testCalloutMock); 37 | buildGoogleDriveInfo(); 38 | 39 | GoogleDrive testGoogleDrive = new GoogleDrive(testCredentials, userAgentName); 40 | testGoogleDrive.files().trash(testFileId) 41 | .setSupportsAllDrives(true) // Lets this request target files residing in Shared Drives 42 | .execute(); 43 | Test.stopTest(); 44 | } 45 | 46 | @isTest 47 | private static void testSupportsAllDrivesOnFileSearch() { 48 | String fullFileSearchBody = '{"kind": "drive#fileList","incompleteSearch": false,"files": [{"kind": "drive#file","mimeType": "application/vnd.google-apps.folder","id": "1dmEbuynf_W6064Acrx8RrpqU4EL60mRs","name": "Test"}]}'; 49 | GoogleDriveHttpMockGenerator testCalloutMock = new GoogleDriveHttpMockGenerator( 50 | GoogleConstants.SEARCH_FILES_ENDPOINT, 51 | 200, 52 | fullFileSearchBody 53 | ); 54 | 55 | Test.startTest(); 56 | Test.setMock(HttpCalloutMock.class, testCalloutMock); 57 | buildGoogleDriveInfo(); 58 | 59 | GoogleDrive testGoogleDrive = new GoogleDrive(testCredentials, userAgentName); 60 | GoogleFileSearchResult result = testGoogleDrive.files().search() 61 | .setSearchQuery('trashed = false') 62 | .setSearchOnAllDrives(true) // Enables searching items that live inside Shared Drives 63 | .setMaxResult(1) 64 | .execute(); 65 | Assert.areEqual(1, result.files.size()); 66 | Test.stopTest(); 67 | } 68 | 69 | @isTest 70 | private static void testSupportsAllDrivesOnPermissionList() { 71 | String testFileId = '1lhu72ZrlfzRljhP4t12RE5GDGa8n7Yv8LHWy20_QBqw'; 72 | String permissionsSearchBody = '{"permissions":[{"id":"0123456789abcdef","emailAddress":"user@example.com"}],"kind":"drive#permissionList"}'; 73 | GoogleDriveHttpMockGenerator testCalloutMock = new GoogleDriveHttpMockGenerator( 74 | buildFileDependentEndpoint(GoogleConstants.LIST_PERMISSIONS_FILE_ENDPOINT, testFileId), 75 | 200, 76 | permissionsSearchBody 77 | ); 78 | 79 | Test.startTest(); 80 | Test.setMock(HttpCalloutMock.class, testCalloutMock); 81 | buildGoogleDriveInfo(); 82 | 83 | GoogleDrive testGoogleDrive = new GoogleDrive(testCredentials, userAgentName); 84 | GooglePermissionSearchResult result = testGoogleDrive.permissions().search(testFileId) 85 | .setSearchOnAllDrives(true) // Allow permission introspection on Shared Drive items 86 | .setMaxResult(10) 87 | .execute(); 88 | 89 | Assert.areEqual(1, result.permissions.size()); 90 | Test.stopTest(); 91 | } 92 | 93 | @isTest 94 | private static void testFailWhenSharedDriveNotSpecified_FileSearch() { 95 | String failedFileSearchBody = '{"error": {"errors": [{"domain": "global","reason": "required","message": "shared drive not specified"}],"code": 400,"message": "shared drive not specified"}}'; 96 | GoogleDriveHttpMockGenerator testCalloutMock = new GoogleDriveHttpMockGenerator( 97 | GoogleConstants.SEARCH_FILES_ENDPOINT, 98 | 400, 99 | failedFileSearchBody 100 | ); 101 | 102 | Test.startTest(); 103 | Test.setMock(HttpCalloutMock.class, testCalloutMock); 104 | buildGoogleDriveInfo(); 105 | 106 | GoogleDrive testGoogleDrive = new GoogleDrive(testCredentials, userAgentName); 107 | try { 108 | GoogleFileSearchResult result = testGoogleDrive.files().search() 109 | .setSearchQuery('name contains "Test"') 110 | .execute(); 111 | System.assert(false, 'Exception expected'); 112 | } catch (GoogleCloudException ex) { 113 | // API returns 400 when querying Shared Drive scope without indicating which drive or enabling all drives 114 | Assert.areEqual(failedFileSearchBody, ex.getMessage()); 115 | } 116 | Test.stopTest(); 117 | } 118 | 119 | @isTest 120 | private static void testFailWhenSharedDriveNotSpecified_PermissionsList() { 121 | String testFileId = '1lhu72ZrlfzRljhP4t12RE5GDGa8n7Yv8LHWy20_QBqw'; 122 | String failedPermissionsBody = '{"error": {"errors": [{"domain": "global","reason": "required","message": "shared drive not specified"}],"code": 400,"message": "shared drive not specified"}}'; 123 | GoogleDriveHttpMockGenerator testCalloutMock = new GoogleDriveHttpMockGenerator( 124 | buildFileDependentEndpoint(GoogleConstants.LIST_PERMISSIONS_FILE_ENDPOINT, testFileId), 125 | 400, 126 | failedPermissionsBody 127 | ); 128 | 129 | Test.startTest(); 130 | Test.setMock(HttpCalloutMock.class, testCalloutMock); 131 | buildGoogleDriveInfo(); 132 | 133 | GoogleDrive testGoogleDrive = new GoogleDrive(testCredentials, userAgentName); 134 | try { 135 | GooglePermissionSearchResult result = testGoogleDrive.permissions().search(testFileId).execute(); 136 | System.assert(false, 'Exception expected'); 137 | } catch (GoogleCloudException ex) { 138 | // API expects Shared Drive awareness for permission listings of Shared Drive files 139 | Assert.areEqual(failedPermissionsBody, ex.getMessage()); 140 | } 141 | Test.stopTest(); 142 | } 143 | 144 | @isTest 145 | private static void testFullDriveSearch() { 146 | String fullDriveSearchBody = '{"kind":"drive#fileList","drives":[{"id":"drive123","name":"My Drive","colorRgb":"#FFFFFF","kind":"drive#drive","backgroundImageLink":"https://example.com/image.png","capabilities":{"canAddChildren":true,"canComment":true,"canCopy":true,"canDeleteDrive":false,"canDownload":true,"canEdit":true,"canListChildren":true,"canManageMembers":true,"canReadRevisions":true,"canRename":true,"canRenameDrive":true,"canChangeDriveBackground":true,"canShare":true,"canChangeCopyRequiresWriterPermissionRestriction":false,"canChangeDomainUsersOnlyRestriction":false,"canChangeDriveMembersOnlyRestriction":false,"canChangeSharingFoldersRequiresOrganizerPermissionRestriction":false,"canResetDriveRestrictions":false,"canDeleteChildren":false,"canTrashChildren":true},"themeId":"theme123","backgroundImageFile":{"id":"image123","xCoordinate":100.0,"yCoordinate":50.0,"width":800.0},"createdTime":"2023-06-15T10:30:00Z","hidden":false,"restrictions":{"copyRequiresWriterPermission":true,"domainUsersOnly":false,"driveMembersOnly":true,"adminManagedRestrictions":false,"sharingFoldersRequiresOrganizerPermission":true},"orgUnitId":"org123"},{"id":"drive456","name":"Not Populated Drive"}]}'; 147 | GoogleDriveHttpMockGenerator testCalloutMock = new GoogleDriveHttpMockGenerator( 148 | GoogleConstants.SEARCH_DRIVES_ENDPOINT, 149 | 200, 150 | fullDriveSearchBody 151 | ); 152 | 153 | Test.startTest(); 154 | Test.setMock(HttpCalloutMock.class, testCalloutMock); 155 | buildGoogleDriveInfo(); 156 | 157 | GoogleDrive testGoogleDrive = new GoogleDrive(testCredentials, userAgentName); 158 | GoogleDriveSearchResult result = testGoogleDrive.drives().search() 159 | .setMaxResult(50) 160 | .setSearchQuery('') 161 | .setDomainAdminAccess(false) 162 | .execute(); 163 | Assert.areEqual(2, result.drives.size()); 164 | Test.stopTest(); 165 | } 166 | 167 | @isTest 168 | private static void testNextPageDriveSearch() { 169 | String nextPageDriveSearchBody = '{"nextPageToken": "2fd620046218c04c12709a67c4879de9","kind":"drive#fileList","drives":[{"id":"drive123","name":"My Drive","colorRgb":"#FFFFFF","kind":"drive#drive","backgroundImageLink":"https://example.com/image.png","capabilities":{"canAddChildren":true,"canComment":true,"canCopy":true,"canDeleteDrive":false,"canDownload":true,"canEdit":true,"canListChildren":true,"canManageMembers":true,"canReadRevisions":true,"canRename":true,"canRenameDrive":true,"canChangeDriveBackground":true,"canShare":true,"canChangeCopyRequiresWriterPermissionRestriction":false,"canChangeDomainUsersOnlyRestriction":false,"canChangeDriveMembersOnlyRestriction":false,"canChangeSharingFoldersRequiresOrganizerPermissionRestriction":false,"canResetDriveRestrictions":false,"canDeleteChildren":false,"canTrashChildren":true},"themeId":"theme123","backgroundImageFile":{"id":"image123","xCoordinate":100.0,"yCoordinate":50.0,"width":800.0},"createdTime":"2023-06-15T10:30:00Z","hidden":false,"restrictions":{"copyRequiresWriterPermission":true,"domainUsersOnly":false,"driveMembersOnly":true,"adminManagedRestrictions":false,"sharingFoldersRequiresOrganizerPermission":true},"orgUnitId":"org123"},{"id":"drive456","name":"Not Populated Drive"}]}'; 170 | GoogleDriveHttpMockGenerator testCalloutMock = new GoogleDriveHttpMockGenerator( 171 | GoogleConstants.SEARCH_DRIVES_ENDPOINT, 172 | 200, 173 | nextPageDriveSearchBody 174 | ); 175 | 176 | Test.startTest(); 177 | Test.setMock(HttpCalloutMock.class, testCalloutMock); 178 | buildGoogleDriveInfo(); 179 | 180 | GoogleDrive testGoogleDrive = new GoogleDrive(testCredentials, userAgentName); 181 | GoogleDriveSearchResult result = testGoogleDrive.drives().search('4mzkteXuXufI6lXV4mzkteXuXufI6lXV') 182 | .execute(); 183 | Assert.areEqual('2fd620046218c04c12709a67c4879de9', result.nextPageToken); 184 | Test.stopTest(); 185 | } 186 | 187 | @isTest 188 | private static void testDriveSearchFail() { 189 | String failedDriveSearchBody = '{"error": {"errors": [{"domain": "global","reason": "authError","message": "Invalid Credentials","locationType": "header","location": "Authorization"}],"code": 401,"message": "Invalid Credentials"}}'; 190 | GoogleDriveHttpMockGenerator testCalloutMock = new GoogleDriveHttpMockGenerator( 191 | GoogleConstants.SEARCH_DRIVES_ENDPOINT, 192 | GoogleConstants.HTTP_UNAUTHORIZED_STATUS_CODE, 193 | failedDriveSearchBody, 194 | new Map() 195 | ); 196 | 197 | Test.startTest(); 198 | Test.setMock(HttpCalloutMock.class, testCalloutMock); 199 | buildGoogleDriveInfo(); 200 | 201 | GoogleDrive testGoogleDrive = new GoogleDrive(testCredentials, userAgentName); 202 | try { 203 | GoogleDriveSearchResult result = testGoogleDrive.drives().search().execute(); 204 | } catch (GoogleCloudException ex) { 205 | Assert.areEqual(failedDriveSearchBody, ex.getMessage()); 206 | } 207 | 208 | Test.stopTest(); 209 | } 210 | 211 | @isTest 212 | private static void testNextPageFileSearch() { 213 | String nextPageFileSearchBody = '{"nextPageToken": "~!!~AI9FV7ThOnDGgvVJDf_o4en1NZxTE_2tX-FVRhM-0UKO3MxOQh-dMLY4EiA==","kind": "drive#fileList","incompleteSearch": true,"files": [{"kind": "drive#file","mimeType": "application/vnd.google-apps.folder","id": "1dmEbuynf_W6064Acrx8RrpqU4EL60mRs","name": "Test"},{"kind": "drive#file","mimeType": "application/vnd.google-apps.folder","id": "1porUCOPDqUHXji8jCRqfG4cjj1gmJTVL","name": "audio"},{"kind": "drive#file","mimeType": "video/quicktime","id": "1ZqgilKNUpNIwvAotA5ss7Wc6swBmApkD","name": "Test.MOV"}]}'; 214 | GoogleDriveHttpMockGenerator testCalloutMock = new GoogleDriveHttpMockGenerator( 215 | GoogleConstants.SEARCH_FILES_ENDPOINT, 216 | 200, 217 | nextPageFileSearchBody 218 | ); 219 | 220 | Test.startTest(); 221 | Test.setMock(HttpCalloutMock.class, testCalloutMock); 222 | buildGoogleDriveInfo(); 223 | 224 | GoogleDrive testGoogleDrive = new GoogleDrive(testCredentials, userAgentName); 225 | GoogleFileSearchResult result = testGoogleDrive.files().search('~!!~BI9FV7ThOnDGgvVJDf_o4en1NZxEOJxjGmloO1QwivWraJd4UKiAAiFaEyV==') 226 | .setDriveId('1QwraJd4UKiAAiFaEyV2worNpYLnbZd4') 227 | .setOrderBy('name') 228 | .execute(); 229 | Assert.areEqual('~!!~AI9FV7ThOnDGgvVJDf_o4en1NZxTE_2tX-FVRhM-0UKO3MxOQh-dMLY4EiA==', result.nextPageToken); 230 | Test.stopTest(); 231 | } 232 | 233 | @isTest 234 | private static void testFullFileSearch() { 235 | String fullFileSearchBody = '{"kind": "drive#fileList","incompleteSearch": false,"files": [{"kind": "drive#file","mimeType": "application/vnd.google-apps.folder","id": "1dmEbuynf_W6064Acrx8RrpqU4EL60mRs","name": "Test"},{"kind": "drive#file","mimeType": "application/vnd.google-apps.folder","id": "1porUCOPDqUHXji8jCRqfG4cjj1gmJTVL","name": "audio"},{"kind": "drive#file","mimeType": "video/quicktime","id": "1ZqgilKNUpNIwvAotA5ss7Wc6swBmApkD","name": "Test.MOV"}]}'; 236 | GoogleDriveHttpMockGenerator testCalloutMock = new GoogleDriveHttpMockGenerator( 237 | GoogleConstants.SEARCH_FILES_ENDPOINT, 238 | 200, 239 | fullFileSearchBody 240 | ); 241 | 242 | Test.startTest(); 243 | Test.setMock(HttpCalloutMock.class, testCalloutMock); 244 | buildGoogleDriveInfo(); 245 | 246 | GoogleDrive testGoogleDrive = new GoogleDrive(testCredentials, userAgentName); 247 | GoogleFileSearchResult result = testGoogleDrive.files().search() 248 | .setMaxResult(3) 249 | .setSearchQuery('trashed = false') 250 | .setSearchOnAllDrives(true) // Include items from Shared Drives in search results 251 | .setFields('fields/*') 252 | .setOrderBy('folder,modifiedTime desc,name') 253 | .execute(); 254 | Assert.areEqual(3, result.files.size()); 255 | Test.stopTest(); 256 | } 257 | 258 | @isTest 259 | private static void testFileSearchFail() { 260 | String failedFileSearchBody = '{"error": {"errors": [{"domain": "global","reason": "authError","message": "Invalid Credentials","locationType": "header","location": "Authorization"}],"code": 401,"message": "Invalid Credentials"}}'; 261 | GoogleDriveHttpMockGenerator testCalloutMock = new GoogleDriveHttpMockGenerator( 262 | GoogleConstants.SEARCH_FILES_ENDPOINT, 263 | GoogleConstants.HTTP_UNAUTHORIZED_STATUS_CODE, 264 | failedFileSearchBody 265 | ); 266 | 267 | Test.startTest(); 268 | Test.setMock(HttpCalloutMock.class, testCalloutMock); 269 | buildGoogleDriveInfo(); 270 | 271 | GoogleDrive testGoogleDrive = new GoogleDrive(testCredentials, userAgentName); 272 | try { 273 | GoogleFileSearchResult result = testGoogleDrive.files().search().execute(); 274 | } catch (GoogleCloudException ex) { 275 | Assert.areEqual(failedFileSearchBody, ex.getMessage()); 276 | } 277 | 278 | Test.stopTest(); 279 | } 280 | 281 | @isTest 282 | private static void testSimpleFileUpload() { 283 | String successSimpleFileUpload = '{"id": "1ZdR3cU-rXTxY3g8B9yJH9kR7A4kx0gk9","name": "My Document","driveId": "0BwwA4oUTeiV1UVNwOHItT0xfa0U","fileExtension": "docx","copyRequiresWriterPermission": false,"md5Checksum": "d41d8cd98f00b204e9800998ecf8427e","writersCanShare": true,"viewedByMe": true,"mimeType": "application/vnd.google-apps.document","exportLinks": {"application/pdf": "https://drive.google.com/uc?id=1ZdR3cU-rXTxY3g8B9yJH9kR7A4kx0gk9&export=pdf"},"parents": ["0BwwA4oUTeiV1NHRlWE56R2EwOEU"],"thumbnailLink": "https://drive.google.com/thumbnail?id=1ZdR3cU-rXTxY3g8B9yJH9kR7A4kx0gk9","iconLink": "https://drive.google.com/icon?id=1ZdR3cU-rXTxY3g8B9yJH9kR7A4kx0gk9","shared": false,"lastModifyingUser": {"displayName": "John Doe","kind": "drive#user","me": true,"permissionId": "123456789","emailAddress": "johndoe@example.com","photoLink": "https://example.com/photo.jpg"},"owners": [{"displayName": "John Doe","kind": "drive#user","me": true,"permissionId": "123456789","emailAddress": "johndoe@example.com","photoLink": "https://example.com/photo.jpg"}],"headRevisionId": "0123456789","sharingUser": {"displayName": "Jane Doe","kind": "drive#user","me": false,"permissionId": "987654321","emailAddress": "janedoe@example.com","photoLink": "https://example.com/photo2.jpg"},"webViewLink": "https://drive.google.com/file/d/1ZdR3cU-rXTxY3g8B9yJH9kR7A4kx0gk9/view","webContentLink": "https://drive.google.com/uc?id=1ZdR3cU-rXTxY3g8B9yJH9kR7A4kx0gk9&export=download","size": "102400","viewersCanCopyContent": true,"permissions": [{"id": "1234567890","displayName": "Jane Doe","type": "user","kind": "drive#permission","permissionDetails": [],"photoLink": "https://example.com/photo2.jpg","emailAddress": "janedoe@example.com","role": "reader","allowFileDiscovery": false,"domain": "","expirationTime": "","teamDrivePermissionDetails": [],"deleted": false,"view": "","pendingOwner": false}],"hasThumbnail": true,"spaces": ["drive"],"folderColorRgb": "#FF0000","description": "A sample document","starred": false,"trashed": false,"explicitlyTrashed": false,"createdTime": "2024-06-29T12:00:00.000Z","modifiedTime": "2024-06-29T12:30:00.000Z","modifiedByMeTime": "2024-06-29T12:15:00.000Z","viewedByMeTime": "2024-06-29T12:20:00.000Z","sharedWithMeTime": "2024-06-29T12:10:00.000Z","quotaBytesUsed": "102400","version": "1","originalFilename": "my_document.docx","ownedByMe": true,"fullFileExtension": "docx","properties": {"key1": "value1"},"appProperties": {"key2": "value2"},"isAppAuthorized": true,"teamDriveId": "0BwwA4oUTeiV1UVNwOHItT0xfa0U","capabilities": {"canEdit": true,"canComment": true,"canShare": true},"hasAugmentedPermissions": false,"trashingUser": {"displayName": "John Doe","kind": "drive#user","me": true,"permissionId": "123456789","emailAddress": "johndoe@example.com","photoLink": "https://example.com/photo.jpg"},"thumbnailVersion": "1","trashedTime": "","modifiedByMe": true,"permissionIds": ["1234567890"],"imageMediaMetadata": {"width": 800,"height": 600},"videoMediaMetadata": {"width": 1920,"height": 1080,"durationMillis": "120000"},"shortcutDetails": {"targetId": "0BwwA4oUTeiV1UVNwOHItT0xfa0U","targetMimeType": "application/vnd.google-apps.folder","targetResourceKey": "resourceKey123"},"resourceKey": "resourceKey123","linkShareMetadata": {"securityUpdateEligible": true,"securityUpdateEnabled": true},"sha1Checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709","sha256Checksum": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}'; 284 | GoogleDriveHttpMockGenerator testCalloutMock = new GoogleDriveHttpMockGenerator( 285 | GoogleConstants.UPLOAD_FILES_ENDPOINT, 286 | 200, 287 | successSimpleFileUpload 288 | ); 289 | 290 | Test.startTest(); 291 | Test.setMock(HttpCalloutMock.class, testCalloutMock); 292 | buildGoogleDriveInfo(); 293 | 294 | GoogleDrive testGoogleDrive = new GoogleDrive(testCredentials, userAgentName); 295 | GoogleFileEntity result = testGoogleDrive.files().simpleCreate() 296 | .setContentType('text/plain') 297 | .setContentLength(11) 298 | .setFields('id, name, driveId, fileExtension, copyRequiresWriterPermission, md5Checksum, writersCanShare, viewedByMe, mimeType, exportLinks, parents, thumbnailLink, iconLink, shared, lastModifyingUser, owners, headRevisionId, sharingUser, webViewLink, webContentLink, size, viewersCanCopyContent, permissions, hasThumbnail, spaces, folderColorRgb, description, starred, trashed, explicitlyTrashed, createdTime, modifiedTime, modifiedByMeTime, viewedByMeTime, sharedWithMeTime, quotaBytesUsed, version, originalFilename, ownedByMe, fullFileExtension, properties, appProperties, isAppAuthorized, teamDriveId, capabilities, hasAugmentedPermissions, trashingUser, thumbnailVersion, trashedTime, modifiedByMe, permissionIds, imageMediaMetadata, videoMediaMetadata, shortcutDetails, resourceKey, linkShareMetadata, sha1Checksum, sha256Checksum') 299 | .setBody(Blob.valueOf('Hello World')) 300 | .execute(); 301 | 302 | // Fields that are specified in "setFields()" will be received 303 | // in response to the creation of the document, 304 | // validation is performed only for some of them, 305 | // so as not to clutter the test class. 306 | Assert.isNotNull(result.id); 307 | Assert.isNotNull(result.name); 308 | Assert.isNotNull(result.driveId); 309 | Test.stopTest(); 310 | } 311 | 312 | @isTest 313 | private static void testMultipartFileUpload() { 314 | String successMultipartFileUpload = '{"id": "1ZdR3cU-rXTxY3g8B9yJH9kR7A4kx0gk9","name": "My Document","driveId": "0BwwA4oUTeiV1UVNwOHItT0xfa0U","fileExtension": "docx","copyRequiresWriterPermission": false,"md5Checksum": "d41d8cd98f00b204e9800998ecf8427e","writersCanShare": true,"viewedByMe": true,"mimeType": "application/vnd.google-apps.document","exportLinks": {"application/pdf": "https://drive.google.com/uc?id=1ZdR3cU-rXTxY3g8B9yJH9kR7A4kx0gk9&export=pdf"},"parents": ["0BwwA4oUTeiV1NHRlWE56R2EwOEU"],"thumbnailLink": "https://drive.google.com/thumbnail?id=1ZdR3cU-rXTxY3g8B9yJH9kR7A4kx0gk9","iconLink": "https://drive.google.com/icon?id=1ZdR3cU-rXTxY3g8B9yJH9kR7A4kx0gk9","shared": false,"lastModifyingUser": {"displayName": "John Doe","kind": "drive#user","me": true,"permissionId": "123456789","emailAddress": "johndoe@example.com","photoLink": "https://example.com/photo.jpg"},"owners": [{"displayName": "John Doe","kind": "drive#user","me": true,"permissionId": "123456789","emailAddress": "johndoe@example.com","photoLink": "https://example.com/photo.jpg"}],"headRevisionId": "0123456789","sharingUser": {"displayName": "Jane Doe","kind": "drive#user","me": false,"permissionId": "987654321","emailAddress": "janedoe@example.com","photoLink": "https://example.com/photo2.jpg"},"webViewLink": "https://drive.google.com/file/d/1ZdR3cU-rXTxY3g8B9yJH9kR7A4kx0gk9/view","webContentLink": "https://drive.google.com/uc?id=1ZdR3cU-rXTxY3g8B9yJH9kR7A4kx0gk9&export=download","size": "102400","viewersCanCopyContent": true,"permissions": [{"id": "1234567890","displayName": "Jane Doe","type": "user","kind": "drive#permission","permissionDetails": [],"photoLink": "https://example.com/photo2.jpg","emailAddress": "janedoe@example.com","role": "reader","allowFileDiscovery": false,"domain": "","expirationTime": "","teamDrivePermissionDetails": [],"deleted": false,"view": "","pendingOwner": false}],"hasThumbnail": true,"spaces": ["drive"],"folderColorRgb": "#FF0000","description": "A sample document","starred": false,"trashed": false,"explicitlyTrashed": false,"createdTime": "2024-06-29T12:00:00.000Z","modifiedTime": "2024-06-29T12:30:00.000Z","modifiedByMeTime": "2024-06-29T12:15:00.000Z","viewedByMeTime": "2024-06-29T12:20:00.000Z","sharedWithMeTime": "2024-06-29T12:10:00.000Z","quotaBytesUsed": "102400","version": "1","originalFilename": "my_document.docx","ownedByMe": true,"fullFileExtension": "docx","properties": {"key1": "value1"},"appProperties": {"key2": "value2"},"isAppAuthorized": true,"teamDriveId": "0BwwA4oUTeiV1UVNwOHItT0xfa0U","capabilities": {"canEdit": true,"canComment": true,"canShare": true},"hasAugmentedPermissions": false,"trashingUser": {"displayName": "John Doe","kind": "drive#user","me": true,"permissionId": "123456789","emailAddress": "johndoe@example.com","photoLink": "https://example.com/photo.jpg"},"thumbnailVersion": "1","trashedTime": "","modifiedByMe": true,"permissionIds": ["1234567890"],"imageMediaMetadata": {"width": 800,"height": 600},"videoMediaMetadata": {"width": 1920,"height": 1080,"durationMillis": "120000"},"shortcutDetails": {"targetId": "0BwwA4oUTeiV1UVNwOHItT0xfa0U","targetMimeType": "application/vnd.google-apps.folder","targetResourceKey": "resourceKey123"},"resourceKey": "resourceKey123","linkShareMetadata": {"securityUpdateEligible": true,"securityUpdateEnabled": true},"sha1Checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709","sha256Checksum": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}'; 315 | GoogleDriveHttpMockGenerator testCalloutMock = new GoogleDriveHttpMockGenerator( 316 | GoogleConstants.UPLOAD_FILES_ENDPOINT, 317 | 200, 318 | successMultipartFileUpload 319 | ); 320 | 321 | Test.startTest(); 322 | Test.setMock(HttpCalloutMock.class, testCalloutMock); 323 | buildGoogleDriveInfo(); 324 | 325 | GoogleDrive testGoogleDrive = new GoogleDrive(testCredentials, userAgentName); 326 | GoogleFileEntity result = testGoogleDrive.files().multipartCreate() 327 | .setContentLength(11) 328 | .setFields('id, name, driveId, fileExtension, copyRequiresWriterPermission, md5Checksum, writersCanShare, viewedByMe, mimeType, exportLinks, parents, thumbnailLink, iconLink, shared, lastModifyingUser, owners, headRevisionId, sharingUser, webViewLink, webContentLink, size, viewersCanCopyContent, permissions, hasThumbnail, spaces, folderColorRgb, description, starred, trashed, explicitlyTrashed, createdTime, modifiedTime, modifiedByMeTime, viewedByMeTime, sharedWithMeTime, quotaBytesUsed, version, originalFilename, ownedByMe, fullFileExtension, properties, appProperties, isAppAuthorized, teamDriveId, capabilities, hasAugmentedPermissions, trashingUser, thumbnailVersion, trashedTime, modifiedByMe, permissionIds, imageMediaMetadata, videoMediaMetadata, shortcutDetails, resourceKey, linkShareMetadata, sha1Checksum, sha256Checksum') 329 | .setFileName('Multipart Upload') 330 | .setMimeType('application/vnd.google-apps.document') 331 | .setParentFolders(new List{'1TLCWgrczvSFnnJpU-6OEEEXMy77OVLjM', '1TLDWgrczvSFnnJpU-2OEEEXMy77OVLjM'}) 332 | .setBody('text/plain', '8bit', 'Hello World') 333 | .execute(); 334 | 335 | // Fields that are specified in "setFields()" will be received 336 | // in response to the creation of the document, 337 | // validation is performed only for some of them, 338 | // so as not to clutter the test class. 339 | Assert.isNotNull(result.id); 340 | Assert.isNotNull(result.name); 341 | Assert.isNotNull(result.driveId); 342 | Test.stopTest(); 343 | } 344 | 345 | @isTest 346 | private static void testMultipartFileUploadFail() { 347 | String failedMultipartFileUpload = '{"error": {"errors": [{"domain": "global","reason": "authError","message": "Invalid Credentials","locationType": "header","location": "Authorization"}],"code": 401,"message": "Invalid Credentials"}}'; 348 | GoogleDriveHttpMockGenerator testCalloutMock = new GoogleDriveHttpMockGenerator( 349 | GoogleConstants.UPLOAD_FILES_ENDPOINT, 350 | GoogleConstants.HTTP_UNAUTHORIZED_STATUS_CODE, 351 | failedMultipartFileUpload 352 | ); 353 | 354 | Test.startTest(); 355 | Test.setMock(HttpCalloutMock.class, testCalloutMock); 356 | buildGoogleDriveInfo(); 357 | 358 | GoogleDrive testGoogleDrive = new GoogleDrive(testCredentials, userAgentName); 359 | try { 360 | GoogleFileEntity result = testGoogleDrive.files().multipartCreate() 361 | .setContentLength('11') 362 | .setFields('id, name') 363 | .setFileName('Multipart Failed Upload') 364 | .setBody('Hello World') // The standard "base64" encoding does not match the content 365 | .execute(); 366 | } catch (GoogleCloudException ex) { 367 | Assert.areEqual(failedMultipartFileUpload, ex.getMessage()); 368 | } 369 | Test.stopTest(); 370 | } 371 | 372 | @isTest 373 | private static void testResumableFileUpload() { 374 | String successInitializedUri = 'https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable&upload_id=ADPycjXkG3_J2JkRhql6g9lPqXEd75CMfbk1fP4O6ZWB7wNHUpl9Tp6V0kF9'; 375 | String successResumableFileUpload = '{"id": "1ZdR3cU-rXTxY3g8B9yJH9kR7A4kx0gk9"}'; 376 | 377 | Test.startTest(); 378 | buildGoogleDriveInfo(); 379 | 380 | GoogleDrive testGoogleDrive = new GoogleDrive(testCredentials, userAgentName); 381 | 382 | // Before uploading a file in parts, the transaction must be initialized, during which all metadata is specified 383 | GoogleDriveHttpMockGenerator testInitCalloutMock = new GoogleDriveHttpMockGenerator(GoogleConstants.UPLOAD_FILES_ENDPOINT, 200, '', new Map{'Location' => successInitializedUri}); 384 | Test.setMock(HttpCalloutMock.class, testInitCalloutMock); 385 | GoogleBigFileEntity initResult = testGoogleDrive.files().resumableCreate() 386 | .setFields('id') 387 | .setFileName('Resumable Upload File') 388 | .setMimeType('application/vnd.google-apps.document') 389 | .setParentFolders(new List{'1TLCWgrczvSFnnJpU-6OEEEXMy77OVLjM'}) 390 | .initialize(); 391 | 392 | // The initialization process results in an endpoint with Uniform Resource Identifier (URI) 393 | Assert.areEqual(successInitializedUri, initResult.resumableSessionId); 394 | 395 | // It is not necessary to have the complete document body (as the heap size is limited); having the total size in bytes is sufficient 396 | Blob fullFileBody = Blob.valueOf('Hello World'); 397 | 398 | // Upload a portion of the document & specify the starting byte and the document's total size 399 | Blob firstFileChunk = Blob.valueOf('Hello'); 400 | GoogleDriveHttpMockGenerator testChunkCalloutMock = new GoogleDriveHttpMockGenerator(initResult.resumableSessionId, 308, '', new Map{'Range' => 'bytes=0-5'}); 401 | Test.setMock(HttpCalloutMock.class, testChunkCalloutMock); 402 | GoogleBigFileEntity chunksResult = testGoogleDrive.files().resumableCreate() 403 | .setExistingSessionUri(initResult.resumableSessionId) 404 | .setBody(firstFileChunk) 405 | .setStartByte(0) 406 | .setTotalBytes(fullFileBody.size()) 407 | .execute(); 408 | 409 | // Uploading a chunk returns the number of bytes successfully uploaded, which may not always match the expected bytes 410 | Assert.areNotEqual(0, chunksResult.resumableLatestByte); 411 | 412 | // Upload the second part of the document by specifying a new body and starting byte 413 | Blob secondFileChunk = Blob.valueOf(' World'); 414 | GoogleDriveHttpMockGenerator testUploadCalloutMock = new GoogleDriveHttpMockGenerator(initResult.resumableSessionId, 201, successResumableFileUpload); 415 | Test.setMock(HttpCalloutMock.class, testUploadCalloutMock); 416 | chunksResult = testGoogleDrive.files().resumableCreate() 417 | .setExistingSessionUri(initResult.resumableSessionId) 418 | .setBody(secondFileChunk) 419 | .setStartByte(chunksResult.resumableLatestByte) 420 | .setTotalBytes(fullFileBody.size()) 421 | .execute(); 422 | 423 | Assert.isNotNull(chunksResult.file); 424 | Test.stopTest(); 425 | } 426 | 427 | @isTest 428 | private static void testCloneFile() { 429 | String testFileId = '1lhu72ZrlfzRljhP4t12RE5GDGa8n7Yv8LHWy20_QBqw'; 430 | String successFileCloned = '{"kind": "drive#file","id": "1lhu72ZrlfzRljhP4t12RE5GDGa8n7Yv8LHWy20_QBqw","name": "CopiedDocument"}'; 431 | GoogleDriveHttpMockGenerator testCalloutMock = new GoogleDriveHttpMockGenerator( 432 | buildFileDependentEndpoint(GoogleConstants.CLONE_FILE_ENDPOINT, testFileId), 433 | 200, 434 | successFileCloned 435 | ); 436 | 437 | Test.startTest(); 438 | Test.setMock(HttpCalloutMock.class, testCalloutMock); 439 | buildGoogleDriveInfo(); 440 | 441 | GoogleDrive testGoogleDrive = new GoogleDrive(testCredentials, userAgentName); 442 | GoogleFileEntity result = testGoogleDrive.files().clone(testFileId) 443 | .setFields('id, name') 444 | .setFileName('CopiedDocument') 445 | .setMimeType('application/vnd.google-apps.document') 446 | .setParentFolders(new List{'1lhu72ZrlfzRljhP4t12RE5GDGa8n7Yv8LHWy20_QBqw'}) 447 | .execute(); 448 | 449 | // Fields that are specified in "setFields()" will be received 450 | // in response to the cloning of the document 451 | Assert.isNotNull(result.id); 452 | Assert.isNotNull(result.name); 453 | Test.stopTest(); 454 | } 455 | 456 | @isTest 457 | private static void testDeleteFile() { 458 | String testFileId = '1lhu72ZrlfzRljhP4t12RE5GDGa8n7Yv8LHWy20_QBqw'; 459 | GoogleDriveHttpMockGenerator testCalloutMock = new GoogleDriveHttpMockGenerator( 460 | buildFileDependentEndpoint(GoogleConstants.DELETE_FILE_ENDPOINT, testFileId), 461 | 204, 462 | '' 463 | ); 464 | 465 | Test.startTest(); 466 | Test.setMock(HttpCalloutMock.class, testCalloutMock); 467 | buildGoogleDriveInfo(); 468 | 469 | GoogleDrive testGoogleDrive = new GoogleDrive(testCredentials, userAgentName); 470 | testGoogleDrive.files().remove(testFileId) 471 | .setSupportsAllDrives(true) // Allow delete operation on Shared Drive files 472 | .execute(); 473 | Test.stopTest(); 474 | } 475 | 476 | @isTest 477 | private static void testPermissionFileCreate() { 478 | String testFileId = '1lhu72ZrlfzRljhP4t12RE5GDGa8n7Yv8LHWy20_QBqw'; 479 | String successFilePermissionCreated = '{"kind": "drive#permission","id": "1lhu72ZrlfzRljhP4t12RE5GDGa8n7Yv8LHWy20_QBqw","type": "user","emailAddress": "test@gmail.com","role": "reader","domain": "","allowFileDiscovery": false,"displayName": "Example User"}'; 480 | GoogleDriveHttpMockGenerator testCalloutMock = new GoogleDriveHttpMockGenerator( 481 | buildFileDependentEndpoint(GoogleConstants.NEW_PERMISSION_FILE_ENDPOINT, testFileId), 482 | 200, 483 | successFilePermissionCreated 484 | ); 485 | 486 | Test.startTest(); 487 | Test.setMock(HttpCalloutMock.class, testCalloutMock); 488 | buildGoogleDriveInfo(); 489 | 490 | GoogleDrive testGoogleDrive = new GoogleDrive(testCredentials, userAgentName); 491 | GooglePermissionEntity result = testGoogleDrive.permissions().create(testFileId) 492 | .setSendNotificationEmail(true) 493 | .setTransferOwnership(false) 494 | .setPrincipalType('user') 495 | .setPrincipalRole('reader') 496 | .setPrincipalEmailAddress('test@gmail.com') 497 | .execute(); 498 | 499 | Assert.isNotNull(result.id); 500 | Test.stopTest(); 501 | } 502 | 503 | @isTest 504 | private static void testPermissionFileDelete() { 505 | String testFileId = '1lhu72ZrlfzRljhP4t12RE5GDGa8n7Yv8LHWy20_QBqw'; 506 | String testPermissionId = '01649911713816498250'; 507 | GoogleDriveHttpMockGenerator testCalloutMock = new GoogleDriveHttpMockGenerator( 508 | buildFileDependentEndpoint(GoogleConstants.NEW_PERMISSION_FILE_ENDPOINT, testFileId, testPermissionId), 509 | 204, 510 | '' 511 | ); 512 | 513 | Test.startTest(); 514 | Test.setMock(HttpCalloutMock.class, testCalloutMock); 515 | buildGoogleDriveInfo(); 516 | 517 | GoogleDrive testGoogleDrive = new GoogleDrive(testCredentials, userAgentName); 518 | testGoogleDrive.permissions().remove(testFileId, testPermissionId) 519 | .setDomainAdminAccess(true) 520 | .setSupportsAllDrives(false) 521 | .execute(); 522 | 523 | Test.stopTest(); 524 | } 525 | 526 | @isTest 527 | private static void testPermissionFileSearch() { 528 | String testFileId = '1lhu72ZrlfzRljhP4t12RE5GDGa8n7Yv8LHWy20_QBqw'; 529 | String permissionsSearchBody = '{"permissions":[{"id":"0123456789abcdef","emailAddress":"user@example.com"}],"kind":"drive#permissionList"}'; 530 | GoogleDriveHttpMockGenerator testCalloutMock = new GoogleDriveHttpMockGenerator( 531 | buildFileDependentEndpoint(GoogleConstants.LIST_PERMISSIONS_FILE_ENDPOINT, testFileId), 532 | 200, 533 | permissionsSearchBody 534 | ); 535 | 536 | Test.startTest(); 537 | Test.setMock(HttpCalloutMock.class, testCalloutMock); 538 | buildGoogleDriveInfo(); 539 | 540 | GoogleDrive testGoogleDrive = new GoogleDrive(testCredentials, userAgentName); 541 | GooglePermissionSearchResult result = testGoogleDrive.permissions().search(testFileId) 542 | .setMaxResult(10) 543 | .setFields('emailAddress') 544 | .setSearchOnAllDrives(true) // List permissions for items located in Shared Drives 545 | .execute(); 546 | 547 | Assert.areEqual(1, result.permissions.size()); 548 | Assert.areEqual('user@example.com', result.permissions.get(0).emailAddress); 549 | 550 | Test.stopTest(); 551 | } 552 | 553 | @isTest 554 | private static void testRetrieveFileByDownloadMetadata() { 555 | String testFileId = '1lhu72ZrlfzRljhP4t12RE5GDGa8n7Yv8LHWy20_QBqw'; 556 | String successFileDownloaded = '{"kind": "drive#file","id": "1lhu72ZrlfzRljhP4t12RE5GDGa8n7Yv8LHWy20_QBqw","name": "filename","mimeType": "application/pdf"}'; 557 | GoogleDriveHttpMockGenerator testCalloutMock = new GoogleDriveHttpMockGenerator( 558 | buildFileDependentEndpoint(GoogleConstants.GET_FILE_ENDPOINT, testFileId), 559 | 200, 560 | successFileDownloaded 561 | ); 562 | 563 | Test.startTest(); 564 | Test.setMock(HttpCalloutMock.class, testCalloutMock); 565 | buildGoogleDriveInfo(); 566 | 567 | GoogleDrive testGoogleDrive = new GoogleDrive(testCredentials, userAgentName); 568 | GoogleFileEntity result = testGoogleDrive.files().retrieve().download(testFileId) 569 | .setFileDownloadType(GoogleDownloadFileBuilder.DownloadType.METADATA) 570 | .setSearchOnAllDrives(false) // Restrict to My Drive only; no Shared Drives lookup 571 | .execute(); 572 | 573 | Assert.isNotNull(result.id); 574 | Test.stopTest(); 575 | } 576 | 577 | @isTest 578 | private static void testRetrieveFileByDownloadContent() { 579 | String testFileId = '1lhu72ZrlfzRljhP4t12RE5GDGa8n7Yv8LHWy20_QBqw'; 580 | String successFileDownloaded = 'JVBERi0xLjUKJdP0zOEKNSAwIG9iaiA8PAovQ3JlYXRvciAoQWRvYmUgUERGIGxpYn'; 581 | GoogleDriveHttpMockGenerator testCalloutMock = new GoogleDriveHttpMockGenerator( 582 | buildFileDependentEndpoint(GoogleConstants.GET_FILE_ENDPOINT, testFileId), 583 | 200, 584 | successFileDownloaded 585 | ); 586 | 587 | Test.startTest(); 588 | Test.setMock(HttpCalloutMock.class, testCalloutMock); 589 | buildGoogleDriveInfo(); 590 | 591 | GoogleDrive testGoogleDrive = new GoogleDrive(testCredentials, userAgentName); 592 | GoogleFileEntity result = testGoogleDrive.files().retrieve().download(testFileId) 593 | .setFileDownloadType(GoogleDownloadFileBuilder.DownloadType.CONTENT) 594 | .execute(); 595 | 596 | Assert.isNotNull(result.body); 597 | Test.stopTest(); 598 | } 599 | 600 | @isTest 601 | private static void testRetrieveFileChunkByDownloadContent() { 602 | String testFileId = '1lhu72ZrlfzRljhP4t12RE5GDGa8n7Yv8LHWy20_QBqw'; 603 | String successFileDownloaded = 'JVBERi0xLj'; 604 | GoogleDriveHttpMockGenerator testCalloutMock = new GoogleDriveHttpMockGenerator( 605 | buildFileDependentEndpoint(GoogleConstants.GET_FILE_ENDPOINT, testFileId), 606 | 200, 607 | successFileDownloaded 608 | ); 609 | 610 | Test.startTest(); 611 | Test.setMock(HttpCalloutMock.class, testCalloutMock); 612 | buildGoogleDriveInfo(); 613 | 614 | GoogleDrive testGoogleDrive = new GoogleDrive(testCredentials, userAgentName); 615 | GoogleFileEntity result = testGoogleDrive.files().retrieve().download(testFileId) 616 | .setFileDownloadType(GoogleDownloadFileBuilder.DownloadType.CONTENT) 617 | .setPartialRange(0, 9) 618 | .setFields('name, description') 619 | .execute(); 620 | 621 | Assert.isNotNull(result.body); 622 | Assert.areEqual(10, result.body.length()); 623 | Test.stopTest(); 624 | } 625 | 626 | @isTest 627 | private static void testRetrieveFileByExport() { 628 | String testFileId = '1lhu72ZrlfzRljhP4t12RE5GDGa8n7Yv8LHWy20_QBqw'; 629 | String successFileExported = '{"body": {"content": [{"endIndex": 650, "paragraph": {"elements": [{"endIndex": 650, "startIndex": 590, "textRun": {"content": "And this is a paragraph that follows the level two heading.\n", "textStyle": {}}}], "paragraphStyle": {"direction": "LEFT_TO_RIGHT", "namedStyleType": "NORMAL_TEXT"}}, "startIndex": 590}]}, "documentId": "1lhu72ZrlfzRljhP4t12RE5GDGa8n7Yv8LHWy20_QBqw", "documentStyle": {}, "lists": {}, "namedStyles": {"styles": []}, "revisionId": "np_INheZiecEMA", "suggestionsViewMode": "SUGGESTIONS_INLINE", "title": "Test mule"}'; 630 | GoogleDriveHttpMockGenerator testCalloutMock = new GoogleDriveHttpMockGenerator( 631 | buildFileDependentEndpoint(GoogleConstants.EXPORT_FILE_ENDPOINT, testFileId), 632 | 200, 633 | successFileExported 634 | ); 635 | 636 | Test.startTest(); 637 | Test.setMock(HttpCalloutMock.class, testCalloutMock); 638 | buildGoogleDriveInfo(); 639 | 640 | GoogleDrive testGoogleDrive = new GoogleDrive(testCredentials, userAgentName); 641 | GoogleFileEntity result = testGoogleDrive.files().retrieve().export(testFileId) 642 | .setMimeType('text/plain') 643 | .setFields('name, description') 644 | .setSearchOnAllDrives(true) 645 | .execute(); 646 | 647 | Assert.isNotNull(result.body); 648 | Test.stopTest(); 649 | } 650 | 651 | private static String buildFileDependentEndpoint(String baseEndpoint, String fileId) { 652 | return String.format(baseEndpoint, new List{fileId}); 653 | } 654 | 655 | private static String buildFileDependentEndpoint(String baseEndpoint, String fileId, String whateverId) { 656 | return String.format(baseEndpoint, new List{fileId, whateverId}); 657 | } 658 | 659 | private static void buildGoogleDriveInfo() { 660 | userAgentName = 'Google Drive/v3 test'; 661 | testCredentials = new GoogleCredential(); 662 | testCredentials.accessToken = 'aZ3X8kP2RtaZ3X8kP2RtaZ3X8kP2RtaZ'; 663 | testCredentials.tokenType = 'Bearer'; 664 | } 665 | } --------------------------------------------------------------------------------