files.com 1.1.813 → 1.1.814
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/_VERSION +1 -1
- data/doc/Files/APIConnectionError.html +1 -1
- data/doc/Files/APIError.html +1 -1
- data/doc/Files/AccountAlreadyExistsError.html +1 -1
- data/doc/Files/AccountLineItem.html +1 -1
- data/doc/Files/AccountOverdueError.html +1 -1
- data/doc/Files/Action.html +1 -1
- data/doc/Files/ActionLog.html +1 -1
- data/doc/Files/ActionNotificationExport.html +1 -1
- data/doc/Files/ActionNotificationExportResult.html +1 -1
- data/doc/Files/AdditionalAuthenticationRequiredError.html +1 -1
- data/doc/Files/AgentNode.html +1 -1
- data/doc/Files/AgentNodeConnection.html +1 -1
- data/doc/Files/AgentNodeInstance.html +1 -1
- data/doc/Files/AgentPushUpdate.html +1 -1
- data/doc/Files/AgentPushUpdateBlockedError.html +1 -1
- data/doc/Files/AgentUnavailableError.html +1 -1
- data/doc/Files/AgentUpgradeRequiredError.html +1 -1
- data/doc/Files/AiAssistantPersonality.html +1 -1
- data/doc/Files/AiTask.html +1 -1
- data/doc/Files/AiTaskCannotBeRunManuallyError.html +1 -1
- data/doc/Files/AlreadyCompletedError.html +1 -1
- data/doc/Files/Api.html +20 -13
- data/doc/Files/ApiClient/RequestLogContext.html +29 -29
- data/doc/Files/ApiClient/TransferSettings.html +721 -0
- data/doc/Files/ApiClient.html +756 -204
- data/doc/Files/ApiKey.html +1 -1
- data/doc/Files/ApiKeyIsDisabledError.html +1 -1
- data/doc/Files/ApiKeyIsPathRestrictedError.html +1 -1
- data/doc/Files/ApiKeyNotFoundError.html +1 -1
- data/doc/Files/ApiKeyOnlyForDesktopAppError.html +1 -1
- data/doc/Files/ApiKeyOnlyForFileOperationsError.html +1 -1
- data/doc/Files/ApiKeyOnlyForMobileAppError.html +1 -1
- data/doc/Files/ApiKeyOnlyForOfficeIntegrationError.html +1 -1
- data/doc/Files/ApiKeySessionsNotSupportedError.html +1 -1
- data/doc/Files/ApiRequestLog.html +1 -1
- data/doc/Files/App.html +1 -1
- data/doc/Files/As2IncomingMessage.html +1 -1
- data/doc/Files/As2OutgoingMessage.html +1 -1
- data/doc/Files/As2Partner.html +1 -1
- data/doc/Files/As2Station.html +1 -1
- data/doc/Files/AttachmentTooLargeError.html +1 -1
- data/doc/Files/AuthenticationError.html +1 -1
- data/doc/Files/AuthenticationRequiredError.html +1 -1
- data/doc/Files/Auto.html +1 -1
- data/doc/Files/Automation.html +1 -1
- data/doc/Files/AutomationAuthoringSchema.html +1 -1
- data/doc/Files/AutomationCannotBeRunManuallyError.html +1 -1
- data/doc/Files/AutomationExecutionNode.html +1 -1
- data/doc/Files/AutomationLog.html +1 -1
- data/doc/Files/AutomationRun.html +1 -1
- data/doc/Files/AutomationsUnavailableError.html +1 -1
- data/doc/Files/BadRequestError.html +1 -1
- data/doc/Files/BandwidthSnapshot.html +1 -1
- data/doc/Files/Behavior.html +1 -1
- data/doc/Files/BehaviorNotAllowedOnRemoteServerError.html +1 -1
- data/doc/Files/BillingInformationHiddenError.html +1 -1
- data/doc/Files/BillingPermissionRequiredError.html +1 -1
- data/doc/Files/BufferedUploadDisabledForThisDestinationError.html +1 -1
- data/doc/Files/Bundle.html +1 -1
- data/doc/Files/BundleAction.html +1 -1
- data/doc/Files/BundleDownload.html +1 -1
- data/doc/Files/BundleMaximumUsesReachedError.html +1 -1
- data/doc/Files/BundleNotification.html +1 -1
- data/doc/Files/BundleOnlyAllowsPreviewsError.html +1 -1
- data/doc/Files/BundleOperationRequiresSubfolderError.html +1 -1
- data/doc/Files/BundlePath.html +1 -1
- data/doc/Files/BundlePathNotFoundError.html +1 -1
- data/doc/Files/BundlePermissionRequiredError.html +1 -1
- data/doc/Files/BundleRecipient.html +1 -1
- data/doc/Files/BundleRegistration.html +1 -1
- data/doc/Files/BundleRegistrationCodeFailedError.html +1 -1
- data/doc/Files/BundleRegistrationNotFoundError.html +1 -1
- data/doc/Files/CannotAdministerHigherLevelUserError.html +1 -1
- data/doc/Files/CannotDownloadDirectoryError.html +1 -1
- data/doc/Files/CannotLoginWhileUsingKeyError.html +1 -1
- data/doc/Files/CantActForOtherUserError.html +1 -1
- data/doc/Files/CantMoveWithMultipleLocationsError.html +1 -1
- data/doc/Files/ChatMessage.html +1 -1
- data/doc/Files/ChatSession.html +1 -1
- data/doc/Files/ChildSiteManagementPolicy.html +1 -1
- data/doc/Files/Clickwrap.html +1 -1
- data/doc/Files/CodeNotFoundError.html +1 -1
- data/doc/Files/ConfigurationLockedPathError.html +1 -1
- data/doc/Files/ContactAdminForPasswordChangeHelpError.html +1 -1
- data/doc/Files/CouldNotCreateParentError.html +1 -1
- data/doc/Files/CustomDomain.html +1 -1
- data/doc/Files/DatetimeParseError.html +1 -1
- data/doc/Files/DesktopConfigurationProfile.html +1 -1
- data/doc/Files/DestinationExistsError.html +1 -1
- data/doc/Files/DestinationFolderLimitedError.html +1 -1
- data/doc/Files/DestinationParentConflictError.html +1 -1
- data/doc/Files/DestinationParentDoesNotExistError.html +1 -1
- data/doc/Files/DestinationSameError.html +1 -1
- data/doc/Files/DestinationSiteMismatchError.html +1 -1
- data/doc/Files/DirectConnectionInfo.html +1 -1
- data/doc/Files/DnsRecord.html +1 -1
- data/doc/Files/DoesNotSupportSortingError.html +1 -1
- data/doc/Files/DuplicateShareRecipientError.html +1 -1
- data/doc/Files/EmailIncomingMessage.html +1 -1
- data/doc/Files/EmailLog.html +1 -1
- data/doc/Files/Error.html +2 -2
- data/doc/Files/Errors.html +1 -1
- data/doc/Files/EventChannel.html +1 -1
- data/doc/Files/EventDeliveryAttempt.html +1 -1
- data/doc/Files/EventRecord.html +1 -1
- data/doc/Files/EventSubscription.html +1 -1
- data/doc/Files/EventTarget.html +1 -1
- data/doc/Files/ExavaultApiRequestLog.html +1 -1
- data/doc/Files/ExceededRuntimeLimitError.html +1 -1
- data/doc/Files/Expectation.html +1 -1
- data/doc/Files/ExpectationAlreadyHasOpenWindowError.html +1 -1
- data/doc/Files/ExpectationEvaluation.html +1 -1
- data/doc/Files/ExpectationIncident.html +1 -1
- data/doc/Files/ExpectationNotManualTriggerError.html +1 -1
- data/doc/Files/ExpiredPrivateKeyError.html +1 -1
- data/doc/Files/ExpiredPublicKeyError.html +1 -1
- data/doc/Files/ExportFailureError.html +1 -1
- data/doc/Files/ExportNotReadyError.html +1 -1
- data/doc/Files/ExternalEvent.html +1 -1
- data/doc/Files/FailedToChangePasswordError.html +1 -1
- data/doc/Files/File.html +2019 -1515
- data/doc/Files/FileAction.html +1 -1
- data/doc/Files/FileActionError.html +1 -1
- data/doc/Files/FileComment.html +1 -1
- data/doc/Files/FileCommentReaction.html +1 -1
- data/doc/Files/FileLockedError.html +1 -1
- data/doc/Files/FileMigration.html +1 -1
- data/doc/Files/FileMigrationLog.html +1 -1
- data/doc/Files/FileNotFoundError.html +1 -1
- data/doc/Files/FileNotUploadedError.html +1 -1
- data/doc/Files/FilePendingProcessingError.html +1 -1
- data/doc/Files/FileProcessingErrorError.html +1 -1
- data/doc/Files/FileTooBigToDecryptError.html +1 -1
- data/doc/Files/FileTooBigToEncryptError.html +1 -1
- data/doc/Files/FileUploadNotFoundError.html +1 -1
- data/doc/Files/FileUploadPart.html +1 -1
- data/doc/Files/FileUploadedToWrongRegionError.html +1 -1
- data/doc/Files/FileUtils.html +1 -1
- data/doc/Files/FilenameTooLongError.html +1 -1
- data/doc/Files/FilesAgentFailedAuthorizationError.html +1 -1
- data/doc/Files/FilesNativeStorageOverloadedError.html +1 -1
- data/doc/Files/Folder.html +1 -1
- data/doc/Files/FolderAdminOrBillingPermissionRequiredError.html +1 -1
- data/doc/Files/FolderAdminPermissionRequiredError.html +1 -1
- data/doc/Files/FolderLockedError.html +1 -1
- data/doc/Files/FolderMustNotBeAFileError.html +1 -1
- data/doc/Files/FolderNotEmptyError.html +1 -1
- data/doc/Files/FoldersNotAllowedError.html +1 -1
- data/doc/Files/FormField.html +1 -1
- data/doc/Files/FormFieldSet.html +1 -1
- data/doc/Files/FtpActionLog.html +1 -1
- data/doc/Files/FullPermissionRequiredError.html +1 -1
- data/doc/Files/GpgKey.html +1 -1
- data/doc/Files/Group.html +1 -1
- data/doc/Files/GroupNotFoundError.html +1 -1
- data/doc/Files/GroupUser.html +1 -1
- data/doc/Files/History.html +1 -1
- data/doc/Files/HistoryExport.html +1 -1
- data/doc/Files/HistoryExportResult.html +1 -1
- data/doc/Files/HistoryPermissionRequiredError.html +1 -1
- data/doc/Files/HistoryUnavailableError.html +1 -1
- data/doc/Files/HolidayCalendar.html +1 -1
- data/doc/Files/HolidayRegion.html +1 -1
- data/doc/Files/Image.html +1 -1
- data/doc/Files/InAppAiAssistantUnavailableError.html +1 -1
- data/doc/Files/InboundS3Log.html +1 -1
- data/doc/Files/InboxNotFoundError.html +1 -1
- data/doc/Files/InboxRecipient.html +1 -1
- data/doc/Files/InboxRegistration.html +1 -1
- data/doc/Files/InboxRegistrationCodeFailedError.html +1 -1
- data/doc/Files/InboxUpload.html +1 -1
- data/doc/Files/InsufficientPermissionForParamsError.html +1 -1
- data/doc/Files/InsufficientPermissionForSiteError.html +1 -1
- data/doc/Files/IntegrationCentricProfile.html +1 -1
- data/doc/Files/InternalGeneralErrorError.html +1 -1
- data/doc/Files/InvalidBodyError.html +1 -1
- data/doc/Files/InvalidBundleCodeError.html +1 -1
- data/doc/Files/InvalidCredentialsError.html +1 -1
- data/doc/Files/InvalidCursorError.html +1 -1
- data/doc/Files/InvalidCursorTypeForSortError.html +1 -1
- data/doc/Files/InvalidEtagsError.html +1 -1
- data/doc/Files/InvalidFileTypeError.html +1 -1
- data/doc/Files/InvalidFilenameError.html +1 -1
- data/doc/Files/InvalidFilterAliasCombinationError.html +1 -1
- data/doc/Files/InvalidFilterFieldError.html +1 -1
- data/doc/Files/InvalidFilterParamError.html +1 -1
- data/doc/Files/InvalidFilterParamFormatError.html +1 -1
- data/doc/Files/InvalidFilterParamValueError.html +1 -1
- data/doc/Files/InvalidInputEncodingError.html +1 -1
- data/doc/Files/InvalidInterfaceError.html +1 -1
- data/doc/Files/InvalidOauthError.html +1 -1
- data/doc/Files/InvalidOauthProviderError.html +1 -1
- data/doc/Files/InvalidOrExpiredCodeError.html +1 -1
- data/doc/Files/InvalidParameterError.html +5 -1
- data/doc/Files/InvalidPathError.html +1 -1
- data/doc/Files/InvalidPriorityColorError.html +1 -1
- data/doc/Files/InvalidRangeError.html +1 -1
- data/doc/Files/InvalidReturnToUrlError.html +1 -1
- data/doc/Files/InvalidSearchQueryError.html +1 -1
- data/doc/Files/InvalidSessionError.html +1 -1
- data/doc/Files/InvalidSiteError.html +1 -1
- data/doc/Files/InvalidSortFieldError.html +1 -1
- data/doc/Files/InvalidSortFilterCombinationError.html +1 -1
- data/doc/Files/InvalidUploadOffsetError.html +1 -1
- data/doc/Files/InvalidUploadPartGapError.html +1 -1
- data/doc/Files/InvalidUploadPartSizeError.html +1 -1
- data/doc/Files/InvalidUsernameOrPasswordError.html +1 -1
- data/doc/Files/InvalidWorkspaceIdHeaderError.html +1 -1
- data/doc/Files/InvalidZipFileError.html +1 -1
- data/doc/Files/Invoice.html +1 -1
- data/doc/Files/InvoiceLineItem.html +1 -1
- data/doc/Files/IpAddress.html +1 -1
- data/doc/Files/KeyLifecycleRule.html +1 -1
- data/doc/Files/List.html +1 -1
- data/doc/Files/Lock.html +1 -1
- data/doc/Files/LockOperationBusyError.html +1 -1
- data/doc/Files/LockedOutError.html +1 -1
- data/doc/Files/LockoutRegionMismatchError.html +1 -1
- data/doc/Files/MetadataCategory.html +1 -1
- data/doc/Files/MetadataNotSupportedOnRemotesError.html +1 -1
- data/doc/Files/MethodNotAllowedError.html +1 -1
- data/doc/Files/MigrationInProgressError.html +1 -1
- data/doc/Files/MissingParameterError.html +1 -1
- data/doc/Files/ModelSaveErrorError.html +1 -1
- data/doc/Files/MoverAccessDeniedError.html +1 -1
- data/doc/Files/MoverPackageRequiredError.html +1 -1
- data/doc/Files/MultipleProcessingErrorsError.html +1 -1
- data/doc/Files/MultipleSortParamsNotAllowedError.html +1 -1
- data/doc/Files/MustAuthenticateWithApiKeyError.html +1 -1
- data/doc/Files/NeedAdminPermissionForInboxError.html +1 -1
- data/doc/Files/NestedNotFoundError.html +1 -1
- data/doc/Files/NoAccountForSiteError.html +1 -1
- data/doc/Files/NoValidInputParamsError.html +1 -1
- data/doc/Files/NonAdminsMustQueryByFolderOrPathError.html +1 -1
- data/doc/Files/NotAllowedToCreateBundleError.html +1 -1
- data/doc/Files/NotAuthenticatedError.html +1 -1
- data/doc/Files/NotAuthorizedError.html +1 -1
- data/doc/Files/NotEnqueuableSyncError.html +1 -1
- data/doc/Files/NotFoundError.html +1 -1
- data/doc/Files/NotImplementedError.html +1 -1
- data/doc/Files/Notification.html +1 -1
- data/doc/Files/OffsetUploadNotAllowedWithMalwareScanningError.html +1 -1
- data/doc/Files/OneTimePasswordIncorrectError.html +1 -1
- data/doc/Files/OutboundConnectionLog.html +1 -1
- data/doc/Files/PartNumberTooLargeError.html +1 -1
- data/doc/Files/Partner.html +1 -1
- data/doc/Files/PartnerChannel.html +1 -1
- data/doc/Files/PartnerChannelTemplate.html +1 -1
- data/doc/Files/PartnerConnection.html +1 -1
- data/doc/Files/PartnerSite.html +1 -1
- data/doc/Files/PartnerSiteRequest.html +1 -1
- data/doc/Files/PasswordChangeNotRequiredError.html +1 -1
- data/doc/Files/PasswordChangeRequiredError.html +1 -1
- data/doc/Files/PathCannotHaveTrailingWhitespaceError.html +1 -1
- data/doc/Files/PathTooLongError.html +1 -1
- data/doc/Files/PathUtil.html +1 -1
- data/doc/Files/Payment.html +1 -1
- data/doc/Files/PaymentLineItem.html +1 -1
- data/doc/Files/PaymentMethodErrorError.html +1 -1
- data/doc/Files/PendingWorkEvent.html +1 -1
- data/doc/Files/Permission.html +1 -1
- data/doc/Files/PlanNotFoundError.html +1 -1
- data/doc/Files/Preview.html +1 -1
- data/doc/Files/PreviewOnlyPermissionCannotDownloadError.html +1 -1
- data/doc/Files/ProcessingFailureError.html +1 -1
- data/doc/Files/PublicHostingRequestLog.html +1 -1
- data/doc/Files/PublicIpAddress.html +1 -1
- data/doc/Files/PublicKey.html +1 -1
- data/doc/Files/RateLimitedError.html +1 -1
- data/doc/Files/ReadOnlySessionError.html +1 -1
- data/doc/Files/ReadPermissionRequiredError.html +1 -1
- data/doc/Files/ReauthenticationFailedError.html +1 -1
- data/doc/Files/ReauthenticationFailedFinalError.html +1 -1
- data/doc/Files/ReauthenticationNeededActionError.html +1 -1
- data/doc/Files/ReauthenticationNeededFieldsError.html +1 -1
- data/doc/Files/ReauthenticationRateLimitedError.html +1 -1
- data/doc/Files/RecaptchaFailedError.html +1 -1
- data/doc/Files/RecipientAlreadySharedError.html +1 -1
- data/doc/Files/RemoteBandwidthSnapshot.html +1 -1
- data/doc/Files/RemoteDesktopDebugLoggingDisabledError.html +1 -1
- data/doc/Files/RemoteEntryReadOnlyError.html +1 -1
- data/doc/Files/RemoteMountBackend.html +1 -1
- data/doc/Files/RemoteServer.html +1 -1
- data/doc/Files/RemoteServerConfigurationFile.html +1 -1
- data/doc/Files/RemoteServerCredential.html +1 -1
- data/doc/Files/RemoteServerErrorError.html +1 -1
- data/doc/Files/Request.html +1 -1
- data/doc/Files/RequestBodyTooLargeError.html +1 -1
- data/doc/Files/RequestParamsContainInvalidCharacterError.html +1 -1
- data/doc/Files/RequestParamsInvalidError.html +1 -1
- data/doc/Files/RequestParamsRequiredError.html +1 -1
- data/doc/Files/ResourceBelongsToParentSiteError.html +1 -1
- data/doc/Files/ResourceLockedError.html +1 -1
- data/doc/Files/Response.html +1 -1
- data/doc/Files/Restore.html +1 -1
- data/doc/Files/RootFolderBehaviorSiteAdminRequiredError.html +1 -1
- data/doc/Files/RootFolderBehaviorSkipSiteAdminRequiredError.html +1 -1
- data/doc/Files/Schedule.html +1 -1
- data/doc/Files/ScheduledExport.html +1 -1
- data/doc/Files/ScimLog.html +1 -1
- data/doc/Files/SearchAllOnChildPathError.html +1 -1
- data/doc/Files/SearchUnavailableError.html +1 -1
- data/doc/Files/Secret.html +1 -1
- data/doc/Files/SelfManagedRequiredError.html +1 -1
- data/doc/Files/ServiceUnavailableError.html +1 -1
- data/doc/Files/Session.html +1 -1
- data/doc/Files/SettingsChange.html +1 -1
- data/doc/Files/SftpActionLog.html +1 -1
- data/doc/Files/SftpHostKey.html +1 -1
- data/doc/Files/ShareGroup.html +1 -1
- data/doc/Files/ShareGroupMember.html +1 -1
- data/doc/Files/SiemHttpDestination.html +1 -1
- data/doc/Files/SiemHttpDestinationEvent.html +1 -1
- data/doc/Files/Site.html +1 -1
- data/doc/Files/SiteAdminOrPartnerAdminPermissionRequiredError.html +1 -1
- data/doc/Files/SiteAdminOrWorkspaceAdminOrFolderAdminPermissionRequiredError.html +1 -1
- data/doc/Files/SiteAdminOrWorkspaceAdminOrPartnerAdminOrFolderAdminPermissionRequiredError.html +1 -1
- data/doc/Files/SiteAdminOrWorkspaceAdminOrPartnerAdminPermissionRequiredError.html +1 -1
- data/doc/Files/SiteAdminOrWorkspaceAdminPermissionRequiredError.html +1 -1
- data/doc/Files/SiteAdminRequiredError.html +1 -1
- data/doc/Files/SiteConfigurationError.html +1 -1
- data/doc/Files/SiteDisabledError.html +1 -1
- data/doc/Files/SiteFilesAreImmutableError.html +1 -1
- data/doc/Files/SiteNotFoundError.html +1 -1
- data/doc/Files/SiteSubdomainRedirect.html +1 -1
- data/doc/Files/SiteWasRemovedError.html +1 -1
- data/doc/Files/SizableIO.html +1 -1
- data/doc/Files/Snapshot.html +1 -1
- data/doc/Files/SsoEvent.html +1 -1
- data/doc/Files/SsoStrategy.html +1 -1
- data/doc/Files/Status.html +1 -1
- data/doc/Files/Style.html +1 -1
- data/doc/Files/SubfolderLockedError.html +1 -1
- data/doc/Files/Sync.html +1 -1
- data/doc/Files/SyncInProgressError.html +1 -1
- data/doc/Files/SyncLog.html +1 -1
- data/doc/Files/SyncRun.html +1 -1
- data/doc/Files/SyncRunLiveTransfer.html +1 -1
- data/doc/Files/SystemProfiler.html +1 -1
- data/doc/Files/TooManyConcurrentLoginsError.html +1 -1
- data/doc/Files/TooManyConcurrentRequestsError.html +1 -1
- data/doc/Files/TooManyLoginAttemptsError.html +1 -1
- data/doc/Files/TooManyRequestsError.html +1 -1
- data/doc/Files/TooManySharesError.html +1 -1
- data/doc/Files/Transfer/BufferLimitError.html +175 -0
- data/doc/Files/Transfer/ByteBudget/Reservation.html +301 -0
- data/doc/Files/Transfer/ByteBudget.html +748 -0
- data/doc/Files/Transfer/CancelledError.html +235 -0
- data/doc/Files/Transfer/Checkpoint.html +1371 -0
- data/doc/Files/Transfer/CheckpointError.html +173 -0
- data/doc/Files/Transfer/ClientTransport/ClientResponse.html +661 -0
- data/doc/Files/Transfer/ClientTransport/Reader.html +530 -0
- data/doc/Files/Transfer/ClientTransport.html +675 -0
- data/doc/Files/Transfer/Clock.html +189 -0
- data/doc/Files/Transfer/Control.html +667 -0
- data/doc/Files/Transfer/Controller/Attempt.html +465 -0
- data/doc/Files/Transfer/Controller.html +1166 -0
- data/doc/Files/Transfer/Coordinator/Timing.html +568 -0
- data/doc/Files/Transfer/Coordinator.html +534 -0
- data/doc/Files/Transfer/DownloadCoordinator/FileSink.html +976 -0
- data/doc/Files/Transfer/DownloadCoordinator/IoSink.html +1168 -0
- data/doc/Files/Transfer/DownloadCoordinator/Piece.html +875 -0
- data/doc/Files/Transfer/DownloadCoordinator.html +728 -0
- data/doc/Files/Transfer/FileSlots.html +480 -0
- data/doc/Files/Transfer/FrontendResponseError.html +235 -0
- data/doc/Files/Transfer/Holds.html +505 -0
- data/doc/Files/Transfer/HttpTransport.html +579 -0
- data/doc/Files/Transfer/Job.html +2338 -0
- data/doc/Files/Transfer/Options.html +924 -0
- data/doc/Files/Transfer/PausedError.html +319 -0
- data/doc/Files/Transfer/Policy.html +1642 -0
- data/doc/Files/Transfer/RequestPermits.html +160 -0
- data/doc/Files/Transfer/SourceChangedError.html +235 -0
- data/doc/Files/Transfer/StorageResponse.html +546 -0
- data/doc/Files/Transfer/StorageRetry.html +1098 -0
- data/doc/Files/Transfer/UploadCoordinator/Grant.html +1048 -0
- data/doc/Files/Transfer/UploadCoordinator/Part.html +1048 -0
- data/doc/Files/Transfer/UploadCoordinator/Region.html +295 -0
- data/doc/Files/Transfer/UploadCoordinator/Segments.html +299 -0
- data/doc/Files/Transfer/UploadCoordinator/Source.html +725 -0
- data/doc/Files/Transfer/UploadCoordinator/StreamSource.html +664 -0
- data/doc/Files/Transfer/UploadCoordinator.html +546 -0
- data/doc/Files/Transfer/UploadPlan.html +522 -0
- data/doc/Files/Transfer/VersionChangedError.html +235 -0
- data/doc/Files/Transfer.html +134 -0
- data/doc/Files/TrialExpiredError.html +1 -1
- data/doc/Files/TrialLockedError.html +1 -1
- data/doc/Files/TwoFactorAuthenticationCodeAlreadySentError.html +1 -1
- data/doc/Files/TwoFactorAuthenticationCountryBlacklistedError.html +1 -1
- data/doc/Files/TwoFactorAuthenticationErrorError.html +1 -1
- data/doc/Files/TwoFactorAuthenticationGeneralErrorError.html +1 -1
- data/doc/Files/TwoFactorAuthenticationMethodUnsupportedErrorError.html +1 -1
- data/doc/Files/TwoFactorAuthenticationRequiredError.html +1 -1
- data/doc/Files/TwoFactorAuthenticationSetupExpiredError.html +1 -1
- data/doc/Files/TwoFactorAuthenticationUnsubscribedRecipientError.html +1 -1
- data/doc/Files/URI.html +1 -1
- data/doc/Files/UnrecognizedSortIndexError.html +1 -1
- data/doc/Files/UnsupportedCurrencyError.html +1 -1
- data/doc/Files/UnsupportedHttpResponseFormatError.html +1 -1
- data/doc/Files/UnsupportedMediaTypeError.html +1 -1
- data/doc/Files/UpdatesNotAllowedForRemotesError.html +1 -1
- data/doc/Files/UploadsUnavailableError.html +1 -1
- data/doc/Files/UsageByTopLevelDir.html +1 -1
- data/doc/Files/UsageDailySnapshot.html +1 -1
- data/doc/Files/UsageSnapshot.html +1 -1
- data/doc/Files/User.html +1 -1
- data/doc/Files/UserAdditionalEmailRecipient.html +1 -1
- data/doc/Files/UserCipherUse.html +1 -1
- data/doc/Files/UserIdInvalidError.html +1 -1
- data/doc/Files/UserIdOnUserEndpointError.html +1 -1
- data/doc/Files/UserIdWithoutSiteAdminError.html +1 -1
- data/doc/Files/UserLifecycleRule.html +1 -1
- data/doc/Files/UserNotFoundError.html +1 -1
- data/doc/Files/UserRequest.html +1 -1
- data/doc/Files/UserRequestsEnabledRequiredError.html +1 -1
- data/doc/Files/UserRequiredError.html +1 -1
- data/doc/Files/UserSecurityEvent.html +1 -1
- data/doc/Files/UserSftpClientUse.html +1 -1
- data/doc/Files/Util/LogSettings.html +1 -1
- data/doc/Files/Util.html +1 -1
- data/doc/Files/WebDavActionLog.html +1 -1
- data/doc/Files/WebhookTest.html +1 -1
- data/doc/Files/Workspace.html +1 -1
- data/doc/Files/WriteAndBundlePermissionRequiredError.html +1 -1
- data/doc/Files/WritePermissionRequiredError.html +1 -1
- data/doc/Files/ZipListEntry.html +1 -1
- data/doc/Files.html +116 -116
- data/doc/_index.html +314 -4
- data/doc/class_list.html +1 -1
- data/doc/file.README.html +1 -1
- data/doc/index.html +1 -1
- data/doc/method_list.html +4703 -2415
- data/doc/top-level-namespace.html +1 -1
- data/docs/file.md +346 -0
- data/lib/files.com/api.rb +6 -1
- data/lib/files.com/api_client.rb +220 -32
- data/lib/files.com/models/file.rb +124 -17
- data/lib/files.com/transfer/budgets.rb +123 -0
- data/lib/files.com/transfer/checkpoint.rb +113 -0
- data/lib/files.com/transfer/clock.rb +14 -0
- data/lib/files.com/transfer/controller.rb +549 -0
- data/lib/files.com/transfer/download_coordinator.rb +906 -0
- data/lib/files.com/transfer/errors.rb +100 -0
- data/lib/files.com/transfer/http_transport.rb +337 -0
- data/lib/files.com/transfer/job.rb +778 -0
- data/lib/files.com/transfer/options.rb +126 -0
- data/lib/files.com/transfer/policy.rb +91 -0
- data/lib/files.com/transfer/storage_retry.rb +169 -0
- data/lib/files.com/transfer/upload_coordinator.rb +657 -0
- data/lib/files.com/transfer/upload_plan.rb +157 -0
- data/lib/files.com.rb +13 -0
- data/spec/lib/api_client_spec.rb +125 -3
- data/spec/models/file_transfer_spec.rb +53 -4
- data/spec/support/transfer_server.rb +113 -14
- data/spec/transfer/adaptive_download_spec.rb +1102 -0
- data/spec/transfer/adaptive_upload_spec.rb +1602 -0
- data/spec/transfer/concurrency_controller_spec.rb +382 -0
- data/spec/workspace_id_spec.rb +19 -14
- metadata +59 -1
|
@@ -57,14 +57,70 @@ module Files
|
|
|
57
57
|
# Downloads a remote file to a local file, replacing its contents.
|
|
58
58
|
# @param path [String] remote file path.
|
|
59
59
|
# @param local_path [String, nil] local destination; defaults to the remote basename.
|
|
60
|
+
# @param transfer [Boolean, Hash, Files::Transfer::Options, Files::Transfer::Job, nil] adaptive options or a shared job.
|
|
61
|
+
# @param progress [#call, nil] progress callback; requires transfer.
|
|
60
62
|
# @return [void]
|
|
61
63
|
# @raise [Files::InvalidParameterError] if the default name is unsafe on Windows; supply local_path explicitly.
|
|
62
64
|
# @raise [StandardError] if the transfer or local write fails.
|
|
63
|
-
|
|
65
|
+
# With transfer:, the download is adaptive, and can report progress (see #download_file).
|
|
66
|
+
def self.download_file(path, local_path = nil, progress: nil, transfer: nil)
|
|
64
67
|
local_path ||= default_local_path(path)
|
|
68
|
+
return new(path).download_file(local_path, progress: progress, transfer: transfer) if transfer
|
|
69
|
+
raise InvalidParameterError, "Bad parameter: progress needs transfer" if progress
|
|
70
|
+
|
|
65
71
|
new(path).download_file(local_path)
|
|
66
72
|
end
|
|
67
73
|
|
|
74
|
+
# Continues a download a Files::Transfer::PausedError stopped, in the same process, from its checkpoint: into the
|
|
75
|
+
# local file it was writing, or into destination, the same IO it was writing. options are the API options, which a
|
|
76
|
+
# checkpoint never keeps; progress and transfer: are as for #download_file, transfer: true for the defaults, with
|
|
77
|
+
# written_bytes counting the bytes from before the pause. One resume at a time can use a checkpoint, and once a
|
|
78
|
+
# resume has started its transfer the checkpoint is spent, however it ends; a pause gives a new one. Raises
|
|
79
|
+
# Files::Transfer::CheckpointError, before anything is sent, written or truncated, when the destination or the bytes
|
|
80
|
+
# already written to it changed or cannot be read back, and Files::Transfer::VersionChangedError when the remote
|
|
81
|
+
# file did.
|
|
82
|
+
def self.resume_download(checkpoint, destination = nil, options = {}, progress: nil, transfer: true)
|
|
83
|
+
raise InvalidParameterError, "Bad parameter: checkpoint must be a Files::Transfer::Checkpoint" unless checkpoint.is_a?(Transfer::Checkpoint)
|
|
84
|
+
|
|
85
|
+
Transfer::Job.using(transfer) { |job| Transfer::DownloadCoordinator.resume(job, checkpoint, destination, options, progress: progress) }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Continues an upload a Files::Transfer::PausedError stopped, in the same process, from its checkpoint, sending only
|
|
89
|
+
# the parts storage had not acknowledged. options are the API options, which a checkpoint never keeps; progress and
|
|
90
|
+
# transfer: are as for upload_file, with progress counting the acknowledged bytes from before the pause. One resume
|
|
91
|
+
# at a time can use a checkpoint, and once a resume has started its transfer the checkpoint is spent, however it
|
|
92
|
+
# ends; a pause gives a new one. Raises Files::Transfer::CheckpointError, before anything is sent, when the local
|
|
93
|
+
# file changed. Returns what upload_file returns.
|
|
94
|
+
def self.resume_upload(checkpoint, options = {}, progress: nil, transfer: true)
|
|
95
|
+
raise InvalidParameterError, "Bad parameter: checkpoint must be a Files::Transfer::Checkpoint" unless checkpoint.is_a?(Transfer::Checkpoint)
|
|
96
|
+
|
|
97
|
+
Transfer::Job.using(transfer) { |job| Transfer::UploadCoordinator.resume(job, checkpoint, options, progress: progress) }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Uploads what io, a caller's IO or StringIO, holds from where it stands to destination, and returns what
|
|
101
|
+
# upload_file returns. With size, an Integer, and transfer: (as for upload_file), the upload is adaptive: exactly
|
|
102
|
+
# size bytes are read on this thread with read(length), part by part in order, through as many reads as it takes,
|
|
103
|
+
# each part only once buffer_bytes can hold it, and a copy of what each read returns, taken before the next read,
|
|
104
|
+
# is kept until storage acknowledges the part, so a retry sends the same bytes even when io's reads reuse one
|
|
105
|
+
# String; io's Strings are never changed. io ending before size bytes raises
|
|
106
|
+
# Files::Transfer::SourceChangedError, and nothing past them is read. Without size or transfer:, io is read to its
|
|
107
|
+
# end and uploaded a part at a time, as upload_chunks does. io is never rewound or closed. A paused stream upload
|
|
108
|
+
# cannot be resumed: its PausedError has no checkpoint. progress is as for upload_file, with total_bytes size (nil
|
|
109
|
+
# when it is not given).
|
|
110
|
+
def self.upload_io(io, destination, options = {}, size: nil, params: {}, progress: nil, transfer: nil)
|
|
111
|
+
raise InvalidParameterError, "Bad parameter: size must be a non-negative Integer" unless size.nil? || (size.is_a?(Integer) && size >= 0)
|
|
112
|
+
|
|
113
|
+
return Transfer::Job.using(transfer) { |job| Transfer::UploadCoordinator.upload_io(job, io, size, destination, options, params: params, progress: progress) } if transfer && size
|
|
114
|
+
|
|
115
|
+
params = params.dup
|
|
116
|
+
params[:size] ||= size if size
|
|
117
|
+
report = progress && ->(part) { progress.call(part.merge(total_bytes: size)) }
|
|
118
|
+
upload, etags, bytes = upload_chunks(io, destination, options, params: params, progress: report)
|
|
119
|
+
raise InvalidParameterError, "Bad parameter: io held #{bytes} bytes, not size #{size}" if size && bytes != size
|
|
120
|
+
|
|
121
|
+
create(destination, { action: "end", etags: etags, ref: upload.ref, size: bytes }, options)
|
|
122
|
+
end
|
|
123
|
+
|
|
68
124
|
private_class_method def self.default_local_path(path)
|
|
69
125
|
name = PathUtil.normalize(path).split("/").last.to_s
|
|
70
126
|
# On Windows a colon makes the name a drive-relative path or an NTFS
|
|
@@ -90,6 +146,11 @@ module Files
|
|
|
90
146
|
PathUtil.normalize("_", root, id.to_s, relative_path)
|
|
91
147
|
end
|
|
92
148
|
|
|
149
|
+
# The transfer: keyword to pass on, only when the caller gave one.
|
|
150
|
+
private_class_method def self.transfer_keywords(transfer)
|
|
151
|
+
transfer ? { transfer: transfer } : {}
|
|
152
|
+
end
|
|
153
|
+
|
|
93
154
|
# Uploads a local file into the RemoteServers destination namespace.
|
|
94
155
|
# @param path [String] local file path.
|
|
95
156
|
# @param remote_server_id [Integer] destination ID.
|
|
@@ -97,10 +158,11 @@ module Files
|
|
|
97
158
|
# @param options [Hash] request configuration; see {Files::Api.send_request}.
|
|
98
159
|
# @param params [Hash] upload API parameters.
|
|
99
160
|
# @param progress [#call, nil] receives the acknowledged-part Hash described by {.upload_file}.
|
|
161
|
+
# @param transfer [Boolean, Hash, Files::Transfer::Options, Files::Transfer::Job, nil] adaptive options or a shared job.
|
|
100
162
|
# @return [Files::File] the completed upload.
|
|
101
|
-
def self.upload_to_remote_server(path, remote_server_id, destination = nil, options = {}, params: {}, progress: nil)
|
|
163
|
+
def self.upload_to_remote_server(path, remote_server_id, destination = nil, options = {}, params: {}, progress: nil, transfer: nil)
|
|
102
164
|
destination ||= ::File.basename(path)
|
|
103
|
-
upload_file(path, underscore_destination_path("RemoteServers", remote_server_id, destination), options, params: params, progress: progress)
|
|
165
|
+
upload_file(path, underscore_destination_path("RemoteServers", remote_server_id, destination), options, params: params, progress: progress, **transfer_keywords(transfer))
|
|
104
166
|
end
|
|
105
167
|
|
|
106
168
|
# Copies a remote file into the RemoteServers destination namespace.
|
|
@@ -132,10 +194,11 @@ module Files
|
|
|
132
194
|
# @param options [Hash] request configuration; see {Files::Api.send_request}.
|
|
133
195
|
# @param params [Hash] upload API parameters.
|
|
134
196
|
# @param progress [#call, nil] receives the acknowledged-part Hash described by {.upload_file}.
|
|
197
|
+
# @param transfer [Boolean, Hash, Files::Transfer::Options, Files::Transfer::Job, nil] adaptive options or a shared job.
|
|
135
198
|
# @return [Files::File] the completed upload.
|
|
136
|
-
def self.upload_to_snapshot(path, snapshot_id, destination = nil, options = {}, params: {}, progress: nil)
|
|
199
|
+
def self.upload_to_snapshot(path, snapshot_id, destination = nil, options = {}, params: {}, progress: nil, transfer: nil)
|
|
137
200
|
destination ||= ::File.basename(path)
|
|
138
|
-
upload_file(path, underscore_destination_path("Snapshots", snapshot_id, destination), options, params: params, progress: progress)
|
|
201
|
+
upload_file(path, underscore_destination_path("Snapshots", snapshot_id, destination), options, params: params, progress: progress, **transfer_keywords(transfer))
|
|
139
202
|
end
|
|
140
203
|
|
|
141
204
|
# Copies a remote file into the Snapshots destination namespace.
|
|
@@ -167,10 +230,11 @@ module Files
|
|
|
167
230
|
# @param options [Hash] request configuration; see {Files::Api.send_request}.
|
|
168
231
|
# @param params [Hash] upload API parameters.
|
|
169
232
|
# @param progress [#call, nil] receives the acknowledged-part Hash described by {.upload_file}.
|
|
233
|
+
# @param transfer [Boolean, Hash, Files::Transfer::Options, Files::Transfer::Job, nil] adaptive options or a shared job.
|
|
170
234
|
# @return [Files::File] the completed upload.
|
|
171
|
-
def self.upload_to_child_site(path, site_id, destination = nil, options = {}, params: {}, progress: nil)
|
|
235
|
+
def self.upload_to_child_site(path, site_id, destination = nil, options = {}, params: {}, progress: nil, transfer: nil)
|
|
172
236
|
destination ||= ::File.basename(path)
|
|
173
|
-
upload_file(path, underscore_destination_path("Sites", site_id, destination), options, params: params, progress: progress)
|
|
237
|
+
upload_file(path, underscore_destination_path("Sites", site_id, destination), options, params: params, progress: progress, **transfer_keywords(transfer))
|
|
174
238
|
end
|
|
175
239
|
|
|
176
240
|
# Copies a remote file into the Sites destination namespace.
|
|
@@ -336,10 +400,11 @@ module Files
|
|
|
336
400
|
end
|
|
337
401
|
|
|
338
402
|
# Uploads what io holds to path, a part at a time as the API grants each, and returns the last part, the parts'
|
|
339
|
-
# ETags and the bytes sent. Each part is filled from as many reads as it takes (read_part).
|
|
340
|
-
#
|
|
341
|
-
#
|
|
342
|
-
#
|
|
403
|
+
# ETags and the bytes sent. Each part is filled from as many reads as it takes (read_part). Each part's response
|
|
404
|
+
# must be a success (2xx) before the part counts as acknowledged; any other status raises APIError before another
|
|
405
|
+
# part is planned or the upload is ended. With progress, a callable, progress is then called with total_bytes (nil:
|
|
406
|
+
# unknown here), acknowledged_bytes (this call's bytes so far), part_number and part_bytes. Anything it raises ends
|
|
407
|
+
# the upload there, with no later part planned or sent.
|
|
343
408
|
# @param io [#read, #eof?] source, read from its current position.
|
|
344
409
|
# @param path [String] remote destination path.
|
|
345
410
|
# @param options [Hash] request configuration; see {Files::Api.send_request}.
|
|
@@ -360,7 +425,7 @@ module Files
|
|
|
360
425
|
bytes_written += buf.length
|
|
361
426
|
method = upload.http_method.downcase.to_sym
|
|
362
427
|
response = client(options).remote_request(method, upload.upload_uri, { 'Content-Length': buf.length.to_s, 'Content-Type': 'application/octet-stream' }, buf)
|
|
363
|
-
raise APIError.new("Transfer request failed", http_status: response.status), cause: nil
|
|
428
|
+
raise APIError.new("Transfer request failed", http_status: response.status), cause: nil unless response.status.between?(200, 299)
|
|
364
429
|
|
|
365
430
|
etags << { etag: response.headers["ETag"], part: upload.part_number }
|
|
366
431
|
progress&.call({ total_bytes: nil, acknowledged_bytes: bytes_written, part_number: upload.part_number, part_bytes: buf.length })
|
|
@@ -390,15 +455,27 @@ module Files
|
|
|
390
455
|
|
|
391
456
|
# With progress, a callable, it is called after each part is acknowledged, as upload_chunks reports it, with
|
|
392
457
|
# total_bytes the local file's size.
|
|
458
|
+
#
|
|
459
|
+
# With transfer: (true, a Hash of Files::Transfer::Options, an Options or a shared Files::Transfer::Job), the
|
|
460
|
+
# upload is adaptive: its parts are sent concurrently, as many at once as the link sustains within those options,
|
|
461
|
+
# and progress is called on this thread once for each part storage acknowledged, in the order they were. A
|
|
462
|
+
# Files::Transfer::Control in the options can cancel or pause it. Without transfer:, parts go one at a time, as
|
|
463
|
+
# they always have. See docs/file.md.
|
|
393
464
|
# @param path [String] local file path.
|
|
394
465
|
# @param destination [String, nil] remote path; defaults to the local basename.
|
|
395
466
|
# @param options [Hash] request configuration; see {Files::Api.send_request}.
|
|
396
467
|
# @param params [Hash] upload API parameters.
|
|
397
468
|
# @param progress [#call, nil] called with total_bytes, acknowledged_bytes, part_number and part_bytes keys.
|
|
398
469
|
# Counts are bytes; part_number is the API part number. Raising stops the upload before any later part.
|
|
470
|
+
# @param transfer [Boolean, Hash, Files::Transfer::Options, Files::Transfer::Job, nil] adaptive options or a shared job.
|
|
399
471
|
# @return [Files::File] the completed upload.
|
|
400
472
|
# @raise [StandardError] if reading, a request or the progress callback fails; the local file is still closed.
|
|
401
|
-
def self.upload_file(path, destination = nil, options = {}, params: {}, progress: nil)
|
|
473
|
+
def self.upload_file(path, destination = nil, options = {}, params: {}, progress: nil, transfer: nil)
|
|
474
|
+
if transfer
|
|
475
|
+
destination ||= ::File.basename(path)
|
|
476
|
+
return Transfer::Job.using(transfer) { |job| Transfer::UploadCoordinator.upload(job, path, destination, options, params: params, progress: progress) }
|
|
477
|
+
end
|
|
478
|
+
|
|
402
479
|
local_file = ::File.open(path, 'r')
|
|
403
480
|
destination ||= File.basename(path)
|
|
404
481
|
params[:size] ||= local_file.size
|
|
@@ -607,9 +684,18 @@ module Files
|
|
|
607
684
|
|
|
608
685
|
# Downloads this remote file to a local file, replacing its contents.
|
|
609
686
|
# @param output_file [String] local destination path.
|
|
687
|
+
# @param transfer [Boolean, Hash, Files::Transfer::Options, Files::Transfer::Job, nil] adaptive options or a shared job.
|
|
688
|
+
# @param progress [#call, nil] progress callback; requires transfer.
|
|
610
689
|
# @return [void]
|
|
611
690
|
# @raise [StandardError] if the transfer or local write fails.
|
|
612
|
-
|
|
691
|
+
# With transfer: (as for File.upload_file), the download is adaptive: when storage binds the file's version, its
|
|
692
|
+
# ranges are fetched concurrently and written in order, and progress, a callable, is called on this thread after
|
|
693
|
+
# each write with total_bytes (nil until known), written_bytes, elapsed_seconds and bytes_per_second. Anything it
|
|
694
|
+
# raises ends the download. progress needs transfer:. See docs/file.md.
|
|
695
|
+
def download_file(output_file, progress: nil, transfer: nil)
|
|
696
|
+
return Transfer::Job.using(transfer) { |job| Transfer::DownloadCoordinator.download_file(job, self, output_file, progress: progress) } if transfer
|
|
697
|
+
raise InvalidParameterError, "Bad parameter: progress needs transfer" if progress
|
|
698
|
+
|
|
613
699
|
::File.open(output_file, 'wb') do |file|
|
|
614
700
|
download_content(file)
|
|
615
701
|
end
|
|
@@ -619,9 +705,19 @@ module Files
|
|
|
619
705
|
# @param io [#write, #<<] destination stream.
|
|
620
706
|
# @param range [Array<Integer>] optional inclusive start and end byte offsets.
|
|
621
707
|
# Applied by the Net::HTTP adapter; other adapters fetch the whole response.
|
|
708
|
+
# @param transfer [Boolean, Hash, Files::Transfer::Options, Files::Transfer::Job, nil] adaptive options or a shared job.
|
|
709
|
+
# @param progress [#call, nil] progress callback; requires transfer.
|
|
622
710
|
# @return [void]
|
|
623
711
|
# @raise [StandardError] if the transfer or destination fails.
|
|
624
|
-
|
|
712
|
+
# With transfer:, as for #download_file, into io; it cannot be combined with range.
|
|
713
|
+
def download_content(io, range: [], progress: nil, transfer: nil)
|
|
714
|
+
if transfer
|
|
715
|
+
raise InvalidParameterError, "Bad parameter: range cannot be combined with transfer" unless range.nil? || range.empty?
|
|
716
|
+
|
|
717
|
+
return Transfer::Job.using(transfer) { |job| Transfer::DownloadCoordinator.download_content(job, self, io, progress: progress) }
|
|
718
|
+
end
|
|
719
|
+
raise InvalidParameterError, "Bad parameter: progress needs transfer" if progress
|
|
720
|
+
|
|
625
721
|
Files::ApiClient.download_client.stream_download(download_uri_with_load, io, range)
|
|
626
722
|
end
|
|
627
723
|
|
|
@@ -758,7 +854,11 @@ module Files
|
|
|
758
854
|
# @param options [Hash] keyword arguments for {#download_content}, such as range.
|
|
759
855
|
# @return [Files::SizableIO] the pipe reader; the caller owns closing it.
|
|
760
856
|
# @raise [StandardError] if the download fails while the reader is being prepared.
|
|
857
|
+
# Never adaptive: transfer: and progress: are refused here, before any pipe or download thread exists.
|
|
761
858
|
def read_io(**options)
|
|
859
|
+
raise InvalidParameterError, "Bad parameter: read_io cannot transfer adaptively; use #download_content with transfer:" if options[:transfer]
|
|
860
|
+
raise InvalidParameterError, "Bad parameter: progress needs transfer" if options[:progress]
|
|
861
|
+
|
|
762
862
|
@read_io ||= begin
|
|
763
863
|
r, w = SizableIO.pipe
|
|
764
864
|
Thread.new do
|
|
@@ -994,8 +1094,15 @@ module Files
|
|
|
994
1094
|
raise NotImplementedError
|
|
995
1095
|
end
|
|
996
1096
|
|
|
997
|
-
|
|
998
|
-
|
|
1097
|
+
# Uploads local_file, an opened local file, to this record's path with this record's API options, and returns
|
|
1098
|
+
# what File.upload_file returns. It reads local_file's path through its own handle and leaves local_file open.
|
|
1099
|
+
# Only the record's mkdir_parents and with_rename go to begin_upload, never its other attributes, such as its size
|
|
1100
|
+
# or download_uri. transfer: is as for File.upload_file.
|
|
1101
|
+
def upload_file(local_file, transfer: nil)
|
|
1102
|
+
params = @attributes.slice(:mkdir_parents, :with_rename)
|
|
1103
|
+
return File.upload_file(local_file.path, path, @options, params: params, transfer: transfer) if transfer
|
|
1104
|
+
|
|
1105
|
+
File.upload_file(local_file.path, path, @options, params: params)
|
|
999
1106
|
end
|
|
1000
1107
|
|
|
1001
1108
|
# Buffers bytes, or uses a readable IO as the upload source.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Files
|
|
4
|
+
module Transfer
|
|
5
|
+
# The bytes a job may hold in memory at once, for parts waiting to be sent or written. A part reserves its bytes
|
|
6
|
+
# before anything is read for it, keeps them through its retries and gives them back once it is acknowledged,
|
|
7
|
+
# written, abandoned or stopped. A part larger than the whole budget is refused before it reads anything, even when
|
|
8
|
+
# nothing else is held. Its methods run under the job's lock.
|
|
9
|
+
class ByteBudget
|
|
10
|
+
Reservation = Struct.new(:bytes, :released)
|
|
11
|
+
|
|
12
|
+
attr_reader :limit, :held
|
|
13
|
+
|
|
14
|
+
def initialize(limit)
|
|
15
|
+
@limit = limit
|
|
16
|
+
@held = 0
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def check!(bytes)
|
|
20
|
+
return if bytes <= @limit
|
|
21
|
+
|
|
22
|
+
raise BufferLimitError, "A #{bytes}-byte part needs more memory than the transfer's buffer_bytes of #{@limit}"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def available?(bytes)
|
|
26
|
+
check!(bytes)
|
|
27
|
+
@held + bytes <= @limit
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def reserve(bytes)
|
|
31
|
+
raise ArgumentError, "the transfer buffer cannot hold #{bytes} more bytes" unless available?(bytes)
|
|
32
|
+
|
|
33
|
+
@held += bytes
|
|
34
|
+
Reservation.new(bytes, false)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Gives back what reservation holds, once however often it is called.
|
|
38
|
+
def release(reservation)
|
|
39
|
+
return if reservation.nil? || reservation.released
|
|
40
|
+
|
|
41
|
+
reservation.released = true
|
|
42
|
+
@held -= reservation.bytes
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Gives back all but bytes of what reservation holds, which holds bytes from then on. Nothing changes for a
|
|
46
|
+
# reservation released already or holding no more than bytes.
|
|
47
|
+
def shrink(reservation, bytes)
|
|
48
|
+
return if reservation.nil? || reservation.released || bytes >= reservation.bytes
|
|
49
|
+
|
|
50
|
+
@held -= reservation.bytes - bytes
|
|
51
|
+
reservation.bytes = bytes
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# How many files a job transfers at once. Its methods run under the job's lock.
|
|
56
|
+
class FileSlots
|
|
57
|
+
attr_reader :limit, :used
|
|
58
|
+
|
|
59
|
+
def initialize(limit)
|
|
60
|
+
@limit = limit
|
|
61
|
+
@used = 0
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def take
|
|
65
|
+
return false if @used >= @limit
|
|
66
|
+
|
|
67
|
+
@used += 1
|
|
68
|
+
true
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def give
|
|
72
|
+
@used -= 1 if @used.positive?
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# How many content requests a job has in flight at once, across all its transfers and destinations: heads, whole
|
|
77
|
+
# responses, parts and ranges alike. A request holds its permit from just before it is sent until its response is
|
|
78
|
+
# read or it fails, never while it waits to be retried. Its methods run under the job's lock.
|
|
79
|
+
class RequestPermits < FileSlots; end
|
|
80
|
+
|
|
81
|
+
# How long each destination is held back after a throttle: no new content request or retry of a job's, from any of
|
|
82
|
+
# its transfers and through whichever transport or controller, goes to it until the latest Retry-After any of them
|
|
83
|
+
# got from it has passed. A later Retry-After can extend that, never shorten it, and requests already sent finish. A
|
|
84
|
+
# destination is an origin: its scheme, host and port. Its methods run under the job's lock.
|
|
85
|
+
class Holds
|
|
86
|
+
# The destination url names, or nil when it names none.
|
|
87
|
+
def self.destination(url)
|
|
88
|
+
uri = ::URI.parse(url.to_s)
|
|
89
|
+
[ uri.scheme.to_s.downcase, uri.host.downcase, uri.port ].freeze if uri.host
|
|
90
|
+
rescue ::URI::InvalidURIError
|
|
91
|
+
nil
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def initialize(clock: Clock)
|
|
95
|
+
@clock = clock
|
|
96
|
+
@until = {}
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Holds destination back for seconds from now, unless it already is for longer.
|
|
100
|
+
def hold(destination, seconds)
|
|
101
|
+
return unless destination && seconds.to_f.positive?
|
|
102
|
+
|
|
103
|
+
deadline = @clock.now + seconds.to_f
|
|
104
|
+
@until[destination] = [ @until[destination] || deadline, deadline ].max
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# The monotonic time until which destination is held back, or nil once that has passed.
|
|
108
|
+
def deadline(destination)
|
|
109
|
+
deadline = @until[destination]
|
|
110
|
+
return deadline if deadline && deadline > @clock.now
|
|
111
|
+
|
|
112
|
+
@until.delete(destination)
|
|
113
|
+
nil
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Each destination held back now, as its origin, with the seconds left.
|
|
117
|
+
def to_h
|
|
118
|
+
now = @clock.now
|
|
119
|
+
@until.select { |_, deadline| deadline > now }.to_h { |(scheme, host, port), deadline| [ "#{scheme}://#{host}:#{port}", deadline - now ] }
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Files
|
|
4
|
+
module Transfer
|
|
5
|
+
# Where a paused transfer stopped, for Files::File.resume_upload or Files::File.resume_download in the same process.
|
|
6
|
+
#
|
|
7
|
+
# It holds no credentials, signed URLs, API options or API response fields; resuming takes the options again. It
|
|
8
|
+
# records, frozen, what it must find unchanged: an upload's local file (device, inode, size and modification
|
|
9
|
+
# time), its reference, the upload parameters it began with (only mkdir_parents, with_rename and size), a
|
|
10
|
+
# fingerprint of each part's instructions (a SHA-256 digest of their method, headers, size and retry rules, never
|
|
11
|
+
# their URL) and the parts storage acknowledged; a download's destination (a local file's device and inode, or the
|
|
12
|
+
# caller's IO object itself), the number of bytes written to it with a SHA-256 digest of them, and the remote
|
|
13
|
+
# file's version. Resuming refuses with CheckpointError, before it sends, writes or truncates anything, when any of
|
|
14
|
+
# them changed; a renewed part whose instructions differ stops the upload, and a download stops with
|
|
15
|
+
# VersionChangedError when storage no longer has that version.
|
|
16
|
+
#
|
|
17
|
+
# One resume at a time holds it. Once that resume has checked the local file or destination and starts its
|
|
18
|
+
# transfer, the checkpoint is spent for good, however the transfer ends: a pause then gives a new checkpoint. Only a
|
|
19
|
+
# resume refused before it started, as above, or paused or cancelled before its turn or before it finished those
|
|
20
|
+
# checks, leaves it usable. It cannot be dumped or resumed in another process.
|
|
21
|
+
class Checkpoint
|
|
22
|
+
# One resume's hold on a checkpoint, which only that resume's transfer has. start! spends the checkpoint.
|
|
23
|
+
class Lease
|
|
24
|
+
def initialize
|
|
25
|
+
@started = false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def start!
|
|
29
|
+
@started = true
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def started?
|
|
33
|
+
@started
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
private_constant :Lease
|
|
37
|
+
|
|
38
|
+
attr_reader :direction, :path, :local_path, :total_bytes, :completed_bytes, :source, :sink, :version, :state
|
|
39
|
+
|
|
40
|
+
# The identity of an opened local file, from its stat: [device, inode], with size and modification time unless
|
|
41
|
+
# contents is false.
|
|
42
|
+
def self.identity(stat, contents: true)
|
|
43
|
+
contents ? [ stat.dev, stat.ino, stat.size, stat.mtime.to_r ] : [ stat.dev, stat.ino ]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# A frozen copy of value's Hashes, Arrays and Strings, all the way down. Anything else, such as a caller's IO, is
|
|
47
|
+
# kept as it is.
|
|
48
|
+
def self.frozen(value)
|
|
49
|
+
case value
|
|
50
|
+
when Hash then value.to_h { |key, item| [ frozen(key), frozen(item) ] }.freeze
|
|
51
|
+
when Array then value.map { |item| frozen(item) }.freeze
|
|
52
|
+
when String then value.frozen? ? value : value.dup.freeze
|
|
53
|
+
else value
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def initialize(direction:, path:, total_bytes:, completed_bytes:, local_path: nil, source: nil, sink: nil, version: nil, state: {})
|
|
58
|
+
@process = Process.pid
|
|
59
|
+
@direction = direction
|
|
60
|
+
@path = path.dup.freeze
|
|
61
|
+
@local_path = local_path&.dup&.freeze
|
|
62
|
+
@total_bytes = total_bytes
|
|
63
|
+
@completed_bytes = completed_bytes
|
|
64
|
+
@source = Checkpoint.frozen(source)
|
|
65
|
+
@sink = Checkpoint.frozen(sink)
|
|
66
|
+
@version = version&.dup&.freeze
|
|
67
|
+
@state = Checkpoint.frozen(state)
|
|
68
|
+
@hold_lock = Mutex.new
|
|
69
|
+
@hold = [ :ready ]
|
|
70
|
+
freeze
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Raises CheckpointError unless this checkpoint can resume a transfer in direction (:upload or :download).
|
|
74
|
+
def verify!(direction)
|
|
75
|
+
raise CheckpointError, "Bad parameter: a transfer checkpoint resumes only in the process that paused it" unless @process == Process.pid
|
|
76
|
+
raise CheckpointError, "Bad parameter: this checkpoint resumes #{@direction == :upload ? 'an upload' : 'a download'}" unless @direction == direction
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Runs the block, which resumes the transfer in direction, as the one resume holding this checkpoint, and returns
|
|
80
|
+
# what it returns. The block gets the resume's lease and calls its start! once its checks pass, before it sends,
|
|
81
|
+
# writes or truncates anything; from then on the checkpoint is spent, whatever happens. Raises CheckpointError as
|
|
82
|
+
# verify! does, while another resume holds it, and once it is spent.
|
|
83
|
+
def resuming(direction)
|
|
84
|
+
verify!(direction)
|
|
85
|
+
lease = Lease.new
|
|
86
|
+
@hold_lock.synchronize do
|
|
87
|
+
case @hold[0]
|
|
88
|
+
when Lease then raise CheckpointError, "Bad parameter: another resume is already using this checkpoint"
|
|
89
|
+
when :spent then raise CheckpointError, "Bad parameter: this checkpoint has already been used to resume its transfer; resume from the checkpoint its own pause gave"
|
|
90
|
+
end
|
|
91
|
+
@hold[0] = lease
|
|
92
|
+
end
|
|
93
|
+
begin
|
|
94
|
+
yield lease
|
|
95
|
+
ensure
|
|
96
|
+
@hold_lock.synchronize { @hold[0] = lease.started? ? :spent : :ready }
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def inspect
|
|
101
|
+
"#<#{self.class.name} #{@direction} #{@path.inspect} #{@completed_bytes} of #{@total_bytes.inspect} bytes>"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def to_s
|
|
105
|
+
inspect
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def marshal_dump
|
|
109
|
+
raise TypeError, "a Files::Transfer::Checkpoint resumes only in the process that paused it, so it cannot be dumped"
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Files
|
|
4
|
+
module Transfer
|
|
5
|
+
# Seconds on the monotonic clock, which changes to the system time never move. Measurements, retry delays and
|
|
6
|
+
# cooldowns use it; only a signed URL's expiry, which storage states in wall-clock time, is compared with Time.now.
|
|
7
|
+
# Anything with the same now method can stand in for it, as a test's controlled clock does.
|
|
8
|
+
module Clock
|
|
9
|
+
def self.now
|
|
10
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|