files.com 1.1.813 → 1.1.815
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 +911 -731
- 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 +597 -386
- 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 +5368 -3048
- data/doc/top-level-namespace.html +1 -1
- data/docs/file.md +346 -0
- data/docs/site.md +10 -0
- data/docs/user.md +8 -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/models/site.rb +16 -0
- data/lib/files.com/models/user.rb +19 -0
- 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.
|
|
@@ -115,6 +115,12 @@ module Files
|
|
|
115
115
|
@attributes[:allow_user_level_allowed_ip_override]
|
|
116
116
|
end
|
|
117
117
|
|
|
118
|
+
# Allow per-user overrides of the FTP TLS session reuse requirement?
|
|
119
|
+
# @return [Boolean, nil] the field value, or nil when absent.
|
|
120
|
+
def allow_user_level_ftp_tls_session_reuse_override
|
|
121
|
+
@attributes[:allow_user_level_ftp_tls_session_reuse_override]
|
|
122
|
+
end
|
|
123
|
+
|
|
118
124
|
# Allow the site-wide FTP SSL requirement to be overriden on a per-user-basis?
|
|
119
125
|
# @return [Boolean, nil] the field value, or nil when absent.
|
|
120
126
|
def allow_user_level_ssl_override
|
|
@@ -1012,6 +1018,12 @@ module Files
|
|
|
1012
1018
|
@attributes[:snapshot_sharing_enabled]
|
|
1013
1019
|
end
|
|
1014
1020
|
|
|
1021
|
+
# Require TLS-protected FTP data connections to reuse the control connection TLS session? Applies to all workspaces.
|
|
1022
|
+
# @return [Boolean, nil] the field value, or nil when absent.
|
|
1023
|
+
def ftp_tls_session_reuse_required
|
|
1024
|
+
@attributes[:ftp_tls_session_reuse_required]
|
|
1025
|
+
end
|
|
1026
|
+
|
|
1015
1027
|
# Is SSL required? Disabling this is insecure.
|
|
1016
1028
|
# @return [Boolean, nil] the field value, or nil when absent.
|
|
1017
1029
|
def ssl_required
|
|
@@ -1256,6 +1268,7 @@ module Files
|
|
|
1256
1268
|
# @option params [Integer] session_expiry_minutes Session expiry in minutes
|
|
1257
1269
|
# @option params [Boolean] legacy_pairing_enabled Allow legacy app pairing in all workspaces? Disabled for new sites; existing sites remain enabled until changed. Disabling blocks new pairing exchanges without revoking existing app credentials or affecting OAuth sign-in. Site administrators can re-enable it.
|
|
1258
1270
|
# @option params [Boolean] ssl_required Is SSL required? Disabling this is insecure.
|
|
1271
|
+
# @option params [Boolean] ftp_tls_session_reuse_required Require TLS-protected FTP data connections to reuse the control connection TLS session? Applies to all workspaces.
|
|
1259
1272
|
# @option params [Boolean] sftp_insecure_ciphers If true, we will allow weak and known insecure ciphers to be used for SFTP connections. Enabling this setting severely weakens the security of your site and it is not recommend, except as a last resort for compatibility.
|
|
1260
1273
|
# @option params [Boolean] sftp_insecure_diffie_hellman If true, we will allow weak Diffie Hellman parameters to be used within ciphers for SFTP that are otherwise on our secure list. This has the effect of making the cipher weaker than our normal threshold for security, but is required to support certain legacy or broken SSH and MFT clients. Enabling this weakens security, but not nearly as much as enabling the full `sftp_insecure_ciphers` option.
|
|
1261
1274
|
# @option params [Boolean] disable_files_certificate_generation If set, Files.com will not set the CAA records required to generate future SSL certificates for this domain.
|
|
@@ -1270,6 +1283,7 @@ module Files
|
|
|
1270
1283
|
# @option params [Boolean] allow_user_level_2fa_override Allow the site-wide two-factor authentication requirement to be overriden on a per-user-basis?
|
|
1271
1284
|
# @option params [Boolean] allow_user_level_allowed_ip_override Allow the site-wide allowed IP restriction to be overriden on a per-user-basis?
|
|
1272
1285
|
# @option params [Boolean] allow_user_level_ssl_override Allow the site-wide FTP SSL requirement to be overriden on a per-user-basis?
|
|
1286
|
+
# @option params [Boolean] allow_user_level_ftp_tls_session_reuse_override Allow per-user overrides of the FTP TLS session reuse requirement?
|
|
1273
1287
|
# @option params [Boolean] enforce_parent_site_user_ip_restrictions Require Parent Site users entering this Child Site to match its allowed IP addresses in addition to their existing IP restrictions. Applies to all Workspaces. Defaults to false.
|
|
1274
1288
|
# @option params [String] disallowed_countries Comma separated list of disallowed Country codes
|
|
1275
1289
|
# @option params [Integer] days_to_retain_backups Number of days to keep deleted files
|
|
@@ -1442,6 +1456,7 @@ module Files
|
|
|
1442
1456
|
raise InvalidParameterError.new("Bad parameter: session_expiry_minutes must be an Integer") if !params[:session_expiry_minutes].nil? and !params[:session_expiry_minutes].is_a?(Integer)
|
|
1443
1457
|
raise InvalidParameterError.new("Bad parameter: legacy_pairing_enabled must be a single value, not #{params[:legacy_pairing_enabled].class}") if [ Array, Hash ].any? { |klass| params[:legacy_pairing_enabled].is_a?(klass) }
|
|
1444
1458
|
raise InvalidParameterError.new("Bad parameter: ssl_required must be a single value, not #{params[:ssl_required].class}") if [ Array, Hash ].any? { |klass| params[:ssl_required].is_a?(klass) }
|
|
1459
|
+
raise InvalidParameterError.new("Bad parameter: ftp_tls_session_reuse_required must be a single value, not #{params[:ftp_tls_session_reuse_required].class}") if [ Array, Hash ].any? { |klass| params[:ftp_tls_session_reuse_required].is_a?(klass) }
|
|
1445
1460
|
raise InvalidParameterError.new("Bad parameter: sftp_insecure_ciphers must be a single value, not #{params[:sftp_insecure_ciphers].class}") if [ Array, Hash ].any? { |klass| params[:sftp_insecure_ciphers].is_a?(klass) }
|
|
1446
1461
|
raise InvalidParameterError.new("Bad parameter: sftp_insecure_diffie_hellman must be a single value, not #{params[:sftp_insecure_diffie_hellman].class}") if [ Array, Hash ].any? { |klass| params[:sftp_insecure_diffie_hellman].is_a?(klass) }
|
|
1447
1462
|
raise InvalidParameterError.new("Bad parameter: disable_files_certificate_generation must be a single value, not #{params[:disable_files_certificate_generation].class}") if [ Array, Hash ].any? { |klass| params[:disable_files_certificate_generation].is_a?(klass) }
|
|
@@ -1456,6 +1471,7 @@ module Files
|
|
|
1456
1471
|
raise InvalidParameterError.new("Bad parameter: allow_user_level_2fa_override must be a single value, not #{params[:allow_user_level_2fa_override].class}") if [ Array, Hash ].any? { |klass| params[:allow_user_level_2fa_override].is_a?(klass) }
|
|
1457
1472
|
raise InvalidParameterError.new("Bad parameter: allow_user_level_allowed_ip_override must be a single value, not #{params[:allow_user_level_allowed_ip_override].class}") if [ Array, Hash ].any? { |klass| params[:allow_user_level_allowed_ip_override].is_a?(klass) }
|
|
1458
1473
|
raise InvalidParameterError.new("Bad parameter: allow_user_level_ssl_override must be a single value, not #{params[:allow_user_level_ssl_override].class}") if [ Array, Hash ].any? { |klass| params[:allow_user_level_ssl_override].is_a?(klass) }
|
|
1474
|
+
raise InvalidParameterError.new("Bad parameter: allow_user_level_ftp_tls_session_reuse_override must be a single value, not #{params[:allow_user_level_ftp_tls_session_reuse_override].class}") if [ Array, Hash ].any? { |klass| params[:allow_user_level_ftp_tls_session_reuse_override].is_a?(klass) }
|
|
1459
1475
|
raise InvalidParameterError.new("Bad parameter: enforce_parent_site_user_ip_restrictions must be a single value, not #{params[:enforce_parent_site_user_ip_restrictions].class}") if [ Array, Hash ].any? { |klass| params[:enforce_parent_site_user_ip_restrictions].is_a?(klass) }
|
|
1460
1476
|
raise InvalidParameterError.new("Bad parameter: disallowed_countries must be an String") if !params[:disallowed_countries].nil? and !params[:disallowed_countries].is_a?(String)
|
|
1461
1477
|
raise InvalidParameterError.new("Bad parameter: days_to_retain_backups must be an Integer") if !params[:days_to_retain_backups].nil? and !params[:days_to_retain_backups].is_a?(Integer)
|
|
@@ -1011,6 +1011,19 @@ module Files
|
|
|
1011
1011
|
@attributes[:skip_welcome_screen] = value
|
|
1012
1012
|
end
|
|
1013
1013
|
|
|
1014
|
+
# FTP TLS session reuse requirement: use_system_setting, always_require, or never_require
|
|
1015
|
+
# @return [String, nil] the field value, or nil when absent.
|
|
1016
|
+
def ftp_tls_session_reuse_required
|
|
1017
|
+
@attributes[:ftp_tls_session_reuse_required]
|
|
1018
|
+
end
|
|
1019
|
+
|
|
1020
|
+
# Changes the local field value. Call {#save} to persist changes.
|
|
1021
|
+
# @param value [String, nil] the new field value.
|
|
1022
|
+
# @return [String, nil] the assigned value.
|
|
1023
|
+
def ftp_tls_session_reuse_required=(value)
|
|
1024
|
+
@attributes[:ftp_tls_session_reuse_required] = value
|
|
1025
|
+
end
|
|
1026
|
+
|
|
1014
1027
|
# SSL required setting
|
|
1015
1028
|
# @return [String, nil] the field value, or nil when absent.
|
|
1016
1029
|
def ssl_required
|
|
@@ -1429,6 +1442,7 @@ module Files
|
|
|
1429
1442
|
# @option params [Boolean] site_admin Is the user an administrator for this site?
|
|
1430
1443
|
# @option params [Boolean] skip_welcome_screen Skip Welcome page in the UI?
|
|
1431
1444
|
# @option params [String] ssl_required SSL required setting
|
|
1445
|
+
# @option params [String] ftp_tls_session_reuse_required FTP TLS session reuse requirement: use_system_setting, always_require, or never_require
|
|
1432
1446
|
# @option params [Integer] sso_strategy_id SSO (Single Sign On) strategy ID for the user, if applicable.
|
|
1433
1447
|
# @option params [String] require_2fa 2FA required setting. `use_system_setting` uses the site-wide setting, including SSO exemptions. `always_require` and `never_require` override the site-wide setting when user-level overrides are allowed.
|
|
1434
1448
|
# @option params [String] tags Comma-separated list of Tags for this user. Tags are used for other features, such as UserLifecycleRules, which can target specific tags. Tags must only contain lowercase letters, numbers, and hyphens.
|
|
@@ -1507,6 +1521,7 @@ module Files
|
|
|
1507
1521
|
raise InvalidParameterError.new("Bad parameter: site_admin must be a single value, not #{params[:site_admin].class}") if [ Array, Hash ].any? { |klass| params[:site_admin].is_a?(klass) }
|
|
1508
1522
|
raise InvalidParameterError.new("Bad parameter: skip_welcome_screen must be a single value, not #{params[:skip_welcome_screen].class}") if [ Array, Hash ].any? { |klass| params[:skip_welcome_screen].is_a?(klass) }
|
|
1509
1523
|
raise InvalidParameterError.new("Bad parameter: ssl_required must be an String") if !params[:ssl_required].nil? and !params[:ssl_required].is_a?(String)
|
|
1524
|
+
raise InvalidParameterError.new("Bad parameter: ftp_tls_session_reuse_required must be an String") if !params[:ftp_tls_session_reuse_required].nil? and !params[:ftp_tls_session_reuse_required].is_a?(String)
|
|
1510
1525
|
raise InvalidParameterError.new("Bad parameter: sso_strategy_id must be an Integer") if !params[:sso_strategy_id].nil? and !params[:sso_strategy_id].is_a?(Integer)
|
|
1511
1526
|
raise InvalidParameterError.new("Bad parameter: require_2fa must be an String") if !params[:require_2fa].nil? and !params[:require_2fa].is_a?(String)
|
|
1512
1527
|
raise InvalidParameterError.new("Bad parameter: tags must be an String") if !params[:tags].nil? and !params[:tags].is_a?(String)
|
|
@@ -1707,6 +1722,7 @@ module Files
|
|
|
1707
1722
|
# @option params [Boolean] site_admin Is the user an administrator for this site?
|
|
1708
1723
|
# @option params [Boolean] skip_welcome_screen Skip Welcome page in the UI?
|
|
1709
1724
|
# @option params [String] ssl_required SSL required setting
|
|
1725
|
+
# @option params [String] ftp_tls_session_reuse_required FTP TLS session reuse requirement: use_system_setting, always_require, or never_require
|
|
1710
1726
|
# @option params [Integer] sso_strategy_id SSO (Single Sign On) strategy ID for the user, if applicable.
|
|
1711
1727
|
# @option params [String] require_2fa 2FA required setting. `use_system_setting` uses the site-wide setting, including SSO exemptions. `always_require` and `never_require` override the site-wide setting when user-level overrides are allowed.
|
|
1712
1728
|
# @option params [String] tags Comma-separated list of Tags for this user. Tags are used for other features, such as UserLifecycleRules, which can target specific tags. Tags must only contain lowercase letters, numbers, and hyphens.
|
|
@@ -1780,6 +1796,7 @@ module Files
|
|
|
1780
1796
|
raise InvalidParameterError.new("Bad parameter: site_admin must be a single value, not #{params[:site_admin].class}") if [ Array, Hash ].any? { |klass| params[:site_admin].is_a?(klass) }
|
|
1781
1797
|
raise InvalidParameterError.new("Bad parameter: skip_welcome_screen must be a single value, not #{params[:skip_welcome_screen].class}") if [ Array, Hash ].any? { |klass| params[:skip_welcome_screen].is_a?(klass) }
|
|
1782
1798
|
raise InvalidParameterError.new("Bad parameter: ssl_required must be an String") if !params[:ssl_required].nil? and !params[:ssl_required].is_a?(String)
|
|
1799
|
+
raise InvalidParameterError.new("Bad parameter: ftp_tls_session_reuse_required must be an String") if !params[:ftp_tls_session_reuse_required].nil? and !params[:ftp_tls_session_reuse_required].is_a?(String)
|
|
1783
1800
|
raise InvalidParameterError.new("Bad parameter: sso_strategy_id must be an Integer") if !params[:sso_strategy_id].nil? and !params[:sso_strategy_id].is_a?(Integer)
|
|
1784
1801
|
raise InvalidParameterError.new("Bad parameter: require_2fa must be an String") if !params[:require_2fa].nil? and !params[:require_2fa].is_a?(String)
|
|
1785
1802
|
raise InvalidParameterError.new("Bad parameter: tags must be an String") if !params[:tags].nil? and !params[:tags].is_a?(String)
|
|
@@ -1915,6 +1932,7 @@ module Files
|
|
|
1915
1932
|
# @option params [Boolean] site_admin Is the user an administrator for this site?
|
|
1916
1933
|
# @option params [Boolean] skip_welcome_screen Skip Welcome page in the UI?
|
|
1917
1934
|
# @option params [String] ssl_required SSL required setting
|
|
1935
|
+
# @option params [String] ftp_tls_session_reuse_required FTP TLS session reuse requirement: use_system_setting, always_require, or never_require
|
|
1918
1936
|
# @option params [Integer] sso_strategy_id SSO (Single Sign On) strategy ID for the user, if applicable.
|
|
1919
1937
|
# @option params [String] require_2fa 2FA required setting. `use_system_setting` uses the site-wide setting, including SSO exemptions. `always_require` and `never_require` override the site-wide setting when user-level overrides are allowed.
|
|
1920
1938
|
# @option params [String] tags Comma-separated list of Tags for this user. Tags are used for other features, such as UserLifecycleRules, which can target specific tags. Tags must only contain lowercase letters, numbers, and hyphens.
|
|
@@ -1993,6 +2011,7 @@ module Files
|
|
|
1993
2011
|
raise InvalidParameterError.new("Bad parameter: site_admin must be a single value, not #{params[:site_admin].class}") if [ Array, Hash ].any? { |klass| params[:site_admin].is_a?(klass) }
|
|
1994
2012
|
raise InvalidParameterError.new("Bad parameter: skip_welcome_screen must be a single value, not #{params[:skip_welcome_screen].class}") if [ Array, Hash ].any? { |klass| params[:skip_welcome_screen].is_a?(klass) }
|
|
1995
2013
|
raise InvalidParameterError.new("Bad parameter: ssl_required must be an String") if !params[:ssl_required].nil? and !params[:ssl_required].is_a?(String)
|
|
2014
|
+
raise InvalidParameterError.new("Bad parameter: ftp_tls_session_reuse_required must be an String") if !params[:ftp_tls_session_reuse_required].nil? and !params[:ftp_tls_session_reuse_required].is_a?(String)
|
|
1996
2015
|
raise InvalidParameterError.new("Bad parameter: sso_strategy_id must be an Integer") if !params[:sso_strategy_id].nil? and !params[:sso_strategy_id].is_a?(Integer)
|
|
1997
2016
|
raise InvalidParameterError.new("Bad parameter: require_2fa must be an String") if !params[:require_2fa].nil? and !params[:require_2fa].is_a?(String)
|
|
1998
2017
|
raise InvalidParameterError.new("Bad parameter: tags must be an String") if !params[:tags].nil? and !params[:tags].is_a?(String)
|
|
@@ -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
|