Repository: cloudinary/cloudinary_ios
Branch: master
Commit: ea94096c41ad
Files: 444
Total size: 3.2 MB
Directory structure:
gitextract_f1wonv0o/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── pull_request_template.md
├── .gitignore
├── .gitmodules
├── .swiftpm/
│ └── xcode/
│ └── package.xcworkspace/
│ └── contents.xcworkspacedata
├── .travis.yml
├── CHANGELOG.md
├── Cloudinary/
│ ├── Assets/
│ │ └── .gitkeep
│ └── Classes/
│ ├── Core/
│ │ ├── BaseNetwork/
│ │ │ ├── CLDNConvertible.swift
│ │ │ ├── CLDNError.swift
│ │ │ ├── CLDNMultipartFormData.swift
│ │ │ ├── CLDNParameterEncoding.swift
│ │ │ ├── CLDNRequest.swift
│ │ │ ├── CLDNResponse.swift
│ │ │ ├── CLDNResponseSerialization.swift
│ │ │ ├── CLDNResult.swift
│ │ │ ├── CLDNSessionDelegate.swift
│ │ │ ├── CLDNSessionManager.swift
│ │ │ ├── CLDNTaskDelegate.swift
│ │ │ ├── CLDNTimeline.swift
│ │ │ ├── CLDNValidation.swift
│ │ │ └── DispatchQueue+Cloudinary.swift
│ │ ├── CLDAnalytics.swift
│ │ ├── CLDCloudinary.swift
│ │ ├── CLDCompatibility.swift
│ │ ├── CLDConfiguration.swift
│ │ ├── CLDEagerTransformation.swift
│ │ ├── CLDResponsiveParams.swift
│ │ ├── Cloudinary.h
│ │ ├── Features/
│ │ │ ├── CacheSystem/
│ │ │ │ └── Enum/
│ │ │ │ └── HTTPStatusCode.swift
│ │ │ ├── Downloader/
│ │ │ │ └── CLDDownloader.swift
│ │ │ ├── Helpers/
│ │ │ │ ├── CLDBaseNetworkObject.swift
│ │ │ │ ├── CLDConditionExpression.swift
│ │ │ │ ├── CLDDefinitions.swift
│ │ │ │ ├── CLDExpression.swift
│ │ │ │ ├── CLDOperators.swift
│ │ │ │ ├── CLDTransformation.swift
│ │ │ │ ├── CLDVariable.swift
│ │ │ │ ├── Layers/
│ │ │ │ │ ├── CLDFetchLayer.swift
│ │ │ │ │ ├── CLDLayer.swift
│ │ │ │ │ ├── CLDSubtitlesLayer.swift
│ │ │ │ │ └── CLDTextLayer.swift
│ │ │ │ ├── RequestParams/
│ │ │ │ │ ├── CLDRequestParams.swift
│ │ │ │ │ └── Helpers/
│ │ │ │ │ └── CLDRequestParamsHelpers.swift
│ │ │ │ ├── Requests/
│ │ │ │ │ └── CLDRequest.swift
│ │ │ │ └── Results/
│ │ │ │ ├── CLDBaseResult.swift
│ │ │ │ └── Helpers/
│ │ │ │ ├── AccessibilityAnalysis/
│ │ │ │ │ ├── CLDAccessibilityAnalysisResult.swift
│ │ │ │ │ └── CLDColorblindAccessibilityAnalysisResult.swift
│ │ │ │ ├── CLDBoundingBox.swift
│ │ │ │ ├── CLDDetection.swift
│ │ │ │ ├── CLDFace.swift
│ │ │ │ ├── CLDInfo.swift
│ │ │ │ ├── CLDOcr/
│ │ │ │ │ ├── CLDAdvOcrResult.swift
│ │ │ │ │ ├── CLDOcrBlockResult.swift
│ │ │ │ │ ├── CLDOcrBoundindBlockResult.swift
│ │ │ │ │ ├── CLDOcrDataResult.swift
│ │ │ │ │ ├── CLDOcrDetectedLanguagesResult.swift
│ │ │ │ │ ├── CLDOcrFullTextAnnotationResult.swift
│ │ │ │ │ ├── CLDOcrPageResult.swift
│ │ │ │ │ ├── CLDOcrParagraphResult.swift
│ │ │ │ │ ├── CLDOcrPropertyResult.swift
│ │ │ │ │ ├── CLDOcrResult.swift
│ │ │ │ │ ├── CLDOcrSymbolResult.swift
│ │ │ │ │ ├── CLDOcrTextAnnotationResult.swift
│ │ │ │ │ └── CLDOcrWordResult.swift
│ │ │ │ ├── CLDQualityAnalysis/
│ │ │ │ │ └── CLDQualityAnalysis.swift
│ │ │ │ ├── CLDRekognitionFace.swift
│ │ │ │ └── CommonResultKeys.swift
│ │ │ ├── ManagementApi/
│ │ │ │ ├── CLDManagementApi.swift
│ │ │ │ ├── Requests/
│ │ │ │ │ ├── CLDDeleteRequest.swift
│ │ │ │ │ ├── CLDExplicitRequest.swift
│ │ │ │ │ ├── CLDExplodeRequest.swift
│ │ │ │ │ ├── CLDMultiRequest.swift
│ │ │ │ │ ├── CLDRenameRequest.swift
│ │ │ │ │ ├── CLDSpriteRequest.swift
│ │ │ │ │ ├── CLDTagRequest.swift
│ │ │ │ │ └── CLDTextRequest.swift
│ │ │ │ ├── RequestsParams/
│ │ │ │ │ ├── CLDDeleteByTokenRequestParams.swift
│ │ │ │ │ ├── CLDDestroyRequestParams.swift
│ │ │ │ │ ├── CLDExplicitRequestParams.swift
│ │ │ │ │ ├── CLDExplodeRequestParams.swift
│ │ │ │ │ ├── CLDMultiRequestParams.swift
│ │ │ │ │ ├── CLDRenameRequestParams.swift
│ │ │ │ │ ├── CLDSpriteRequestParams.swift
│ │ │ │ │ ├── CLDTagsRequestParams.swift
│ │ │ │ │ └── CLDTextRequestParams.swift
│ │ │ │ └── Results/
│ │ │ │ ├── CLDDeleteResult.swift
│ │ │ │ ├── CLDExplicitResult.swift
│ │ │ │ ├── CLDExplodeResult.swift
│ │ │ │ ├── CLDMultiResult.swift
│ │ │ │ ├── CLDRenameResult.swift
│ │ │ │ ├── CLDSpriteResult.swift
│ │ │ │ ├── CLDTagResult.swift
│ │ │ │ └── CLDTextResult.swift
│ │ │ ├── UploadWidget/
│ │ │ │ ├── CLDUploaderWidget.swift
│ │ │ │ ├── CropView/
│ │ │ │ │ ├── CLDCropOverlayView.swift
│ │ │ │ │ ├── CLDCropScrollView.swift
│ │ │ │ │ ├── CLDCropScrollViewController.swift
│ │ │ │ │ ├── CLDCropView.swift
│ │ │ │ │ ├── CLDCropViewCalculator.swift
│ │ │ │ │ └── CLDCropViewUIManager.swift
│ │ │ │ ├── WidgetConfiguration/
│ │ │ │ │ └── CLDWidgetConfiguration.swift
│ │ │ │ ├── WidgetElements/
│ │ │ │ │ ├── CLDWidgetAssetContainer.swift
│ │ │ │ │ └── CLDWidgetPreviewCollectionCell.swift
│ │ │ │ ├── WidgetImages/
│ │ │ │ │ ├── BackIconInstructions.swift
│ │ │ │ │ ├── CropIconInstructions.swift
│ │ │ │ │ ├── CropRotateIconInstructions.swift
│ │ │ │ │ ├── DoneIconInstructions.swift
│ │ │ │ │ ├── RatioLockedIconInstructions.swift
│ │ │ │ │ ├── RatioOpenedIconInstructions.swift
│ │ │ │ │ └── RotateIconInstructions.swift
│ │ │ │ ├── WidgetVideo/
│ │ │ │ │ ├── CLDDisplayLinkObserver.swift
│ │ │ │ │ ├── CLDVideoControlsState.swift
│ │ │ │ │ ├── CLDVideoControlsView.swift
│ │ │ │ │ ├── CLDVideoHiddenAndPausedState.swift
│ │ │ │ │ ├── CLDVideoHiddenAndPlayingState.swift
│ │ │ │ │ ├── CLDVideoPlayerView.swift
│ │ │ │ │ ├── CLDVideoShownAndPausedState.swift
│ │ │ │ │ ├── CLDVideoShownAndPlayingState.swift
│ │ │ │ │ └── CLDVideoView.swift
│ │ │ │ └── WidgetViewControllers/
│ │ │ │ ├── CLDWidgetEditViewController.swift
│ │ │ │ ├── CLDWidgetPreviewViewController.swift
│ │ │ │ └── CLDWidgetViewController.swift
│ │ │ ├── Uploader/
│ │ │ │ ├── CLDUploader.swift
│ │ │ │ ├── Preupload/
│ │ │ │ │ └── CLDPreprocessChain.swift
│ │ │ │ ├── RequestParams/
│ │ │ │ │ └── CLDUploadRequestParams.swift
│ │ │ │ ├── Requests/
│ │ │ │ │ ├── CLDDefaultUploadRequest.swift
│ │ │ │ │ ├── CLDUploadRequest.swift
│ │ │ │ │ └── CLDUploadRequestWrapper.swift
│ │ │ │ └── Results/
│ │ │ │ └── CLDUploadResult.swift
│ │ │ └── Url/
│ │ │ └── CLDUrl.swift
│ │ ├── Network/
│ │ │ ├── Adapter/
│ │ │ │ └── CLDNetworkAdapter.swift
│ │ │ ├── CLDDefaultNetworkAdapter.swift
│ │ │ ├── CLDNetworkCoordinator.swift
│ │ │ ├── NetworkRequest/
│ │ │ │ ├── CLDAsyncNetworkUploadRequest.swift
│ │ │ │ ├── CLDErrorRequest.swift
│ │ │ │ ├── CLDGenericNetworkRequest.swift
│ │ │ │ ├── CLDNetworkDataRequestImpl.swift
│ │ │ │ ├── CLDNetworkDownloadRequest.swift
│ │ │ │ └── CLDNetworkUploadRequest.swift
│ │ │ └── PrivacyInfo.xcprivacy
│ │ └── Utils/
│ │ ├── CLDBuildParamsUtils.swift
│ │ ├── CLDCryptoUtils.swift
│ │ ├── CLDDictionaryUtils.swift
│ │ ├── CLDError.swift
│ │ ├── CLDFileUtils.swift
│ │ ├── CLDImageGenerator/
│ │ │ └── CLDImageGenerator.swift
│ │ ├── CLDImageUtils.swift
│ │ ├── CLDJsonUtils.swift
│ │ ├── CLDLogManager.swift
│ │ └── CLDStringUtils.swift
│ └── ios/
│ ├── Extensions/
│ │ ├── CLDTransformation+Ios.swift
│ │ ├── ExtensionCLDDownloader.swift
│ │ ├── UIButton+Cloudinary.swift
│ │ ├── UIImageView+Cloudinary.swift
│ │ └── UIView+Cloudinary.swift
│ ├── NetworkRequest/
│ │ ├── CLDFetchAssetRequestImpl.swift
│ │ └── CLDFetchImageRequestImpl.swift
│ ├── UIViews/
│ │ ├── CLDResponsiveViewHelper.swift
│ │ └── CLDUIImageView.swift
│ ├── Uploader/
│ │ ├── CLDImagePreprocessChain.swift
│ │ ├── CLDPreprocessHelpers.swift
│ │ ├── CLDVideoPreprocessChain.swift
│ │ ├── CLDVideoPreprocessHelpers.swift
│ │ └── CLDVideoTranscode.swift
│ └── Video/
│ ├── Analytics/
│ │ ├── VideoAnalytics.swift
│ │ └── VideoEventsManager.swift
│ └── CLDVideoPlayer.swift
├── Cloudinary.podspec
├── Cloudinary.xcodeproj/
│ ├── Cloudinary_Info.plist
│ ├── project.pbxproj
│ ├── project.xcworkspace/
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata/
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
│ └── xcshareddata/
│ └── xcschemes/
│ └── Cloudinary-Package.xcscheme
├── Example/
│ ├── Cloudinary/
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets/
│ │ │ ├── AppIcon.appiconset/
│ │ │ │ ├── Contents.json
│ │ │ │ └── app_icon 1.heic
│ │ │ ├── Contents.json
│ │ │ ├── Delivery/
│ │ │ │ ├── Contents.json
│ │ │ │ ├── Inner/
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── ski.imageset/
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ └── sofa.imageset/
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── Video/
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── instagram.imageset/
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ ├── tiktok.imageset/
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ └── youtube.imageset/
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── background_normalizing.imageset/
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── color_alternation.imageset/
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── delivery-city.imageset/
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── localization_branding.imageset/
│ │ │ │ │ └── Contents.json
│ │ │ │ └── smart_cropping.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── Upload/
│ │ │ │ ├── Contents.json
│ │ │ │ ├── question_mark.imageset/
│ │ │ │ │ └── Contents.json
│ │ │ │ └── upload_missing.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── Video/
│ │ │ │ ├── Contents.json
│ │ │ │ └── TikTok/
│ │ │ │ ├── Contents.json
│ │ │ │ ├── tiktok_bar_icon.imageset/
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── tiktok_comments.imageset/
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── tiktok_discover.imageset/
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── tiktok_home.imageset/
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── tiktok_inbox.imageset/
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── tiktok_like.imageset/
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── tiktok_more.imageset/
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── tiktok_music.imageset/
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── tiktok_note.imageset/
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── tiktok_share.imageset/
│ │ │ │ │ └── Contents.json
│ │ │ │ └── tiktok_social_icon.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── Widgets/
│ │ │ │ ├── Contents.json
│ │ │ │ └── house.imageset/
│ │ │ │ ├── Contents.json
│ │ │ │ └── Frame_871_ao5o4r_2_viaeyi.heic
│ │ │ ├── back_arrow.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── car-speed-limiter.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── cloudinary_logo.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── info_icon.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── splash.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── splash_without_logo.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── upload.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── video.imageset/
│ │ │ │ └── Contents.json
│ │ │ └── widgets.imageset/
│ │ │ └── Contents.json
│ │ ├── CldModel.xcdatamodeld/
│ │ │ └── CldModel.xcdatamodel/
│ │ │ └── contents
│ │ ├── Cloudinary_Example-Bridging-Header.h
│ │ ├── Colors.xcassets/
│ │ │ ├── Contents.json
│ │ │ ├── gradient-first.colorset/
│ │ │ │ └── Contents.json
│ │ │ ├── gradient-second.colorset/
│ │ │ │ └── Contents.json
│ │ │ ├── primary.colorset/
│ │ │ │ └── Contents.json
│ │ │ ├── secondary.colorset/
│ │ │ │ └── Contents.json
│ │ │ ├── size_green.colorset/
│ │ │ │ └── Contents.json
│ │ │ ├── size_red.colorset/
│ │ │ │ └── Contents.json
│ │ │ ├── surface.colorset/
│ │ │ │ └── Contents.json
│ │ │ └── text_not_selected.colorset/
│ │ │ └── Contents.json
│ │ ├── Controllers/
│ │ │ ├── Base/
│ │ │ │ └── BaseViewController.swift
│ │ │ ├── Inner Views/
│ │ │ │ ├── Transform/
│ │ │ │ │ ├── DeliveryViewController.swift
│ │ │ │ │ ├── Optimization/
│ │ │ │ │ │ └── OptimizationViewController.swift
│ │ │ │ │ ├── Transformation/
│ │ │ │ │ │ ├── DeliveryTransformCollectionController.swift
│ │ │ │ │ │ ├── Reveal Image/
│ │ │ │ │ │ │ └── RevealImageController.swift
│ │ │ │ │ │ ├── Smart Cropping/
│ │ │ │ │ │ │ └── SmartCroppingController.swift
│ │ │ │ │ │ ├── TransformCollectionCell.swift
│ │ │ │ │ │ ├── TransformCollectionController.swift
│ │ │ │ │ │ ├── TransformViewController.swift
│ │ │ │ │ │ └── TransformationCell.swift
│ │ │ │ │ └── Use Cases/
│ │ │ │ │ ├── DeliveryUseCasesCollectionController.swift
│ │ │ │ │ ├── Screens/
│ │ │ │ │ │ ├── ConditionalProductViewController.swift
│ │ │ │ │ │ ├── IntegrateAIViewController.swift
│ │ │ │ │ │ └── NormalizingProductAssetsViewController.swift
│ │ │ │ │ ├── UseCaseCell.swift
│ │ │ │ │ ├── UseCaseCollectionCell.swift
│ │ │ │ │ ├── UseCasesCollectionController.swift
│ │ │ │ │ └── UseCasesViewController.swift
│ │ │ │ ├── Upload/
│ │ │ │ │ ├── InnerUploadFrame.swift
│ │ │ │ │ ├── Single Upload/
│ │ │ │ │ │ ├── SingleUploadCell.swift
│ │ │ │ │ │ ├── SingleUploadCollectionController.swift
│ │ │ │ │ │ ├── SingleUploadCollectionLayout.swift
│ │ │ │ │ │ ├── SingleUploadPreview.swift
│ │ │ │ │ │ └── SingleUploadViewController.swift
│ │ │ │ │ ├── Upload Does Not Exist/
│ │ │ │ │ │ └── UploadDoesNotExistController.swift
│ │ │ │ │ ├── UploadChoiceController.swift
│ │ │ │ │ ├── UploadNoCloudController.swift
│ │ │ │ │ └── UploadViewController.swift
│ │ │ │ └── Video/
│ │ │ │ ├── Video Feed/
│ │ │ │ │ └── VideoFeedCell.swift
│ │ │ │ └── VideoFeedCollectionController.swift
│ │ │ ├── MainViewController.swift
│ │ │ ├── SplashViewController.swift
│ │ │ ├── Video Feed/
│ │ │ │ ├── Controllers/
│ │ │ │ │ ├── MainPageController.swift
│ │ │ │ │ ├── VideoFeedContainerController.swift
│ │ │ │ │ ├── VideoFeedController.swift
│ │ │ │ │ ├── VideoFeedViewController.swift
│ │ │ │ │ ├── VideoSocialOverlaysController.swift
│ │ │ │ │ └── VideoViewController.swift
│ │ │ │ ├── Helpers/
│ │ │ │ │ └── VideoHelper.swift
│ │ │ │ └── Resources/
│ │ │ │ └── video_links.plist
│ │ │ └── Widgets/
│ │ │ ├── ImageWidgetViewController.swift
│ │ │ ├── UploadWidgetViewController.swift
│ │ │ └── WidgetsViewController.swift
│ │ ├── Core Data/
│ │ │ ├── AssetItems+CoreDataClass.swift
│ │ │ ├── AssetItems+CoreDataProperties.swift
│ │ │ ├── AssetModel.swift
│ │ │ ├── AssetModel.xcdatamodeld/
│ │ │ │ └── Model.xcdatamodel/
│ │ │ │ └── contents
│ │ │ └── CoreDataHelper.swift
│ │ ├── Custom Views/
│ │ │ ├── GradientView.swift
│ │ │ ├── RevealImageView.swift
│ │ │ ├── ToolBar/
│ │ │ │ ├── Item/
│ │ │ │ │ ├── ToolbarItem.swift
│ │ │ │ │ └── ToolbarItem.xib
│ │ │ │ ├── Toolbar.swift
│ │ │ │ └── Toolbar.xib
│ │ │ └── Upload Loading View/
│ │ │ ├── UploadLoadingView.swift
│ │ │ └── UploadLoadingView.xib
│ │ ├── Extensions/
│ │ │ └── Double+Extension.swift
│ │ ├── GoogleService-Info.plist
│ │ ├── Helpers/
│ │ │ ├── AnimationHelper.swift
│ │ │ ├── Events/
│ │ │ │ ├── EventObject.swift
│ │ │ │ ├── EventsHandler.swift
│ │ │ │ ├── EventsProvider.swift
│ │ │ │ └── FirebaseEventsHandler.swift
│ │ │ └── ImageHelper.swift
│ │ ├── Info.plist
│ │ ├── SceneDelegate.swift
│ │ ├── Utils/
│ │ │ ├── CloudinaryHelper.swift
│ │ │ └── FileUtils.swift
│ │ └── Views/
│ │ ├── Base.lproj/
│ │ │ ├── LaunchScreen.storyboard
│ │ │ └── Main.storyboard
│ │ ├── Inner Views/
│ │ │ ├── Base/
│ │ │ │ └── Base.storyboard
│ │ │ ├── Transform/
│ │ │ │ ├── Delivery.storyboard
│ │ │ │ ├── Optimization.storyboard
│ │ │ │ ├── Use Cases/
│ │ │ │ │ ├── ConditionalProduct.storyboard
│ │ │ │ │ ├── IntegrateAI.storyboard
│ │ │ │ │ └── NormalizingProductAssets.storyboard
│ │ │ │ └── UseCases.storyboard
│ │ │ ├── Upload/
│ │ │ │ ├── InnerUploadFrame.xib
│ │ │ │ ├── Upload.storyboard
│ │ │ │ └── UploadNoCloud.storyboard
│ │ │ ├── Video/
│ │ │ │ ├── Social/
│ │ │ │ │ └── VideoSocialOverlays.storyboard
│ │ │ │ ├── Video.storyboard
│ │ │ │ └── VideoFeed.storyboard
│ │ │ └── Widgets/
│ │ │ ├── ImageWidget.storyboard
│ │ │ ├── UploadWidget.storyboard
│ │ │ └── Widgets.storyboard
│ │ ├── Splash.storyboard
│ │ ├── Transform/
│ │ │ ├── RevealImage.storyboard
│ │ │ ├── SmartCropping.storyboard
│ │ │ └── Transform.storyboard
│ │ └── Upload/
│ │ ├── SingleUpload.storyboard
│ │ ├── SingleUploadPreview.storyboard
│ │ ├── UploadChoice.storyboard
│ │ └── UploadDoesNotExist.storyboard
│ ├── Cloudinary.xcodeproj/
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace/
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata/
│ │ │ └── IDEWorkspaceChecks.plist
│ │ └── xcshareddata/
│ │ └── xcschemes/
│ │ ├── Cloudinary-Example.xcscheme
│ │ └── travis_public_scheme.xcscheme
│ ├── Podfile
│ ├── README.md
│ ├── Tests/
│ │ ├── BaseNetwork/
│ │ │ ├── BaseTestCase.swift
│ │ │ ├── Core/
│ │ │ │ ├── AuthenticationTests.swift
│ │ │ │ ├── CLDCloudinaryTests.m
│ │ │ │ ├── CLDCloudinaryTests.swift
│ │ │ │ ├── ParameterEncodingTests.swift
│ │ │ │ ├── RequestTests.swift
│ │ │ │ ├── ResponseTests.swift
│ │ │ │ ├── ResultTests.swift
│ │ │ │ ├── SessionDelegateTests.swift
│ │ │ │ ├── SessionManagerTests.swift
│ │ │ │ └── UploadTests.swift
│ │ │ ├── Extensions/
│ │ │ │ ├── CLDNDataResponse+CloudinaryTests.swift
│ │ │ │ ├── CLDNError+CloudinaryTests.swift
│ │ │ │ ├── CLDNResult+CloudinaryTests.swift
│ │ │ │ └── FileManager+CloudinaryTests.swift
│ │ │ ├── Features/
│ │ │ │ ├── CacheTests.swift
│ │ │ │ ├── MultipartFormDataTests.swift
│ │ │ │ ├── ResponseSerializationTests.swift
│ │ │ │ ├── URLProtocolTests.swift
│ │ │ │ └── ValidationTests.swift
│ │ │ ├── ObjcBaseTestCase.h
│ │ │ ├── ObjcBaseTestCase.m
│ │ │ └── Resources/
│ │ │ └── Responses/
│ │ │ ├── JSON/
│ │ │ │ ├── empty_data.json
│ │ │ │ ├── invalid_data.json
│ │ │ │ └── valid_data.json
│ │ │ ├── Property List/
│ │ │ │ ├── empty.data
│ │ │ │ ├── invalid.data
│ │ │ │ └── valid.data
│ │ │ └── String/
│ │ │ ├── empty_string.txt
│ │ │ ├── utf32_string.txt
│ │ │ └── utf8_string.txt
│ │ ├── ConfigurationTests/
│ │ │ ├── CLDAnalyticsTests.swift
│ │ │ ├── CLDConfigurationTests.m
│ │ │ └── CLDConfigurationTests.swift
│ │ ├── CryptoUtilsTests/
│ │ │ ├── CryptoUtilsTests.m
│ │ │ └── CryptoUtilsTests.swift
│ │ ├── FileUtilsTests.swift
│ │ ├── GenerateUrlTests/
│ │ │ ├── UrlTests.m
│ │ │ └── UrlTests.swift
│ │ ├── Info.plist
│ │ ├── NetworkTests/
│ │ │ ├── AccessibilityUploderTests/
│ │ │ │ ├── UploaderAccessibilityTests.m
│ │ │ │ └── UploaderAccessibilityTests.swift
│ │ │ ├── DownloaderAssetTests.swift
│ │ │ ├── DownloaderTests.swift
│ │ │ ├── ManagementApiTests.swift
│ │ │ ├── NetworkBaseTests/
│ │ │ │ ├── NetworkBaseTest.swift
│ │ │ │ ├── NetworkBaseTestObjc.h
│ │ │ │ └── NetworkBaseTestObjc.m
│ │ │ ├── NetworkTestUtils.swift
│ │ │ └── UploaderTests/
│ │ │ ├── MockProvider/
│ │ │ │ └── BaseMockProvider.swift
│ │ │ ├── OcrUploaderTests/
│ │ │ │ ├── ExplicitMockOcrTests.m
│ │ │ │ ├── ExplicitMockOcrTests.swift
│ │ │ │ ├── OcrMockProvider.swift
│ │ │ │ ├── UploaderMockOcrTests.m
│ │ │ │ ├── UploaderMockOcrTests.swift
│ │ │ │ └── UploaderOcrTests.swift
│ │ │ ├── PreprocessUploaderTests/
│ │ │ │ └── PreprocessUploaderTests.swift
│ │ │ ├── QualityAnalysisUploaderTests/
│ │ │ │ ├── MockProviderQualityAnalysis.swift
│ │ │ │ ├── ObjcQualityAnalysisExplicitResultParserTests.m
│ │ │ │ ├── ObjcQualityAnalysisUploadResultParserTests.m
│ │ │ │ ├── ObjcUploaderQualityAnalysisTests.m
│ │ │ │ ├── QualityAnalysisExplicitResultParserTests.swift
│ │ │ │ ├── QualityAnalysisUploadResultParserTests.swift
│ │ │ │ └── UploaderQualityAnalysisTests.swift
│ │ │ └── UploaderTests.swift
│ │ ├── ParamTests/
│ │ │ └── UploadRequestParamsTests.swift
│ │ ├── Preprocess/
│ │ │ ├── PreprocessTests.swift
│ │ │ └── VideoPreprocessTests.swift
│ │ ├── Resources/
│ │ │ └── Docs/
│ │ │ └── docx.docx
│ │ ├── StringUtilsTest.swift
│ │ ├── TestableCloudinary.swift
│ │ ├── TransformationTests/
│ │ │ ├── CLDConditionExpressionTests/
│ │ │ │ ├── CLDConditionExpressionHelpersTests.swift
│ │ │ │ ├── CLDConditionExpressionTests.m
│ │ │ │ └── CLDConditionExpressionTests.swift
│ │ │ ├── CLDExpressionTests/
│ │ │ │ ├── CLDExpressionTests.m
│ │ │ │ └── CLDExpressionTests.swift
│ │ │ ├── CLDTransformationTests/
│ │ │ │ ├── CLDTransformationBaselineTests.swift
│ │ │ │ ├── CLDTransformationConditionsTests.swift
│ │ │ │ ├── CLDTransformationExpressionsTests.swift
│ │ │ │ ├── CLDTransformationTests.m
│ │ │ │ ├── CLDTransformationTests.swift
│ │ │ │ └── CLDTransformationVariablesTests.swift
│ │ │ └── CLDVariableTests/
│ │ │ ├── CLDVariableTests.m
│ │ │ └── CLDVariableTests.swift
│ │ ├── UIExtensions/
│ │ │ ├── CLDVideoPlayerTests.swift
│ │ │ ├── UIBaseTest.swift
│ │ │ ├── UIButtonTests.swift
│ │ │ ├── UIImageViewTests.swift
│ │ │ └── Video Analytics/
│ │ │ ├── VideoEventsManagerTests.swift
│ │ │ └── VideoEventsTests.swift
│ │ └── UploadWidgetTests/
│ │ ├── UploadWidgetHelpersTests/
│ │ │ ├── UploaderWidgetAssetContainerTests.swift
│ │ │ ├── UploaderWidgetConfigurationTests.m
│ │ │ └── UploaderWidgetConfigurationTests.swift
│ │ ├── UploadWidgetVideoTests/
│ │ │ ├── UploaderWidgetVideoControlsTests.swift
│ │ │ ├── UploaderWidgetVideoDisplayLinkTests.swift
│ │ │ ├── UploaderWidgetVideoPlayerTests.swift
│ │ │ └── UploaderWidgetVideoViewTests.swift
│ │ ├── UploaderWidgetEditTests/
│ │ │ └── UploaderWidgetEditViewControllerTests.swift
│ │ ├── UploaderWidgetPreviewTests/
│ │ │ ├── UploaderWidgetCollectionCellTests.swift
│ │ │ └── UploaderWidgetPreviewViewControllerTests.swift
│ │ ├── UploaderWidgetTests/
│ │ │ ├── UploaderWidgetTests.m
│ │ │ └── UploaderWidgetTests.swift
│ │ ├── UploaderWidgetViewControllerTests/
│ │ │ └── UploaderWidgetViewControllerTests.swift
│ │ └── WidgetBaseTest.swift
│ └── WidgetUITests/
│ ├── Info.plist
│ └── WidgetUITests.swift
├── LICENSE
├── Package.swift
├── Package@swift-5.3.swift
├── README.md
└── tools/
└── get_test_cloud.sh
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
## Bug report for Cloudinary iOS SDK
Before proceeding, please update to latest version and test if the issue persists
## Describe the bug in a sentence or two.
…
## Issue Type (Can be multiple)
[ ] Build - Can’t install or import the SDK
[ ] Performance - Performance issues
[ ] Behaviour - Functions aren’t working as expected (Such as generate URL)
[ ] Documentation - Inconsistency between the docs and behaviour
[ ] Other (Specify)
## Steps to reproduce
… if applicable
## Error screenshots or Stack Trace (if applicable)
…
## Build/Dependency management
[ ] Cocoa-Pods
[ ] Carthage
[ ] Manual import
[ ] Other (Specify)
## Is the issue reproducible only on a specific device?
[ ] No
[ ] Yes (specify model + iOS version)
## Versions and Libraries (fill in the version numbers)
iOS Cloudinary SDK version - 0.0.0
OSX (on the dev environment) - 0.0.0
XCode - 0.0.0
Swift - 0.0.0
Target iOS - 0.0.0
Repository
If possible, please provide a link to a reproducible repository that showcases the problem
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
## Feature request for Cloudinary iOS SDK
…(If your feature is for other SDKs, please request them there)
## Explain your use case
… (A high level explanation of why you need this feature)
## Describe the problem you’re trying to solve
… (A more technical view of what you’d like to accomplish, and how this feature will help you achieve it)
## Do you have a proposed solution?
… (yes, no? Please elaborate if needed)
================================================
FILE: .github/pull_request_template.md
================================================
### Brief Summary of Changes
#### What does this PR address?
- [ ] GitHub issue (Add reference - #XX)
- [ ] Refactoring
- [ ] New feature
- [ ] Bug fix
- [ ] Adds more tests
#### Are tests included?
- [ ] Yes
- [ ] No
#### Reviewer, please note:
#### Checklist:
- [ ] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [ ] I ran the full test suite before pushing the changes and all the tests pass.
================================================
FILE: .gitignore
================================================
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
*.DS_Store
## Build generated
build/
DerivedData/
## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/
## Other
*.moved-aside
*.xcuserstate
## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM
## Playgrounds
timeline.xctimeline
playground.xcworkspace
# Swift Package Manager
#
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
.build/
# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/
_Pods.xcodeproj
# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
Example/Carthage/Checkouts
Example/Carthage/Build
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
iOSInjectionProject/
Cloudinary/Frameworks/CLDCrypto
# Samples
AppDelegate+config.swift
Example/Pods/
Example/Podfile.lock
Example/Cloudinary.xcworkspace/
================================================
FILE: .gitmodules
================================================
================================================
FILE: .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
================================================
================================================
FILE: .travis.yml
================================================
language: objective-c
os: osx
before_script: >
export CLOUDINARY_URL=$(bash tools/get_test_cloud.sh);
echo cloud_name: "$(echo $CLOUDINARY_URL | cut -d'@' -f2)"
notifications:
email:
recipients:
- sdk_developers@cloudinary.com
jobs:
- osx_image: xcode12
xcode_workspace: Example/Cloudinary.xcworkspace
xcode_scheme: travis_public_scheme
podfile: Example/Podfile
install: pod install --project-directory=Example
env: CLOUDINARY_URL=$CLOUDINARY_URL
xcode_destination: platform=iOS Simulator,OS=13.0,name=iPhone 8
- osx_image: xcode12
xcode_workspace: Example/Cloudinary.xcworkspace
xcode_scheme: travis_public_scheme
podfile: Example/Podfile
install: pod install --project-directory=Example
env: CLOUDINARY_URL=$CLOUDINARY_URL
xcode_destination: platform=iOS Simulator,OS=14.0,name=iPhone 8
- osx_image: xcode13.2
xcode_workspace: Example/Cloudinary.xcworkspace
xcode_scheme: travis_public_scheme
podfile: Example/Podfile
install: pod install --project-directory=Example
env: CLOUDINARY_URL=$CLOUDINARY_URL
xcode_destination: platform=iOS Simulator,OS=15.2,name=iPhone 8
================================================
FILE: CHANGELOG.md
================================================
5.2.5 / 2026-01-08
==================
* Remove _Pods.xcodeproj
5.2.4 / 2025-11-30
==================
Other changes
-------------
* Fix duration attribute for video analytics
5.2.3 / 2025-08-12
==================
Other Changes
-------------
* Add video analytics duration event
* Add flush events support to video analytics
5.2.2 / 2025-07-30
==================
Other Changes
--------------
* Fix video analytics duration event
5.2.1 / 2025-06-25
==================
Other Changes
--------------
* Add video analytics as public API
5.2.0 / 2025-06-18
==================
New functionality
-----------------
* Add support for `auto_chaptering` and `auto_transcription` in Upload API
* Add extra headers support
Other Changes
--------------
* Fix API parameters signature
5.1.0 / 2024-09-17
==================
New functionality
-----------------
* Add video transcode
* Add auto_chaptering and auto_transcription to Upload API
5.0.0 / 2024-04-14
==================
Breaking Changes
----------------
* Remove CLDURLCache in favor of native URLCache
* Remove ImageCache
* Old cache saved to the disk will be purged
New functionality
-----------------
* Add URLCache support for `CLDUIImageVIew`
4.7.0 / 2024-03-25
==================
Other Changes
-------------
* Fix privacy manifest
* Fix `CLDVideoPlayer`
4.6.0 / 2024-03-12
==================
New functionality
-----------------
* Add support for `media_metadata`
Other Changes
-------------
* Fix video analytics
4.5.0 / 2024-02-18
==================
Other Changes
-------------
* Add privacy manifest
4.4.0 / 2024-01-15
==================
Other Changes
-------------
* Update analytics token
4.3.0 / 2023-12-25
==================
Other Changes
-------------
* Add video player analytics
4.2.0 / 2023-10-12
==================
Other Changes
-------------
* Update analytics token
4.1.1 / 2023-09-18
==================
Other Changes
-------------
* Fix analytics import
4.1.0 / 2023-08-06
==================
New functionality
-----------------
* Add video player widget
4.0.1 / 2023-06-12
==================
Other changes
-------------
* Fix analytics token prefix
4.0.0 / 2023-05-22
==================
New functionality
-----------------
* Turn on `CLDURLCache` on by default
Other changes
-------------
* Make upload request respect timeout
3.4.0 / 2023-03-12
==================
New functionality
-----------------
* Add URLCache support for images
* Add tests for explicit and rename
Other changes
-------------
* Fix warning using `URLCredentialStorage`
3.3.0 / 2022-06-12
==================
New functionality
-----------------
* Add support for folder decoupling
* Add support for `startOffset` and `endOffset` as expression
* Allow to disable b-frames
* Send tags as an array
* Add support for `originalFilename` upload parameter
Other changes
-------------
* Fix expression normalisation
3.2.1 / 2022-01-11
==================
* Fix `Carthage`
3.2.0 / 2022-01-10
==================
New functionality
-----------------
* Add support for `apiKey` argument in Upload API
* Add support for `preview` effect
Other changes
-------------
* Update travis job to support multiple iOS versions
* Add test for preview effect with duration parameter
* Recover `Cloudinary.xcodeproj` file
* Fix `ImageView` size in preview widget `CollectionView`
* Improve network error handling
* Add `PNG` image upload unit tests
3.1.0 / 2021-12-03
==================
New functionality
-----------------
* Add support for `backgroundRemoval` upload parameter
Other changes
-------------
* Fix `Carthage`
3.0.3 / 2021-11-14
==================
* Fix error handling in `fetchImage`
3.0.2 / 2021-11-02
==================
* Improve error handling in `fetchImage`
3.0.1 / 2021-08-31
=============
* Fix deprecation warnings
3.0.0 / 2021-04-26
=============
Breaking changes
-----------------
* Bump minimum iOS target version to 9.0 (#334)
New functionality
-----------------
* Add new url cache system (#331)
* Add operationQueue names (#333)
* Add support for video assets in upload widget (#322)
Other changes
-------------
* Fix image assets for tests in multiple OS versions (#329)
* Add missing imports for SPM (#326)
2.10.1 / 2021-01-20
==================
* Re-add `Cloudinary.xscheme` to fix carthage support (#324)
2.10.0 / 2021-01-10
==================
New functionality
-----------------
* Add Upload Widget (#320)
* Add support for crop preprocess action (#263)
* Add support for rotation preprocess action (#264)
* Add support for enhanced quality analysis scores
* Add support for user defined variables and conditional expressions (#238)
* Support array of values for radius (#235)
* Add `eval` parameter to the upload Params.
* Add support for accessibility analysis (#260)
* Add option to control url signature algorithm (#256)
* Add support for custom pre-functions (#253)
* Add support for Long signature in URLs (#250)
* Add global timeout support(#251)
* Add OCR support un transformations and Upload APIs.
Other changes
-------------
* Add checks to validate responsive transformation.
* Fix space encoding in a generated URL (#274)
* Change local cache-keys encoding to `sha256`
* Fix OCR parameter usage in `UploadRequestParamsTests` (#262)
* Support urls with mime-type suffix in uploader.
* Update SPM definitions file (#234)
2.9.0 / 2020-04-16
==================
* Remove Alamofire dependency (#229)
* Add Swift Package Manager support.
* Add Carthage support.
* Rearrange workspace (#226)
* Add support for `assist_colorblind` effect. (#227)
* Validate URL scheme (#224)
2.9.0-beta.2 / 2020-04-05
=========================
* Remove Alamofire dependency (#229)
2.9.0-beta.1 / 2020-03-23
=========================
* Add Swift Package Manager support.
* Add Carthage support.
* Rearrange workspace (#226)
* Add support for `assist_colorblind` effect. (#227)
* Validate URL scheme (#224)
2.8.0 / 2019-11-11
==================
* Add shared scheme (#220)
* Add support for artistic filters. (#214)
* Fix chunk upload to keep original file name. (#217)
* Fix face coordinates test. (#215)
2.7.0 / 2019-04-07
==================
New functionality
-----------------
* Support swift 5, fix related warnings.
* Bump Alamofire version to 4.8.2
* Add support for fetch layer in transformations. (#197)
* Add support for `quality_analysis` parameter in upload. (#195)
* Add support for font antialiasing and hinting in text overlays. (#193)
* Add support for `streaming_profile` param in `CLDTransformation`. (#194)
* Support excluding the default version from the generated cloudinary-url. (#206)
* Add quality_override parameter to upload/explicit. (#199)
Other changes
-------------
* Replace crypto kit files with `CommonCrypto` import.
* Fix memory cache limit not getting initially set in `CLDImageCache` (#201)
* Fix bas64 string validation in uploader. (#196)
2.6.1 / 2019-01-30
==================
* Fix project setup and tests for swift 4.2 (#191)
2.6.0 / 2019-01-29
==================
New functionality
-----------------
* Add support for google storage (`gs://`) urls in uploader. (#184)
* Add support for `fps` parameter in Transformations. (#182)
* Add format field to responsive breakpoints in upload params (#152)
* Add `removeFromCache` to `CLDCloudinary`
Other changes
-------------
* Bump Alamofire version to 4.8.1
* Add device and os data to user agent. (#180)
* Remove duplicate Alamofire reference
2.5.0 / 2018-11-05
==================
* Migrate to Swift 4.2
* Add support for custom transformation functions
* Fix duplicate upload callbacks when using preprocessing
* Bump Alamofire to version to 4.7.3
* Add String extensions for base64 encoding
* Add `@discardableResult` annotations for uploadLarge methods
2.4.0 / 2018-06-26
==================
New functionality
-----------------
* Add support for `async` upload parameter
* Add support for eager transformation format
* Add support for automatic quality in transformations. (#150)
* Add cache max-memory config (#98)
* Add Keyframe interval transformation parameter (#90)
Other changes
-------------
* Refactor CLDBaseParam for compatibility with iOS 11.4
* Remove wrong project config (library search paths)
* Bump Alamofire version to 4.7.2
* Fix Alamofire submodule definition
* Fix signature parameter handling in Management Api. (#161)
* Fix `faceCenter` and `facesCenter` values in `CLDGravity` (#159)
* Fix calculation of UIImage memory cache cost
2.3.0 / 2018-03-16
==================
* Add access control parameter to upload (#142)
2.2.2 / 2018-03-05
==================
* Add baseline Objective-C test.
* Use `@objcMembers` where applies, for improved Objective-C compatibility.
2.2.1 / 2018-02-14
==================
* Fix objective-c compatibility issues with `CLDResponsiveParams` properties.
2.2.0 / 2018-02-01
==================
New functionality
-----------------
* Add support for responsive image download
* Add `auto` to `CLDGravity`
Other changes
-------------
* Fix synchronization issue when using `cldSetImage()` on `UIViews` within view collections.
2.1.0 / 2018-01-04
==================
New functionality
-----------------
* Add image preprocessing and validations
* Resizing
* Format and quality settings
* Support for custom preprocessors and validators
Other changes
-------------
* Remove custom operators
* Update Alamofire to version 4.6.0
2.0.4 / 2017-12-20
==================
* Fix user-agent sdk version
* Replace CommonCrypto wrapper with CryptoKit based code
* Remove autotagging test (behaviour change)
* Support Swift 4
2.0.3 / 2017-11-23
==================
New functionality
-----------------
* Add support for chunked upload
Other changes
-------------
* Update Readme to point to HTTPS URLs of cloudinary.com
* Fix manual installation repository url.
2.0.2 / 2017-06-08
==================
New functionality
-----------------
* Support SEO suffix for private images.
Other changes
-------------
* Escape `|` and `=` characters in context values.
* Double encode commas and slashes instead of using special UTF-8 values
* Generate CLDCrypto framework in every build. Fixes #80.
* Removing extra CLDCrypto path from podspec
* Update `README.md` Alamo version to 4.1.0
* Add 3 images in PhotoViewController to demonstrate transformations.
* Add progress indicator
* Updated the framework and project deployment target to 8.0, updated podspec deployment target to 8.0
* Fixed signed upload using CLDSignature - Added unit test to test a signed upload using CLDSignature
2.0.1 / 2017-01-24
==================
* Fix pod install issue. Fixes #57.
* Fix shellScript
* Fix URLs in tests
* Increment Alamofire to ~>4.1. Fixes #59.
* Update configuration
2.0.1 / 2017-01-22
==================
* Fix Framework path in podspec/xcconfig
2.0.0 / 2017-01-18
==================
* New Swift 3.0 code
================================================
FILE: Cloudinary/Assets/.gitkeep
================================================
================================================
FILE: Cloudinary/Classes/Core/BaseNetwork/CLDNConvertible.swift
================================================
//
// CLDNConvertible.swift
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/// Types adopting the `CLDNURLConvertible` protocol can be used to construct URLs, which are then used to construct
/// URL requests.
internal protocol CLDNURLConvertible {
/// Returns a URL that conforms to RFC 2396 or throws an `Error`.
///
/// - throws: An `Error` if the type cannot be converted to a `URL`.
///
/// - returns: A URL or throws an `Error`.
func CLDN_AsURL() throws -> URL
}
extension String: CLDNURLConvertible {
/// Returns a URL if `self` represents a valid URL string that conforms to RFC 2396 or throws an `CLDNError`.
///
/// - throws: An `CLDNError.invalidURL` if `self` is not a valid URL string.
///
/// - returns: A URL or throws an `CLDNError`.
internal func CLDN_AsURL() throws -> URL {
guard let url = URL(string: self) else { throw CLDNError.invalidURL(url: self) }
return url
}
}
extension URL: CLDNURLConvertible {
/// Returns self.
internal func CLDN_AsURL() throws -> URL { return self }
}
extension URLComponents: CLDNURLConvertible {
/// Returns a URL if `url` is not nil, otherwise throws an `Error`.
///
/// - throws: An `CLDNError.invalidURL` if `url` is `nil`.
///
/// - returns: A URL or throws an `CLDNError`.
internal func CLDN_AsURL() throws -> URL {
guard let url = url else { throw CLDNError.invalidURL(url: self) }
return url
}
}
// MARK: -
/// Types adopting the `CLDNURLRequestConvertible` protocol can be used to construct URL requests.
internal protocol CLDNURLRequestConvertible {
/// Returns a URL request or throws if an `Error` was encountered.
///
/// - throws: An `Error` if the underlying `URLRequest` is `nil`.
///
/// - returns: A URL request.
func CLDN_AsURLRequest() throws -> URLRequest
}
extension CLDNURLRequestConvertible {
/// The URL request.
internal var urlRequest: URLRequest? { return try? CLDN_AsURLRequest() }
}
extension URLRequest: CLDNURLRequestConvertible {
/// Returns a URL request or throws if an `Error` was encountered.
internal func CLDN_AsURLRequest() throws -> URLRequest { return self }
}
// MARK: -
extension URLRequest {
/// Creates an instance with the specified `method`, `urlString` and `headers`.
///
/// - parameter url: The URL.
/// - parameter method: The HTTP method.
/// - parameter headers: The HTTP headers. `nil` by default.
///
/// - returns: The new `URLRequest` instance.
internal init(url: CLDNURLConvertible, method: CLDNHTTPMethod, headers: CLDNHTTPHeaders? = nil) throws {
let url = try url.CLDN_AsURL()
self.init(url: url)
httpMethod = method.rawValue
if let headers = headers {
for (headerField, headerValue) in headers {
setValue(headerValue, forHTTPHeaderField: headerField)
}
}
}
func adapt(using adapter: CLDNRequestAdapter?) throws -> URLRequest {
guard let adapter = adapter else { return self }
return try adapter.CLDN_Adapt(self)
}
}
================================================
FILE: Cloudinary/Classes/Core/BaseNetwork/CLDNError.swift
================================================
//
// CLDNError.swift
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/// `CLDNError` is the error type returned by Cloudinary. It encompasses a few different types of errors, each with
/// their own associated reasons.
///
/// - invalidURL: Returned when a `URLConvertible` type fails to create a valid `URL`.
/// - parameterEncodingFailed: Returned when a parameter encoding object throws an error during the encoding process.
/// - multipartEncodingFailed: Returned when some step in the multipart encoding process fails.
/// - responseValidationFailed: Returned when a `validate()` call fails.
/// - responseSerializationFailed: Returned when a response serializer encounters an error in the serialization process.
internal enum CLDNError: Error {
/// The underlying reason the parameter encoding error occurred.
///
/// - missingURL: The URL request did not have a URL to encode.
/// - jsonEncodingFailed: JSON serialization failed with an underlying system error during the
/// encoding process.
/// - propertyListEncodingFailed: Property list serialization failed with an underlying system error during
/// encoding process.
internal enum ParameterEncodingFailureReason {
case missingURL
case jsonEncodingFailed(error: Error)
case propertyListEncodingFailed(error: Error)
}
/// The underlying reason the multipart encoding error occurred.
///
/// - bodyPartURLInvalid: The `fileURL` provided for reading an encodable body part isn't a
/// file URL.
/// - bodyPartFilenameInvalid: The filename of the `fileURL` provided has either an empty
/// `lastPathComponent` or `pathExtension.
/// - bodyPartFileNotReachable: The file at the `fileURL` provided was not reachable.
/// - bodyPartFileNotReachableWithError: Attempting to check the reachability of the `fileURL` provided threw
/// an error.
/// - bodyPartFileIsDirectory: The file at the `fileURL` provided is actually a directory.
/// - bodyPartFileSizeNotAvailable: The size of the file at the `fileURL` provided was not returned by
/// the system.
/// - bodyPartFileSizeQueryFailedWithError: The attempt to find the size of the file at the `fileURL` provided
/// threw an error.
/// - bodyPartInputStreamCreationFailed: An `InputStream` could not be created for the provided `fileURL`.
/// - outputStreamCreationFailed: An `OutputStream` could not be created when attempting to write the
/// encoded data to disk.
/// - outputStreamFileAlreadyExists: The encoded body data could not be writtent disk because a file
/// already exists at the provided `fileURL`.
/// - outputStreamURLInvalid: The `fileURL` provided for writing the encoded body data to disk is
/// not a file URL.
/// - outputStreamWriteFailed: The attempt to write the encoded body data to disk failed with an
/// underlying error.
/// - inputStreamReadFailed: The attempt to read an encoded body part `InputStream` failed with
/// underlying system error.
internal enum MultipartEncodingFailureReason {
case bodyPartURLInvalid(url: URL)
case bodyPartFilenameInvalid(in: URL)
case bodyPartFileNotReachable(at: URL)
case bodyPartFileNotReachableWithError(atURL: URL, error: Error)
case bodyPartFileIsDirectory(at: URL)
case bodyPartFileSizeNotAvailable(at: URL)
case bodyPartFileSizeQueryFailedWithError(forURL: URL, error: Error)
case bodyPartInputStreamCreationFailed(for: URL)
case outputStreamCreationFailed(for: URL)
case outputStreamFileAlreadyExists(at: URL)
case outputStreamURLInvalid(url: URL)
case outputStreamWriteFailed(error: Error)
case inputStreamReadFailed(error: Error)
}
/// The underlying reason the response validation error occurred.
///
/// - dataFileNil: The data file containing the server response did not exist.
/// - dataFileReadFailed: The data file containing the server response could not be read.
/// - missingContentType: The response did not contain a `Content-Type` and the `acceptableContentTypes`
/// provided did not contain wildcard type.
/// - unacceptableContentType: The response `Content-Type` did not match any type in the provided
/// `acceptableContentTypes`.
/// - unacceptableStatusCode: The response status code was not acceptable.
internal enum ResponseValidationFailureReason {
case dataFileNil
case dataFileReadFailed(at: URL)
case missingContentType(acceptableContentTypes: [String])
case unacceptableContentType(acceptableContentTypes: [String], responseContentType: String)
case unacceptableStatusCode(code: Int)
}
/// The underlying reason the response serialization error occurred.
///
/// - inputDataNil: The server response contained no data.
/// - inputDataNilOrZeroLength: The server response contained no data or the data was zero length.
/// - inputFileNil: The file containing the server response did not exist.
/// - inputFileReadFailed: The file containing the server response could not be read.
/// - stringSerializationFailed: String serialization failed using the provided `String.Encoding`.
/// - jsonSerializationFailed: JSON serialization failed with an underlying system error.
/// - propertyListSerializationFailed: Property list serialization failed with an underlying system error.
internal enum ResponseSerializationFailureReason {
case inputDataNil
case inputDataNilOrZeroLength
case inputFileNil
case inputFileReadFailed(at: URL)
case stringSerializationFailed(encoding: String.Encoding)
case jsonSerializationFailed(error: Error)
case propertyListSerializationFailed(error: Error)
}
case invalidURL(url: CLDNURLConvertible)
case parameterEncodingFailed(reason: ParameterEncodingFailureReason)
case multipartEncodingFailed(reason: MultipartEncodingFailureReason)
case responseValidationFailed(reason: ResponseValidationFailureReason)
case responseSerializationFailed(reason: ResponseSerializationFailureReason)
}
// MARK: - Adapt Error
struct AdaptError: Error {
let error: Error
}
extension Error {
var underlyingAdaptError: Error? { return (self as? AdaptError)?.error }
}
// MARK: - Error Booleans
extension CLDNError {
/// Returns whether the CLDNError is an invalid URL error.
internal var isInvalidURLError: Bool {
if case .invalidURL = self { return true }
return false
}
/// Returns whether the CLDNError is a parameter encoding error. When `true`, the `underlyingError` property will
/// contain the associated value.
internal var isParameterEncodingError: Bool {
if case .parameterEncodingFailed = self { return true }
return false
}
/// Returns whether the CLDNError is a multipart encoding error. When `true`, the `url` and `underlyingError` properties
/// will contain the associated values.
internal var isMultipartEncodingError: Bool {
if case .multipartEncodingFailed = self { return true }
return false
}
/// Returns whether the `CLDNError` is a response validation error. When `true`, the `acceptableContentTypes`,
/// `responseContentType`, and `responseCode` properties will contain the associated values.
internal var isResponseValidationError: Bool {
if case .responseValidationFailed = self { return true }
return false
}
/// Returns whether the `CLDNError` is a response serialization error. When `true`, the `failedStringEncoding` and
/// `underlyingError` properties will contain the associated values.
internal var isResponseSerializationError: Bool {
if case .responseSerializationFailed = self { return true }
return false
}
}
// MARK: - Convenience Properties
extension CLDNError {
/// The `URLConvertible` associated with the error.
var urlConvertible: CLDNURLConvertible? {
switch self {
case .invalidURL(let url):
return url
default:
return nil
}
}
/// The `URL` associated with the error.
internal var url: URL? {
switch self {
case .multipartEncodingFailed(let reason):
return reason.url
default:
return nil
}
}
/// The `Error` returned by a system framework associated with a `.parameterEncodingFailed`,
/// `.multipartEncodingFailed` or `.responseSerializationFailed` error.
internal var underlyingError: Error? {
switch self {
case .parameterEncodingFailed(let reason):
return reason.underlyingError
case .multipartEncodingFailed(let reason):
return reason.underlyingError
case .responseSerializationFailed(let reason):
return reason.underlyingError
default:
return nil
}
}
/// The acceptable `Content-Type`s of a `.responseValidationFailed` error.
internal var acceptableContentTypes: [String]? {
switch self {
case .responseValidationFailed(let reason):
return reason.acceptableContentTypes
default:
return nil
}
}
/// The response `Content-Type` of a `.responseValidationFailed` error.
internal var responseContentType: String? {
switch self {
case .responseValidationFailed(let reason):
return reason.responseContentType
default:
return nil
}
}
/// The response code of a `.responseValidationFailed` error.
internal var responseCode: Int? {
switch self {
case .responseValidationFailed(let reason):
return reason.responseCode
default:
return nil
}
}
/// The `String.Encoding` associated with a failed `.stringResponse()` call.
internal var failedStringEncoding: String.Encoding? {
switch self {
case .responseSerializationFailed(let reason):
return reason.failedStringEncoding
default:
return nil
}
}
}
extension CLDNError.ParameterEncodingFailureReason {
var underlyingError: Error? {
switch self {
case .jsonEncodingFailed(let error), .propertyListEncodingFailed(let error):
return error
default:
return nil
}
}
}
extension CLDNError.MultipartEncodingFailureReason {
var url: URL? {
switch self {
case .bodyPartURLInvalid(let url), .bodyPartFilenameInvalid(let url), .bodyPartFileNotReachable(let url),
.bodyPartFileIsDirectory(let url), .bodyPartFileSizeNotAvailable(let url),
.bodyPartInputStreamCreationFailed(let url), .outputStreamCreationFailed(let url),
.outputStreamFileAlreadyExists(let url), .outputStreamURLInvalid(let url),
.bodyPartFileNotReachableWithError(let url, _), .bodyPartFileSizeQueryFailedWithError(let url, _):
return url
default:
return nil
}
}
var underlyingError: Error? {
switch self {
case .bodyPartFileNotReachableWithError(_, let error), .bodyPartFileSizeQueryFailedWithError(_, let error),
.outputStreamWriteFailed(let error), .inputStreamReadFailed(let error):
return error
default:
return nil
}
}
}
extension CLDNError.ResponseValidationFailureReason {
var acceptableContentTypes: [String]? {
switch self {
case .missingContentType(let types), .unacceptableContentType(let types, _):
return types
default:
return nil
}
}
var responseContentType: String? {
switch self {
case .unacceptableContentType(_, let responseType):
return responseType
default:
return nil
}
}
var responseCode: Int? {
switch self {
case .unacceptableStatusCode(let code):
return code
default:
return nil
}
}
}
extension CLDNError.ResponseSerializationFailureReason {
var failedStringEncoding: String.Encoding? {
switch self {
case .stringSerializationFailed(let encoding):
return encoding
default:
return nil
}
}
var underlyingError: Error? {
switch self {
case .jsonSerializationFailed(let error), .propertyListSerializationFailed(let error):
return error
default:
return nil
}
}
}
// MARK: - Error Descriptions
extension CLDNError: LocalizedError {
internal var errorDescription: String? {
switch self {
case .invalidURL(let url):
return "URL is not valid: \(url)"
case .parameterEncodingFailed(let reason):
return reason.localizedDescription
case .multipartEncodingFailed(let reason):
return reason.localizedDescription
case .responseValidationFailed(let reason):
return reason.localizedDescription
case .responseSerializationFailed(let reason):
return reason.localizedDescription
}
}
}
extension CLDNError.ParameterEncodingFailureReason {
var localizedDescription: String {
switch self {
case .missingURL:
return "URL request to encode was missing a URL"
case .jsonEncodingFailed(let error):
return "JSON could not be encoded because of error:\n\(error.localizedDescription)"
case .propertyListEncodingFailed(let error):
return "PropertyList could not be encoded because of error:\n\(error.localizedDescription)"
}
}
}
extension CLDNError.MultipartEncodingFailureReason {
var localizedDescription: String {
switch self {
case .bodyPartURLInvalid(let url):
return "The URL provided is not a file URL: \(url)"
case .bodyPartFilenameInvalid(let url):
return "The URL provided does not have a valid filename: \(url)"
case .bodyPartFileNotReachable(let url):
return "The URL provided is not reachable: \(url)"
case .bodyPartFileNotReachableWithError(let url, let error):
return (
"The system returned an error while checking the provided URL for " +
"reachability.\nURL: \(url)\nError: \(error)"
)
case .bodyPartFileIsDirectory(let url):
return "The URL provided is a directory: \(url)"
case .bodyPartFileSizeNotAvailable(let url):
return "Could not fetch the file size from the provided URL: \(url)"
case .bodyPartFileSizeQueryFailedWithError(let url, let error):
return (
"The system returned an error while attempting to fetch the file size from the " +
"provided URL.\nURL: \(url)\nError: \(error)"
)
case .bodyPartInputStreamCreationFailed(let url):
return "Failed to create an InputStream for the provided URL: \(url)"
case .outputStreamCreationFailed(let url):
return "Failed to create an OutputStream for URL: \(url)"
case .outputStreamFileAlreadyExists(let url):
return "A file already exists at the provided URL: \(url)"
case .outputStreamURLInvalid(let url):
return "The provided OutputStream URL is invalid: \(url)"
case .outputStreamWriteFailed(let error):
return "OutputStream write failed with error: \(error)"
case .inputStreamReadFailed(let error):
return "InputStream read failed with error: \(error)"
}
}
}
extension CLDNError.ResponseSerializationFailureReason {
var localizedDescription: String {
switch self {
case .inputDataNil:
return "Response could not be serialized, input data was nil."
case .inputDataNilOrZeroLength:
return "Response could not be serialized, input data was nil or zero length."
case .inputFileNil:
return "Response could not be serialized, input file was nil."
case .inputFileReadFailed(let url):
return "Response could not be serialized, input file could not be read: \(url)."
case .stringSerializationFailed(let encoding):
return "String could not be serialized with encoding: \(encoding)."
case .jsonSerializationFailed(let error):
return "JSON could not be serialized because of error:\n\(error.localizedDescription)"
case .propertyListSerializationFailed(let error):
return "PropertyList could not be serialized because of error:\n\(error.localizedDescription)"
}
}
}
extension CLDNError.ResponseValidationFailureReason {
var localizedDescription: String {
switch self {
case .dataFileNil:
return "Response could not be validated, data file was nil."
case .dataFileReadFailed(let url):
return "Response could not be validated, data file could not be read: \(url)."
case .missingContentType(let types):
return (
"Response Content-Type was missing and acceptable content types " +
"(\(types.joined(separator: ","))) do not match \"*/*\"."
)
case .unacceptableContentType(let acceptableTypes, let responseType):
return (
"Response Content-Type \"\(responseType)\" does not match any acceptable types: " +
"\(acceptableTypes.joined(separator: ","))."
)
case .unacceptableStatusCode(let code):
return "Response status code was unacceptable: \(code)."
}
}
}
================================================
FILE: Cloudinary/Classes/Core/BaseNetwork/CLDNMultipartFormData.swift
================================================
//
// CLDNMultipartFormData.swift
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
#if os(iOS) || os(watchOS) || os(tvOS)
import MobileCoreServices
#elseif os(macOS)
import CoreServices
#endif
/// Constructs `multipart/form-data` for uploads within an HTTP or HTTPS body. There are currently two ways to encode
/// multipart form data. The first way is to encode the data directly in memory. This is very efficient, but can lead
/// to memory issues if the dataset is too large. The second way is designed for larger datasets and will write all the
/// data to a single file on disk with all the proper boundary segmentation. The second approach MUST be used for
/// larger datasets such as video content, otherwise your app may run out of memory when trying to encode the dataset.
///
/// For more information on `multipart/form-data` in general, please refer to the RFC-2388 and RFC-2045 specs as well
/// and the w3 form documentation.
///
/// - https://www.ietf.org/rfc/rfc2388.txt
/// - https://www.ietf.org/rfc/rfc2045.txt
/// - https://www.w3.org/TR/html401/interact/forms.html#h-17.13
internal class CLDNMultipartFormData {
// MARK: - Helper Types
struct EncodingCharacters {
static let crlf = "\r\n"
}
struct BoundaryGenerator {
enum BoundaryType {
case initial, encapsulated, final
}
static func randomBoundary() -> String {
return String(format: "Cloudinary.boundary.%08x%08x", arc4random(), arc4random())
}
static func boundaryData(forBoundaryType boundaryType: BoundaryType, boundary: String) -> Data {
let boundaryText: String
switch boundaryType {
case .initial:
boundaryText = "--\(boundary)\(EncodingCharacters.crlf)"
case .encapsulated:
boundaryText = "\(EncodingCharacters.crlf)--\(boundary)\(EncodingCharacters.crlf)"
case .final:
boundaryText = "\(EncodingCharacters.crlf)--\(boundary)--\(EncodingCharacters.crlf)"
}
return boundaryText.data(using: String.Encoding.utf8, allowLossyConversion: false)!
}
}
class BodyPart {
let headers: CLDNHTTPHeaders
let bodyStream: InputStream
let bodyContentLength: UInt64
var hasInitialBoundary = false
var hasFinalBoundary = false
init(headers: CLDNHTTPHeaders, bodyStream: InputStream, bodyContentLength: UInt64) {
self.headers = headers
self.bodyStream = bodyStream
self.bodyContentLength = bodyContentLength
}
}
// MARK: - Properties
/// The `Content-Type` header value containing the boundary used to generate the `multipart/form-data`.
internal lazy var contentType: String = "multipart/form-data; boundary=\(self.boundary)"
/// The content length of all body parts used to generate the `multipart/form-data` not including the boundaries.
internal var contentLength: UInt64 { return bodyParts.reduce(0) { $0 + $1.bodyContentLength } }
/// The boundary used to separate the body parts in the encoded form data.
internal var boundary: String
private var bodyParts: [BodyPart]
private var bodyPartError: CLDNError?
private let streamBufferSize: Int
// MARK: - Lifecycle
/// Creates a multipart form data object.
///
/// - returns: The multipart form data object.
internal init() {
self.boundary = BoundaryGenerator.randomBoundary()
self.bodyParts = []
///
/// The optimal read/write buffer size in bytes for input and output streams is 1024 (1KB). For more
/// information, please refer to the following article:
/// - https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Streams/Articles/ReadingInputStreams.html
///
self.streamBufferSize = 1024
}
// MARK: - Body Parts
/// Creates a body part from the data and appends it to the multipart form data object.
///
/// The body part data will be encoded using the following format:
///
/// - `Content-Disposition: form-data; name=#{name}` (HTTP Header)
/// - Encoded data
/// - Multipart form boundary
///
/// - parameter data: The data to encode into the multipart form data.
/// - parameter name: The name to associate with the data in the `Content-Disposition` HTTP header.
internal func append(_ data: Data, withName name: String) {
let headers = contentHeaders(withName: name)
let stream = InputStream(data: data)
let length = UInt64(data.count)
append(stream, withLength: length, headers: headers)
}
/// Creates a body part from the data and appends it to the multipart form data object.
///
/// The body part data will be encoded using the following format:
///
/// - `Content-Disposition: form-data; name=#{name}` (HTTP Header)
/// - `Content-Type: #{generated mimeType}` (HTTP Header)
/// - Encoded data
/// - Multipart form boundary
///
/// - parameter data: The data to encode into the multipart form data.
/// - parameter name: The name to associate with the data in the `Content-Disposition` HTTP header.
/// - parameter mimeType: The MIME type to associate with the data content type in the `Content-Type` HTTP header.
internal func append(_ data: Data, withName name: String, mimeType: String) {
let headers = contentHeaders(withName: name, mimeType: mimeType)
let stream = InputStream(data: data)
let length = UInt64(data.count)
append(stream, withLength: length, headers: headers)
}
/// Creates a body part from the data and appends it to the multipart form data object.
///
/// The body part data will be encoded using the following format:
///
/// - `Content-Disposition: form-data; name=#{name}; filename=#{filename}` (HTTP Header)
/// - `Content-Type: #{mimeType}` (HTTP Header)
/// - Encoded file data
/// - Multipart form boundary
///
/// - parameter data: The data to encode into the multipart form data.
/// - parameter name: The name to associate with the data in the `Content-Disposition` HTTP header.
/// - parameter fileName: The filename to associate with the data in the `Content-Disposition` HTTP header.
/// - parameter mimeType: The MIME type to associate with the data in the `Content-Type` HTTP header.
internal func append(_ data: Data, withName name: String, fileName: String, mimeType: String) {
let headers = contentHeaders(withName: name, fileName: fileName, mimeType: mimeType)
let stream = InputStream(data: data)
let length = UInt64(data.count)
append(stream, withLength: length, headers: headers)
}
/// Creates a body part from the file and appends it to the multipart form data object.
///
/// The body part data will be encoded using the following format:
///
/// - `Content-Disposition: form-data; name=#{name}; filename=#{generated filename}` (HTTP Header)
/// - `Content-Type: #{generated mimeType}` (HTTP Header)
/// - Encoded file data
/// - Multipart form boundary
///
/// The filename in the `Content-Disposition` HTTP header is generated from the last path component of the
/// `fileURL`. The `Content-Type` HTTP header MIME type is generated by mapping the `fileURL` extension to the
/// system associated MIME type.
///
/// - parameter fileURL: The URL of the file whose content will be encoded into the multipart form data.
/// - parameter name: The name to associate with the file content in the `Content-Disposition` HTTP header.
internal func append(_ fileURL: URL, withName name: String) {
let fileName = fileURL.lastPathComponent
let pathExtension = fileURL.pathExtension
if !fileName.isEmpty && !pathExtension.isEmpty {
let mime = mimeType(forPathExtension: pathExtension)
append(fileURL, withName: name, fileName: fileName, mimeType: mime)
} else {
setBodyPartError(withReason: .bodyPartFilenameInvalid(in: fileURL))
}
}
/// Creates a body part from the file and appends it to the multipart form data object.
///
/// The body part data will be encoded using the following format:
///
/// - Content-Disposition: form-data; name=#{name}; filename=#{filename} (HTTP Header)
/// - Content-Type: #{mimeType} (HTTP Header)
/// - Encoded file data
/// - Multipart form boundary
///
/// - parameter fileURL: The URL of the file whose content will be encoded into the multipart form data.
/// - parameter name: The name to associate with the file content in the `Content-Disposition` HTTP header.
/// - parameter fileName: The filename to associate with the file content in the `Content-Disposition` HTTP header.
/// - parameter mimeType: The MIME type to associate with the file content in the `Content-Type` HTTP header.
internal func append(_ fileURL: URL, withName name: String, fileName: String, mimeType: String) {
let headers = contentHeaders(withName: name, fileName: fileName, mimeType: mimeType)
//============================================================
// Check 1 - is file URL?
//============================================================
guard fileURL.isFileURL else {
setBodyPartError(withReason: .bodyPartURLInvalid(url: fileURL))
return
}
//============================================================
// Check 2 - is file URL reachable?
//============================================================
do {
let isReachable = try fileURL.checkPromisedItemIsReachable()
guard isReachable else {
setBodyPartError(withReason: .bodyPartFileNotReachable(at: fileURL))
return
}
} catch {
setBodyPartError(withReason: .bodyPartFileNotReachableWithError(atURL: fileURL, error: error))
return
}
//============================================================
// Check 3 - is file URL a directory?
//============================================================
var isDirectory: ObjCBool = false
let path = fileURL.path
guard FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory) && !isDirectory.boolValue else {
setBodyPartError(withReason: .bodyPartFileIsDirectory(at: fileURL))
return
}
//============================================================
// Check 4 - can the file size be extracted?
//============================================================
let bodyContentLength: UInt64
do {
guard let fileSize = try FileManager.default.attributesOfItem(atPath: path)[.size] as? NSNumber else {
setBodyPartError(withReason: .bodyPartFileSizeNotAvailable(at: fileURL))
return
}
bodyContentLength = fileSize.uint64Value
}
catch {
setBodyPartError(withReason: .bodyPartFileSizeQueryFailedWithError(forURL: fileURL, error: error))
return
}
//============================================================
// Check 5 - can a stream be created from file URL?
//============================================================
guard let stream = InputStream(url: fileURL) else {
setBodyPartError(withReason: .bodyPartInputStreamCreationFailed(for: fileURL))
return
}
append(stream, withLength: bodyContentLength, headers: headers)
}
/// Creates a body part from the stream and appends it to the multipart form data object.
///
/// The body part data will be encoded using the following format:
///
/// - `Content-Disposition: form-data; name=#{name}; filename=#{filename}` (HTTP Header)
/// - `Content-Type: #{mimeType}` (HTTP Header)
/// - Encoded stream data
/// - Multipart form boundary
///
/// - parameter stream: The input stream to encode in the multipart form data.
/// - parameter length: The content length of the stream.
/// - parameter name: The name to associate with the stream content in the `Content-Disposition` HTTP header.
/// - parameter fileName: The filename to associate with the stream content in the `Content-Disposition` HTTP header.
/// - parameter mimeType: The MIME type to associate with the stream content in the `Content-Type` HTTP header.
internal func append(
_ stream: InputStream,
withLength length: UInt64,
name: String,
fileName: String,
mimeType: String)
{
let headers = contentHeaders(withName: name, fileName: fileName, mimeType: mimeType)
append(stream, withLength: length, headers: headers)
}
/// Creates a body part with the headers, stream and length and appends it to the multipart form data object.
///
/// The body part data will be encoded using the following format:
///
/// - HTTP headers
/// - Encoded stream data
/// - Multipart form boundary
///
/// - parameter stream: The input stream to encode in the multipart form data.
/// - parameter length: The content length of the stream.
/// - parameter headers: The HTTP headers for the body part.
internal func append(_ stream: InputStream, withLength length: UInt64, headers: CLDNHTTPHeaders) {
let bodyPart = BodyPart(headers: headers, bodyStream: stream, bodyContentLength: length)
bodyParts.append(bodyPart)
}
// MARK: - Data Encoding
/// Encodes all the appended body parts into a single `Data` value.
///
/// It is important to note that this method will load all the appended body parts into memory all at the same
/// time. This method should only be used when the encoded data will have a small memory footprint. For large data
/// cases, please use the `writeEncodedDataToDisk(fileURL:completionHandler:)` method.
///
/// - throws: An `CLDNError` if encoding encounters an error.
///
/// - returns: The encoded `Data` if encoding is successful.
internal func encode() throws -> Data {
if let bodyPartError = bodyPartError {
throw bodyPartError
}
var encoded = Data()
bodyParts.first?.hasInitialBoundary = true
bodyParts.last?.hasFinalBoundary = true
for bodyPart in bodyParts {
let encodedData = try encode(bodyPart)
encoded.append(encodedData)
}
return encoded
}
/// Writes the appended body parts into the given file URL.
///
/// This process is facilitated by reading and writing with input and output streams, respectively. Thus,
/// this approach is very memory efficient and should be used for large body part data.
///
/// - parameter fileURL: The file URL to write the multipart form data into.
///
/// - throws: An `CLDNError` if encoding encounters an error.
internal func writeEncodedData(to fileURL: URL) throws {
if let bodyPartError = bodyPartError {
throw bodyPartError
}
if FileManager.default.fileExists(atPath: fileURL.path) {
throw CLDNError.multipartEncodingFailed(reason: .outputStreamFileAlreadyExists(at: fileURL))
} else if !fileURL.isFileURL {
throw CLDNError.multipartEncodingFailed(reason: .outputStreamURLInvalid(url: fileURL))
}
guard let outputStream = OutputStream(url: fileURL, append: false) else {
throw CLDNError.multipartEncodingFailed(reason: .outputStreamCreationFailed(for: fileURL))
}
outputStream.open()
defer { outputStream.close() }
self.bodyParts.first?.hasInitialBoundary = true
self.bodyParts.last?.hasFinalBoundary = true
for bodyPart in self.bodyParts {
try write(bodyPart, to: outputStream)
}
}
// MARK: - Private - Body Part Encoding
private func encode(_ bodyPart: BodyPart) throws -> Data {
var encoded = Data()
let initialData = bodyPart.hasInitialBoundary ? initialBoundaryData() : encapsulatedBoundaryData()
encoded.append(initialData)
let headerData = encodeHeaders(for: bodyPart)
encoded.append(headerData)
let bodyStreamData = try encodeBodyStream(for: bodyPart)
encoded.append(bodyStreamData)
if bodyPart.hasFinalBoundary {
encoded.append(finalBoundaryData())
}
return encoded
}
private func encodeHeaders(for bodyPart: BodyPart) -> Data {
var headerText = ""
for (key, value) in bodyPart.headers {
headerText += "\(key): \(value)\(EncodingCharacters.crlf)"
}
headerText += EncodingCharacters.crlf
return headerText.data(using: String.Encoding.utf8, allowLossyConversion: false)!
}
private func encodeBodyStream(for bodyPart: BodyPart) throws -> Data {
let inputStream = bodyPart.bodyStream
inputStream.open()
defer { inputStream.close() }
var encoded = Data()
while inputStream.hasBytesAvailable {
var buffer = [UInt8](repeating: 0, count: streamBufferSize)
let bytesRead = inputStream.read(&buffer, maxLength: streamBufferSize)
if let error = inputStream.streamError {
throw CLDNError.multipartEncodingFailed(reason: .inputStreamReadFailed(error: error))
}
if bytesRead > 0 {
encoded.append(buffer, count: bytesRead)
} else {
break
}
}
return encoded
}
// MARK: - Private - Writing Body Part to Output Stream
private func write(_ bodyPart: BodyPart, to outputStream: OutputStream) throws {
try writeInitialBoundaryData(for: bodyPart, to: outputStream)
try writeHeaderData(for: bodyPart, to: outputStream)
try writeBodyStream(for: bodyPart, to: outputStream)
try writeFinalBoundaryData(for: bodyPart, to: outputStream)
}
private func writeInitialBoundaryData(for bodyPart: BodyPart, to outputStream: OutputStream) throws {
let initialData = bodyPart.hasInitialBoundary ? initialBoundaryData() : encapsulatedBoundaryData()
return try write(initialData, to: outputStream)
}
private func writeHeaderData(for bodyPart: BodyPart, to outputStream: OutputStream) throws {
let headerData = encodeHeaders(for: bodyPart)
return try write(headerData, to: outputStream)
}
private func writeBodyStream(for bodyPart: BodyPart, to outputStream: OutputStream) throws {
let inputStream = bodyPart.bodyStream
inputStream.open()
defer { inputStream.close() }
while inputStream.hasBytesAvailable {
var buffer = [UInt8](repeating: 0, count: streamBufferSize)
let bytesRead = inputStream.read(&buffer, maxLength: streamBufferSize)
if let streamError = inputStream.streamError {
throw CLDNError.multipartEncodingFailed(reason: .inputStreamReadFailed(error: streamError))
}
if bytesRead > 0 {
if buffer.count != bytesRead {
buffer = Array(buffer[0.. 0, outputStream.hasSpaceAvailable {
let bytesWritten = outputStream.write(buffer, maxLength: bytesToWrite)
if let error = outputStream.streamError {
throw CLDNError.multipartEncodingFailed(reason: .outputStreamWriteFailed(error: error))
}
bytesToWrite -= bytesWritten
if bytesToWrite > 0 {
buffer = Array(buffer[bytesWritten.. String {
if
let id = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension as CFString, nil)?.takeRetainedValue(),
let contentType = UTTypeCopyPreferredTagWithClass(id, kUTTagClassMIMEType)?.takeRetainedValue()
{
return contentType as String
}
return "application/octet-stream"
}
// MARK: - Private - Content Headers
private func contentHeaders(withName name: String, fileName: String? = nil, mimeType: String? = nil) -> [String: String] {
var disposition = "form-data; name=\"\(name)\""
if let fileName = fileName { disposition += "; filename=\"\(fileName)\"" }
var headers = ["Content-Disposition": disposition]
if let mimeType = mimeType { headers["Content-Type"] = mimeType }
return headers
}
// MARK: - Private - Boundary Encoding
private func initialBoundaryData() -> Data {
return BoundaryGenerator.boundaryData(forBoundaryType: .initial, boundary: boundary)
}
private func encapsulatedBoundaryData() -> Data {
return BoundaryGenerator.boundaryData(forBoundaryType: .encapsulated, boundary: boundary)
}
private func finalBoundaryData() -> Data {
return BoundaryGenerator.boundaryData(forBoundaryType: .final, boundary: boundary)
}
// MARK: - Private - Errors
private func setBodyPartError(withReason reason: CLDNError.MultipartEncodingFailureReason) {
guard bodyPartError == nil else { return }
bodyPartError = CLDNError.multipartEncodingFailed(reason: reason)
}
}
================================================
FILE: Cloudinary/Classes/Core/BaseNetwork/CLDNParameterEncoding.swift
================================================
//
// CLDNParameterEncoding.swift
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/// HTTP method definitions.
///
/// See https://tools.ietf.org/html/rfc7231#section-4.3
internal enum CLDNHTTPMethod: String {
case options = "OPTIONS"
case get = "GET"
case head = "HEAD"
case post = "POST"
case put = "PUT"
case patch = "PATCH"
case delete = "DELETE"
case trace = "TRACE"
case connect = "CONNECT"
}
// MARK: -
/// A dictionary of parameters to apply to a `URLRequest`.
internal typealias CLDNParameters = [String: Any]
/// A type used to define how a set of parameters are applied to a `URLRequest`.
internal protocol CLDNParameterEncoding {
/// Creates a URL request by encoding parameters and applying them onto an existing request.
///
/// - parameter urlRequest: The request to have parameters applied.
/// - parameter parameters: The parameters to apply.
///
/// - throws: An `CLDNError.parameterEncodingFailed` error if encoding fails.
///
/// - returns: The encoded request.
func CLDN_Encode(_ urlRequest: CLDNURLRequestConvertible, with parameters: CLDNParameters?) throws -> URLRequest
}
// MARK: -
/// Creates a url-encoded query string to be set as or appended to any existing URL query string or set as the HTTP
/// body of the URL request. Whether the query string is set or appended to any existing URL query string or set as
/// the HTTP body depends on the destination of the encoding.
///
/// The `Content-Type` HTTP header field of an encoded request with HTTP body is set to
/// `application/x-www-form-urlencoded; charset=utf-8`.
///
/// There is no published specification for how to encode collection types. By default the convention of appending
/// `[]` to the key for array values (`foo[]=1&foo[]=2`), and appending the key surrounded by square brackets for
/// nested dictionary values (`foo[bar]=baz`) is used. Optionally, `ArrayEncoding` can be used to omit the
/// square brackets appended to array keys.
///
/// `BoolEncoding` can be used to configure how boolean values are encoded. The default behavior is to encode
/// `true` as 1 and `false` as 0.
internal struct CLDNURLEncoding: CLDNParameterEncoding {
// MARK: Helper Types
/// Defines whether the url-encoded query string is applied to the existing query string or HTTP body of the
/// resulting URL request.
///
/// - methodDependent: Applies encoded query string result to existing query string for `GET`, `HEAD` and `DELETE`
/// requests and sets as the HTTP body for requests with any other HTTP method.
/// - queryString: Sets or appends encoded query string result to existing query string.
/// - httpBody: Sets encoded query string result as the HTTP body of the URL request.
internal enum Destination {
case methodDependent, queryString, httpBody
}
/// Configures how `Array` parameters are encoded.
///
/// - brackets: An empty set of square brackets is appended to the key for every value.
/// This is the default behavior.
/// - noBrackets: No brackets are appended. The key is encoded as is.
internal enum ArrayEncoding {
case brackets, noBrackets
func encode(key: String) -> String {
switch self {
case .brackets:
return "\(key)[]"
case .noBrackets:
return key
}
}
}
/// Configures how `Bool` parameters are encoded.
///
/// - numeric: Encode `true` as `1` and `false` as `0`. This is the default behavior.
/// - literal: Encode `true` and `false` as string literals.
internal enum BoolEncoding {
case numeric, literal
func encode(value: Bool) -> String {
switch self {
case .numeric:
return value ? "1" : "0"
case .literal:
return value ? "true" : "false"
}
}
}
// MARK: Properties
/// Returns a default `URLEncoding` instance.
internal static var `default`: CLDNURLEncoding { return CLDNURLEncoding() }
/// Returns a `URLEncoding` instance with a `.methodDependent` destination.
internal static var methodDependent: CLDNURLEncoding { return CLDNURLEncoding() }
/// Returns a `URLEncoding` instance with a `.queryString` destination.
internal static var queryString: CLDNURLEncoding { return CLDNURLEncoding(destination: .queryString) }
/// Returns a `URLEncoding` instance with an `.httpBody` destination.
internal static var httpBody: CLDNURLEncoding { return CLDNURLEncoding(destination: .httpBody) }
/// The destination defining where the encoded query string is to be applied to the URL request.
internal let destination: Destination
/// The encoding to use for `Array` parameters.
internal let arrayEncoding: ArrayEncoding
/// The encoding to use for `Bool` parameters.
internal let boolEncoding: BoolEncoding
// MARK: Initialization
/// Creates a `CLDNURLEncoding` instance using the specified destination.
///
/// - parameter destination: The destination defining where the encoded query string is to be applied.
/// - parameter arrayEncoding: The encoding to use for `Array` parameters.
/// - parameter boolEncoding: The encoding to use for `Bool` parameters.
///
/// - returns: The new `CLDNURLEncoding` instance.
internal init(destination: Destination = .methodDependent, arrayEncoding: ArrayEncoding = .brackets, boolEncoding: BoolEncoding = .numeric) {
self.destination = destination
self.arrayEncoding = arrayEncoding
self.boolEncoding = boolEncoding
}
// MARK: Encoding
/// Creates a URL request by encoding parameters and applying them onto an existing request.
///
/// - parameter urlRequest: The request to have parameters applied.
/// - parameter parameters: The parameters to apply.
///
/// - throws: An `Error` if the encoding process encounters an error.
///
/// - returns: The encoded request.
internal func CLDN_Encode(_ urlRequest: CLDNURLRequestConvertible, with parameters: CLDNParameters?) throws -> URLRequest {
var urlRequest = try urlRequest.CLDN_AsURLRequest()
guard let parameters = parameters else { return urlRequest }
if let method = CLDNHTTPMethod(rawValue: urlRequest.httpMethod ?? "GET"), encodesParametersInURL(with: method) {
guard let url = urlRequest.url else {
throw CLDNError.parameterEncodingFailed(reason: .missingURL)
}
if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty {
let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters)
urlComponents.percentEncodedQuery = percentEncodedQuery
urlRequest.url = urlComponents.url
}
} else {
if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")
}
urlRequest.httpBody = query(parameters).data(using: .utf8, allowLossyConversion: false)
}
return urlRequest
}
/// Creates percent-escaped, URL encoded query string components from the given key-value pair using recursion.
///
/// - parameter key: The key of the query component.
/// - parameter value: The value of the query component.
///
/// - returns: The percent-escaped, URL encoded query string components.
internal func queryComponents(fromKey key: String, value: Any) -> [(String, String)] {
var components: [(String, String)] = []
if let dictionary = value as? [String: Any] {
for (nestedKey, value) in dictionary {
components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value)
}
} else if let array = value as? [Any] {
for value in array {
components += queryComponents(fromKey: arrayEncoding.encode(key: key), value: value)
}
} else if let value = value as? NSNumber {
if value.isBool {
components.append((escape(key), escape(boolEncoding.encode(value: value.boolValue))))
} else {
components.append((escape(key), escape("\(value)")))
}
} else if let bool = value as? Bool {
components.append((escape(key), escape(boolEncoding.encode(value: bool))))
} else {
components.append((escape(key), escape("\(value)")))
}
return components
}
/// Returns a percent-escaped string following RFC 3986 for a query string key or value.
///
/// RFC 3986 states that the following characters are "reserved" characters.
///
/// - General Delimiters: ":", "#", "[", "]", "@", "?", "/"
/// - Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="
///
/// In RFC 3986 - Section 3.4, it states that the "?" and "/" characters should not be escaped to allow
/// query strings to include a URL. Therefore, all "reserved" characters with the exception of "?" and "/"
/// should be percent-escaped in the query string.
///
/// - parameter string: The string to be percent-escaped.
///
/// - returns: The percent-escaped string.
internal func escape(_ string: String) -> String {
let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4
let subDelimitersToEncode = "!$&'()*+,;="
var allowedCharacterSet = CharacterSet.urlQueryAllowed
allowedCharacterSet.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")
var escaped = ""
//==========================================================================================================
//
// Batching is required for escaping due to an internal bug in iOS 8.1 and 8.2. Encoding more than a few
// hundred Chinese characters causes various malloc error crashes. To avoid this issue until iOS 8 is no
// longer supported, batching MUST be used for encoding. This introduces roughly a 20% overhead. For more
// info, please refer to:
//
// - https://github.com/Alamofire/Alamofire/issues/206
//
//==========================================================================================================
if #available(iOS 8.3, *) {
escaped = string.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? string
} else {
let batchSize = 50
var index = string.startIndex
while index != string.endIndex {
let startIndex = index
let endIndex = string.index(index, offsetBy: batchSize, limitedBy: string.endIndex) ?? string.endIndex
let range = startIndex.. String {
var components: [(String, String)] = []
for key in parameters.keys.sorted(by: <) {
let value = parameters[key]!
components += queryComponents(fromKey: key, value: value)
}
return components.map { "\($0)=\($1)" }.joined(separator: "&")
}
private func encodesParametersInURL(with method: CLDNHTTPMethod) -> Bool {
switch destination {
case .queryString:
return true
case .httpBody:
return false
default:
break
}
switch method {
case .get, .head, .delete:
return true
default:
return false
}
}
}
// MARK: -
/// Uses `JSONSerialization` to create a JSON representation of the parameters object, which is set as the body of the
/// request. The `Content-Type` HTTP header field of an encoded request is set to `application/json`.
internal struct CLDNJSONEncoding: CLDNParameterEncoding {
// MARK: Properties
/// Returns a `JSONEncoding` instance with default writing options.
internal static var `default`: CLDNJSONEncoding { return CLDNJSONEncoding() }
/// Returns a `JSONEncoding` instance with `.prettyPrinted` writing options.
internal static var prettyPrinted: CLDNJSONEncoding { return CLDNJSONEncoding(options: .prettyPrinted) }
/// The options for writing the parameters as JSON data.
internal let options: JSONSerialization.WritingOptions
// MARK: Initialization
/// Creates a `CLDNJSONEncoding` instance using the specified options.
///
/// - parameter options: The options for writing the parameters as JSON data.
///
/// - returns: The new `CLDNJSONEncoding` instance.
internal init(options: JSONSerialization.WritingOptions = []) {
self.options = options
}
// MARK: Encoding
/// Creates a URL request by encoding parameters and applying them onto an existing request.
///
/// - parameter urlRequest: The request to have parameters applied.
/// - parameter parameters: The parameters to apply.
///
/// - throws: An `Error` if the encoding process encounters an error.
///
/// - returns: The encoded request.
internal func CLDN_Encode(_ urlRequest: CLDNURLRequestConvertible, with parameters: CLDNParameters?) throws -> URLRequest {
var urlRequest = try urlRequest.CLDN_AsURLRequest()
guard let parameters = parameters else { return urlRequest }
do {
let data = try JSONSerialization.data(withJSONObject: parameters, options: options)
if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
}
urlRequest.httpBody = data
} catch {
throw CLDNError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
}
return urlRequest
}
/// Creates a URL request by encoding the JSON object and setting the resulting data on the HTTP body.
///
/// - parameter urlRequest: The request to apply the JSON object to.
/// - parameter jsonObject: The JSON object to apply to the request.
///
/// - throws: An `Error` if the encoding process encounters an error.
///
/// - returns: The encoded request.
internal func encode(_ urlRequest: CLDNURLRequestConvertible, withJSONObject jsonObject: Any? = nil) throws -> URLRequest {
var urlRequest = try urlRequest.CLDN_AsURLRequest()
guard let jsonObject = jsonObject else { return urlRequest }
do {
let data = try JSONSerialization.data(withJSONObject: jsonObject, options: options)
if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
}
urlRequest.httpBody = data
} catch {
throw CLDNError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
}
return urlRequest
}
}
// MARK: -
extension NSNumber {
fileprivate var isBool: Bool { return CFBooleanGetTypeID() == CFGetTypeID(self) }
}
================================================
FILE: Cloudinary/Classes/Core/BaseNetwork/CLDNRequest.swift
================================================
//
// CLDNRequest.swift
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/// A type that can inspect and optionally adapt a `URLRequest` in some manner if necessary.
internal protocol CLDNRequestAdapter {
/// Inspects and adapts the specified `URLRequest` in some manner if necessary and returns the result.
///
/// - parameter urlRequest: The URL request to adapt.
///
/// - throws: An `Error` if the adaptation encounters an error.
///
/// - returns: The adapted `URLRequest`.
func CLDN_Adapt(_ urlRequest: URLRequest) throws -> URLRequest
}
// MARK: -
/// A closure executed when the `CLDNRequestRetrier` determines whether a `CLDNRequest` should be retried or not.
internal typealias CLDNRequestRetryCompletion = (_ shouldRetry: Bool, _ timeDelay: TimeInterval) -> Void
/// A type that determines whether a request should be retried after being executed by the specified session manager
/// and encountering an error.
internal protocol CLDNRequestRetrier {
/// Determines whether the `CLDNRequest` should be retried by calling the `completion` closure.
///
/// This operation is fully asynchronous. Any amount of time can be taken to determine whether the request needs
/// to be retried. The one requirement is that the completion closure is called to ensure the request is properly
/// cleaned up after.
///
/// - parameter manager: The session manager the request was executed on.
/// - parameter request: The request that failed due to the encountered error.
/// - parameter error: The error encountered when executing the request.
/// - parameter completion: The completion closure to be executed when retry decision has been determined.
func CLDN_Should(_ manager: CLDNSessionManager, retry request: CLDNRequest, with error: Error, completion: @escaping CLDNRequestRetryCompletion)
}
// MARK: -
protocol CLDNTaskConvertible {
func CLDN_Task(session: URLSession, adapter: CLDNRequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask
}
/// A dictionary of headers to apply to a `URLRequest`.
internal typealias CLDNHTTPHeaders = [String: String]
// MARK: -
/// Responsible for sending a request and receiving the response and associated data from the server, as well as
/// managing its underlying `URLSessionTask`.
internal class CLDNRequest {
// MARK: Helper Types
/// A closure executed when monitoring upload or download progress of a request.
internal typealias ProgressHandler = (Progress) -> Void
enum RequestTask {
case data(CLDNTaskConvertible?, URLSessionTask?)
case upload(CLDNTaskConvertible?, URLSessionTask?)
}
// MARK: Properties
/// The delegate for the underlying task.
internal var delegate: CLDNTaskDelegate {
get {
taskDelegateLock.lock() ; defer { taskDelegateLock.unlock() }
return taskDelegate
}
set {
taskDelegateLock.lock() ; defer { taskDelegateLock.unlock() }
taskDelegate = newValue
}
}
/// The underlying task.
internal var task: URLSessionTask? { return delegate.task }
/// The session belonging to the underlying task.
internal let session: URLSession
/// The request sent or to be sent to the server.
internal var request: URLRequest? { return task?.originalRequest }
/// The response received from the server, if any.
internal var response: HTTPURLResponse? { return task?.response as? HTTPURLResponse }
/// The number of times the request has been retried.
internal var retryCount: UInt = 0
let originalTask: CLDNTaskConvertible?
var startTime: CFAbsoluteTime?
var endTime: CFAbsoluteTime?
var validations: [() -> Void] = []
private var taskDelegate: CLDNTaskDelegate
private var taskDelegateLock = NSLock()
// MARK: Lifecycle
init(session: URLSession, requestTask: RequestTask, error: Error? = nil) {
self.session = session
switch requestTask {
case .data(let originalTask, let task):
taskDelegate = CLDNDataTaskDelegate(task: task)
self.originalTask = originalTask
case .upload(let originalTask, let task):
taskDelegate = CLDNUploadTaskDelegate(task: task)
self.originalTask = originalTask
}
delegate.error = error
delegate.queue.addOperation { self.endTime = CFAbsoluteTimeGetCurrent() }
}
// MARK: Authentication
/// Associates an HTTP Basic credential with the request.
///
/// - parameter user: The user.
/// - parameter password: The password.
/// - parameter persistence: The URL credential persistence. `.ForSession` by default.
///
/// - returns: The request.
@discardableResult
internal func authenticate(
user: String,
password: String,
persistence: URLCredential.Persistence = .forSession)
-> Self
{
let credential = URLCredential(user: user, password: password, persistence: persistence)
return authenticate(usingCredential: credential)
}
/// Associates a specified credential with the request.
///
/// - parameter credential: The credential.
///
/// - returns: The request.
@discardableResult
internal func authenticate(usingCredential credential: URLCredential) -> Self {
delegate.credential = credential
return self
}
/// Returns a base64 encoded basic authentication credential as an authorization header tuple.
///
/// - parameter user: The user.
/// - parameter password: The password.
///
/// - returns: A tuple with Authorization header and credential value if encoding succeeds, `nil` otherwise.
internal class func authorizationHeader(user: String, password: String) -> (key: String, value: String)? {
guard let data = "\(user):\(password)".data(using: .utf8) else { return nil }
let credential = data.base64EncodedString(options: [])
return (key: "Authorization", value: "Basic \(credential)")
}
// MARK: State
/// Resumes the request.
internal func resume() {
guard let task = task else { delegate.queue.isSuspended = false ; return }
if startTime == nil { startTime = CFAbsoluteTimeGetCurrent() }
task.resume()
// NotificationCenter.default.post(
// name: Notification.Name.Task.DidResume,
// object: self,
// userInfo: [Notification.Key.Task: task]
// )
}
/// Suspends the request.
internal func suspend() {
guard let task = task else { return }
task.suspend()
}
/// Cancels the request.
internal func cancel() {
guard let task = task else { return }
task.cancel()
}
}
// MARK: - CustomStringConvertible
extension CLDNRequest: CustomStringConvertible {
/// The textual representation used when written to an output stream, which includes the HTTP method and URL, as
/// well as the response status code if a response has been received.
internal var description: String {
var components: [String] = []
if let HTTPMethod = request?.httpMethod {
components.append(HTTPMethod)
}
if let urlString = request?.url?.absoluteString {
components.append(urlString)
}
if let response = response {
components.append("(\(response.statusCode))")
}
return components.joined(separator: " ")
}
}
// MARK: - CustomDebugStringConvertible
extension CLDNRequest: CustomDebugStringConvertible {
/// The textual representation used when written to an output stream, in the form of a cURL command.
internal var debugDescription: String {
return cURLRepresentation()
}
func cURLRepresentation() -> String {
var components = ["$ curl -v"]
guard let request = self.request,
let url = request.url,
let host = url.host
else {
return "$ curl command could not be created"
}
if let httpMethod = request.httpMethod, httpMethod != "GET" {
components.append("-X \(httpMethod)")
}
if let credentialStorage = self.session.configuration.urlCredentialStorage {
let protectionSpace = URLProtectionSpace(
host: host,
port: url.port ?? 0,
protocol: url.scheme,
realm: host,
authenticationMethod: NSURLAuthenticationMethodHTTPBasic
)
if let credentials = credentialStorage.credentials(for: protectionSpace)?.values {
for credential in credentials {
guard let user = credential.user, let password = credential.password else { continue }
components.append("-u \(user):\(password)")
}
} else {
if let credential = delegate.credential, let user = credential.user, let password = credential.password {
components.append("-u \(user):\(password)")
}
}
}
if session.configuration.httpShouldSetCookies {
if
let cookieStorage = session.configuration.httpCookieStorage,
let cookies = cookieStorage.cookies(for: url), !cookies.isEmpty
{
let string = cookies.reduce("") { $0 + "\($1.name)=\($1.value);" }
#if swift(>=3.2)
components.append("-b \"\(string[.. URLSessionTask {
do {
let urlRequest = try self.urlRequest.adapt(using: adapter)
return queue.sync { session.dataTask(with: urlRequest) }
} catch {
throw AdaptError(error: error)
}
}
}
// MARK: Properties
/// The request sent or to be sent to the server.
internal override var request: URLRequest? {
if let request = super.request { return request }
if let requestable = originalTask as? Requestable { return requestable.urlRequest }
return nil
}
/// The progress of fetching the response data from the server for the request.
internal var progress: Progress { return dataDelegate.progress }
var dataDelegate: CLDNDataTaskDelegate { return delegate as! CLDNDataTaskDelegate }
// MARK: Stream
/// Sets a closure to be called periodically during the lifecycle of the request as data is read from the server.
///
/// This closure returns the bytes most recently received from the server, not including data from previous calls.
/// If this closure is set, data will only be available within this closure, and will not be saved elsewhere. It is
/// also important to note that the server data in any `Response` object will be `nil`.
///
/// - parameter closure: The code to be executed periodically during the lifecycle of the request.
///
/// - returns: The request.
@discardableResult
internal func stream(closure: ((Data) -> Void)? = nil) -> Self {
dataDelegate.dataStream = closure
return self
}
// MARK: Progress
/// Sets a closure to be called periodically during the lifecycle of the `CLDNRequest` as data is read from the server.
///
/// - parameter queue: The dispatch queue to execute the closure on.
/// - parameter closure: The code to be executed periodically as data is read from the server.
///
/// - returns: The request.
@discardableResult
internal func downloadProgress(queue: DispatchQueue = DispatchQueue.main, closure: @escaping ProgressHandler) -> Self {
dataDelegate.progressHandler = (closure, queue)
return self
}
}
// MARK: -
/// Specific type of `CLDNRequest` that manages an underlying `URLSessionUploadTask`.
internal class CLDNUploadRequest: CLDNDataRequest {
// MARK: Helper Types
enum Uploadable: CLDNTaskConvertible {
case data(Data, URLRequest)
case file(URL, URLRequest)
case stream(InputStream, URLRequest)
func CLDN_Task(session: URLSession, adapter: CLDNRequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask {
do {
let task: URLSessionTask
switch self {
case let .data(data, urlRequest):
let urlRequest = try urlRequest.adapt(using: adapter)
task = queue.sync { session.uploadTask(with: urlRequest, from: data) }
case let .file(url, urlRequest):
let urlRequest = try urlRequest.adapt(using: adapter)
task = queue.sync { session.uploadTask(with: urlRequest, fromFile: url) }
case let .stream(_, urlRequest):
let urlRequest = try urlRequest.adapt(using: adapter)
task = queue.sync { session.uploadTask(withStreamedRequest: urlRequest) }
}
return task
} catch {
throw AdaptError(error: error)
}
}
}
// MARK: Properties
/// The request sent or to be sent to the server.
internal override var request: URLRequest? {
if let request = super.request { return request }
guard let uploadable = originalTask as? Uploadable else { return nil }
switch uploadable {
case .data(_, let urlRequest), .file(_, let urlRequest), .stream(_, let urlRequest):
return urlRequest
}
}
/// The progress of uploading the payload to the server for the upload request.
internal var uploadProgress: Progress { return uploadDelegate.uploadProgress }
var uploadDelegate: CLDNUploadTaskDelegate { return delegate as! CLDNUploadTaskDelegate }
// MARK: Upload Progress
/// Sets a closure to be called periodically during the lifecycle of the `CLDNUploadRequest` as data is sent to
/// the server.
///
/// After the data is sent to the server, the `progress(queue:closure:)` APIs can be used to monitor the progress
/// of data being read from the server.
///
/// - parameter queue: The dispatch queue to execute the closure on.
/// - parameter closure: The code to be executed periodically as data is sent to the server.
///
/// - returns: The request.
@discardableResult
internal func uploadProgress(queue: DispatchQueue = DispatchQueue.main, closure: @escaping ProgressHandler) -> Self {
uploadDelegate.uploadProgressHandler = (closure, queue)
return self
}
}
================================================
FILE: Cloudinary/Classes/Core/BaseNetwork/CLDNResponse.swift
================================================
//
// CLDNResponse.swift
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/// Used to store all data associated with an non-serialized response of a data or upload request.
internal struct CLDNDefaultDataResponse {
/// The URL request sent to the server.
internal let request: URLRequest?
/// The server's response to the URL request.
internal let response: HTTPURLResponse?
/// The data returned by the server.
internal let data: Data?
/// The error encountered while executing or validating the request.
internal let error: Error?
/// The timeline of the complete lifecycle of the request.
internal let timeline: CLDNTimeline
var _metrics: AnyObject?
/// Creates a `CLDNDefaultDataResponse` instance from the specified parameters.
///
/// - Parameters:
/// - request: The URL request sent to the server.
/// - response: The server's response to the URL request.
/// - data: The data returned by the server.
/// - error: The error encountered while executing or validating the request.
/// - timeline: The timeline of the complete lifecycle of the request. `CLDNTimeline()` by default.
/// - metrics: The task metrics containing the request / response statistics. `nil` by default.
internal init(
request: URLRequest?,
response: HTTPURLResponse?,
data: Data?,
error: Error?,
timeline: CLDNTimeline = CLDNTimeline(),
metrics: AnyObject? = nil)
{
self.request = request
self.response = response
self.data = data
self.error = error
self.timeline = timeline
}
}
// MARK: -
/// Used to store all data associated with a serialized response of a data or upload request.
internal struct CLDNDataResponse {
/// The URL request sent to the server.
internal let request: URLRequest?
/// The server's response to the URL request.
internal let response: HTTPURLResponse?
/// The data returned by the server.
internal let data: Data?
/// The result of response serialization.
internal let result: CLDNResult
/// The timeline of the complete lifecycle of the request.
internal let timeline: CLDNTimeline
/// Returns the associated value of the result if it is a success, `nil` otherwise.
internal var value: Value? { return result.value }
/// Returns the associated error value if the result if it is a failure, `nil` otherwise.
internal var error: Error? { return result.error }
var _metrics: AnyObject?
/// Creates a `CLDNDataResponse` instance with the specified parameters derived from response serialization.
///
/// - parameter request: The URL request sent to the server.
/// - parameter response: The server's response to the URL request.
/// - parameter data: The data returned by the server.
/// - parameter result: The result of response serialization.
/// - parameter timeline: The timeline of the complete lifecycle of the `CLDNRequest`. Defaults to `CLDNTimeline()`.
///
/// - returns: The new `CLDNDataResponse` instance.
internal init(
request: URLRequest?,
response: HTTPURLResponse?,
data: Data?,
result: CLDNResult,
timeline: CLDNTimeline = CLDNTimeline())
{
self.request = request
self.response = response
self.data = data
self.result = result
self.timeline = timeline
}
}
// MARK: -
extension CLDNDataResponse: CustomStringConvertible, CustomDebugStringConvertible {
/// The textual representation used when written to an output stream, which includes whether the result was a
/// success or failure.
internal var description: String {
return result.debugDescription
}
/// The debug textual representation used when written to an output stream, which includes the URL request, the URL
/// response, the server data, the response serialization result and the timeline.
internal var debugDescription: String {
let requestDescription = request.map { "\($0.httpMethod ?? "GET") \($0)"} ?? "nil"
let requestBody = request?.httpBody.map { String(decoding: $0, as: UTF8.self) } ?? "None"
let responseDescription = response.map { "\($0)" } ?? "nil"
let responseBody = data.map { String(decoding: $0, as: UTF8.self) } ?? "None"
return """
[Request]: \(requestDescription)
[Request Body]: \n\(requestBody)
[Response]: \(responseDescription)
[Response Body]: \n\(responseBody)
[Result]: \(result)
[Timeline]: \(timeline.debugDescription)
"""
}
}
// MARK: -
protocol CLDNResponse {
/// The task metrics containing the request / response statistics.
var _metrics: AnyObject? { get set }
mutating func CLDN_Add(_ metrics: AnyObject?)
}
extension CLDNResponse {
mutating func CLDN_Add(_ metrics: AnyObject?) {
#if !os(watchOS)
guard #available(iOS 10.0, macOS 10.12, tvOS 10.0, *) else { return }
guard let metrics = metrics as? URLSessionTaskMetrics else { return }
_metrics = metrics
#endif
}
}
// MARK: -
@available(iOS 10.0, macOS 10.12, tvOS 10.0, *)
extension CLDNDefaultDataResponse: CLDNResponse {
#if !os(watchOS)
/// The task metrics containing the request / response statistics.
internal var metrics: URLSessionTaskMetrics? { return _metrics as? URLSessionTaskMetrics }
#endif
}
@available(iOS 10.0, macOS 10.12, tvOS 10.0, *)
extension CLDNDataResponse: CLDNResponse {
#if !os(watchOS)
/// The task metrics containing the request / response statistics.
internal var metrics: URLSessionTaskMetrics? { return _metrics as? URLSessionTaskMetrics }
#endif
}
================================================
FILE: Cloudinary/Classes/Core/BaseNetwork/CLDNResponseSerialization.swift
================================================
//
// CLDNResponseSerialization.swift
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/// The type in which all data response serializers must conform to in order to serialize a response.
internal protocol CLDNDataResponseSerializerProtocol {
/// The type of serialized object to be created by this `DataResponseSerializerType`.
associatedtype SerializedObject
/// A closure used by response handlers that takes a request, response, data and error and returns a result.
var serializeResponse: (URLRequest?, HTTPURLResponse?, Data?, Error?) -> CLDNResult { get }
}
// MARK: -
/// A generic `DataResponseSerializerType` used to serialize a request, response, and data into a serialized object.
internal struct CLDNDataResponseSerializer: CLDNDataResponseSerializerProtocol {
/// The type of serialized object to be created by this `CLDNDataResponseSerializer`.
internal typealias SerializedObject = Value
/// A closure used by response handlers that takes a request, response, data and error and returns a result.
internal var serializeResponse: (URLRequest?, HTTPURLResponse?, Data?, Error?) -> CLDNResult
/// Initializes the `ResponseSerializer` instance with the given serialize response closure.
///
/// - parameter serializeResponse: The closure used to serialize the response.
///
/// - returns: The new generic response serializer instance.
internal init(serializeResponse: @escaping (URLRequest?, HTTPURLResponse?, Data?, Error?) -> CLDNResult) {
self.serializeResponse = serializeResponse
}
}
// MARK: - Timeline
extension CLDNRequest {
var timeline: CLDNTimeline {
let requestStartTime = self.startTime ?? CFAbsoluteTimeGetCurrent()
let requestCompletedTime = self.endTime ?? CFAbsoluteTimeGetCurrent()
let initialResponseTime = self.delegate.initialResponseTime ?? requestCompletedTime
return CLDNTimeline(
requestStartTime: requestStartTime,
initialResponseTime: initialResponseTime,
requestCompletedTime: requestCompletedTime,
serializationCompletedTime: CFAbsoluteTimeGetCurrent()
)
}
}
// MARK: - Default
extension CLDNDataRequest {
/// Adds a handler to be called once the request has finished.
///
/// - parameter queue: The queue on which the completion handler is dispatched.
/// - parameter completionHandler: The code to be executed once the request has finished.
///
/// - returns: The request.
@discardableResult
internal func response(queue: DispatchQueue? = nil, completionHandler: @escaping (CLDNDefaultDataResponse) -> Void) -> Self {
delegate.queue.addOperation {
(queue ?? DispatchQueue.main).async {
var dataResponse = CLDNDefaultDataResponse(
request: self.request,
response: self.response,
data: self.delegate.data,
error: self.delegate.error,
timeline: self.timeline
)
if #available(iOS 10.0, *)
{
dataResponse.CLDN_Add(self.delegate.metrics)
}
completionHandler(dataResponse)
}
}
return self
}
/// Adds a handler to be called once the request has finished.
///
/// - parameter queue: The queue on which the completion handler is dispatched.
/// - parameter responseSerializer: The response serializer responsible for serializing the request, response,
/// and data.
/// - parameter completionHandler: The code to be executed once the request has finished.
///
/// - returns: The request.
@discardableResult
internal func response(
queue: DispatchQueue? = nil,
responseSerializer: T,
completionHandler: @escaping (CLDNDataResponse) -> Void)
-> Self
{
delegate.queue.addOperation {
let result = responseSerializer.serializeResponse(
self.request,
self.response,
self.delegate.data,
self.delegate.error
)
var dataResponse = CLDNDataResponse(
request: self.request,
response: self.response,
data: self.delegate.data,
result: result,
timeline: self.timeline
)
if #available(iOS 10.0, *)
{
dataResponse.CLDN_Add(self.delegate.metrics)
}
(queue ?? DispatchQueue.main).async { completionHandler(dataResponse) }
}
return self
}
}
// MARK: - Data
extension CLDNRequest {
/// Returns a result data type that contains the response data as-is.
///
/// - parameter response: The response from the server.
/// - parameter data: The data returned from the server.
/// - parameter error: The error already encountered if it exists.
///
/// - returns: The result data type.
internal static func serializeResponseData(response: HTTPURLResponse?, data: Data?, error: Error?) -> CLDNResult {
guard error == nil else { return .failure(error!) }
if let response = response, emptyDataStatusCodes.contains(response.statusCode) { return .success(Data()) }
guard let validData = data else {
if let statusCode = response?.statusCode,
let httpStatusCode = HTTPStatusCode(rawValue: statusCode), httpStatusCode.isError {
return .failure(CLDError.error(code: statusCode, userInfo: ["message": httpStatusCode.localizedReason]))
}
return .failure(CLDNError.responseSerializationFailed(reason: .inputDataNil))
}
return .success(validData)
}
}
extension CLDNDataRequest {
/// Creates a response serializer that returns the associated data as-is.
///
/// - returns: A data response serializer.
internal static func dataResponseSerializer() -> CLDNDataResponseSerializer {
return CLDNDataResponseSerializer { _, response, data, error in
return CLDNRequest.serializeResponseData(response: response, data: data, error: error)
}
}
/// Adds a handler to be called once the request has finished.
///
/// - parameter completionHandler: The code to be executed once the request has finished.
///
/// - returns: The request.
@discardableResult
internal func responseData(
queue: DispatchQueue? = nil,
completionHandler: @escaping (CLDNDataResponse) -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer: CLDNDataRequest.dataResponseSerializer(),
completionHandler: completionHandler
)
}
}
// MARK: - String
extension CLDNRequest {
/// Returns a result string type initialized from the response data with the specified string encoding.
///
/// - parameter encoding: The string encoding. If `nil`, the string encoding will be determined from the server
/// response, falling back to the default HTTP default character set, ISO-8859-1.
/// - parameter response: The response from the server.
/// - parameter data: The data returned from the server.
/// - parameter error: The error already encountered if it exists.
///
/// - returns: The result data type.
internal static func serializeResponseString(
encoding: String.Encoding?,
response: HTTPURLResponse?,
data: Data?,
error: Error?)
-> CLDNResult
{
guard error == nil else { return .failure(error!) }
if let response = response, emptyDataStatusCodes.contains(response.statusCode) { return .success("") }
guard let validData = data else {
return .failure(CLDNError.responseSerializationFailed(reason: .inputDataNil))
}
var convertedEncoding = encoding
if let encodingName = response?.textEncodingName as CFString?, convertedEncoding == nil {
convertedEncoding = String.Encoding(rawValue: CFStringConvertEncodingToNSStringEncoding(
CFStringConvertIANACharSetNameToEncoding(encodingName))
)
}
let actualEncoding = convertedEncoding ?? .isoLatin1
if let string = String(data: validData, encoding: actualEncoding) {
return .success(string)
} else {
if let statusCode = response?.statusCode,
let httpStatusCode = HTTPStatusCode(rawValue: statusCode), httpStatusCode.isError {
return .failure(CLDError.error(code: statusCode, userInfo: ["message": httpStatusCode.localizedReason]))
}
return .failure(CLDNError.responseSerializationFailed(reason: .stringSerializationFailed(encoding: actualEncoding)))
}
}
}
extension CLDNDataRequest {
/// Creates a response serializer that returns a result string type initialized from the response data with
/// the specified string encoding.
///
/// - parameter encoding: The string encoding. If `nil`, the string encoding will be determined from the server
/// response, falling back to the default HTTP default character set, ISO-8859-1.
///
/// - returns: A string response serializer.
internal static func stringResponseSerializer(encoding: String.Encoding? = nil) -> CLDNDataResponseSerializer {
return CLDNDataResponseSerializer { _, response, data, error in
return CLDNRequest.serializeResponseString(encoding: encoding, response: response, data: data, error: error)
}
}
/// Adds a handler to be called once the request has finished.
///
/// - parameter encoding: The string encoding. If `nil`, the string encoding will be determined from the
/// server response, falling back to the default HTTP default character set,
/// ISO-8859-1.
/// - parameter completionHandler: A closure to be executed once the request has finished.
///
/// - returns: The request.
@discardableResult
internal func responseString(
queue: DispatchQueue? = nil,
encoding: String.Encoding? = nil,
completionHandler: @escaping (CLDNDataResponse) -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer: CLDNDataRequest.stringResponseSerializer(encoding: encoding),
completionHandler: completionHandler
)
}
}
// MARK: - JSON
extension CLDNRequest {
/// Returns a JSON object contained in a result type constructed from the response data using `JSONSerialization`
/// with the specified reading options.
///
/// - parameter options: The JSON serialization reading options. Defaults to `.allowFragments`.
/// - parameter response: The response from the server.
/// - parameter data: The data returned from the server.
/// - parameter error: The error already encountered if it exists.
///
/// - returns: The result data type.
internal static func serializeResponseJSON(
options: JSONSerialization.ReadingOptions,
response: HTTPURLResponse?,
data: Data?,
error: Error?)
-> CLDNResult
{
guard error == nil else { return .failure(error!) }
if let response = response, emptyDataStatusCodes.contains(response.statusCode) { return .success(NSNull()) }
guard let validData = data, validData.count > 0 else {
return .failure(CLDNError.responseSerializationFailed(reason: .inputDataNilOrZeroLength))
}
do {
let json = try JSONSerialization.jsonObject(with: validData, options: options)
return .success(json)
} catch {
if let statusCode = response?.statusCode,
let httpStatusCode = HTTPStatusCode(rawValue: statusCode), httpStatusCode.isError {
return .failure(CLDError.error(code: statusCode, userInfo: ["message": httpStatusCode.localizedReason]))
}
return .failure(CLDNError.responseSerializationFailed(reason: .jsonSerializationFailed(error: error)))
}
}
}
extension CLDNDataRequest {
/// Creates a response serializer that returns a JSON object result type constructed from the response data using
/// `JSONSerialization` with the specified reading options.
///
/// - parameter options: The JSON serialization reading options. Defaults to `.allowFragments`.
///
/// - returns: A JSON object response serializer.
internal static func jsonResponseSerializer(
options: JSONSerialization.ReadingOptions = .allowFragments)
-> CLDNDataResponseSerializer
{
return CLDNDataResponseSerializer { _, response, data, error in
return CLDNRequest.serializeResponseJSON(options: options, response: response, data: data, error: error)
}
}
/// Adds a handler to be called once the request has finished.
///
/// - parameter options: The JSON serialization reading options. Defaults to `.allowFragments`.
/// - parameter completionHandler: A closure to be executed once the request has finished.
///
/// - returns: The request.
@discardableResult
internal func responseJSON(
queue: DispatchQueue? = nil,
options: JSONSerialization.ReadingOptions = .allowFragments,
completionHandler: @escaping (CLDNDataResponse) -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer: CLDNDataRequest.jsonResponseSerializer(options: options),
completionHandler: completionHandler
)
}
}
/// A set of HTTP response status code that do not contain response data.
private let emptyDataStatusCodes: Set = [204, 205]
================================================
FILE: Cloudinary/Classes/Core/BaseNetwork/CLDNResult.swift
================================================
//
// CLDNResult.swift
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/// Used to represent whether a request was successful or encountered an error.
///
/// - success: The request and all post processing operations were successful resulting in the serialization of the
/// provided associated value.
///
/// - failure: The request encountered an error resulting in a failure. The associated values are the original data
/// provided by the server as well as the error that caused the failure.
internal enum CLDNResult {
case success(Value)
case failure(Error)
/// Returns `true` if the result is a success, `false` otherwise.
internal var isSuccess: Bool {
switch self {
case .success:
return true
case .failure:
return false
}
}
/// Returns `true` if the result is a failure, `false` otherwise.
internal var isFailure: Bool {
return !isSuccess
}
/// Returns the associated value if the result is a success, `nil` otherwise.
internal var value: Value? {
switch self {
case .success(let value):
return value
case .failure:
return nil
}
}
/// Returns the associated error value if the result is a failure, `nil` otherwise.
internal var error: Error? {
switch self {
case .success:
return nil
case .failure(let error):
return error
}
}
}
// MARK: - CustomStringConvertible
extension CLDNResult: CustomStringConvertible {
/// The textual representation used when written to an output stream, which includes whether the result was a
/// success or failure.
internal var description: String {
switch self {
case .success:
return "SUCCESS"
case .failure:
return "FAILURE"
}
}
}
// MARK: - CustomDebugStringConvertible
extension CLDNResult: CustomDebugStringConvertible {
/// The debug textual representation used when written to an output stream, which includes whether the result was a
/// success or failure in addition to the value or error.
internal var debugDescription: String {
switch self {
case .success(let value):
return "SUCCESS: \(value)"
case .failure(let error):
return "FAILURE: \(error)"
}
}
}
// MARK: - Functional APIs
extension CLDNResult {
/// Creates a `CLDNResult` instance from the result of a closure.
///
/// A failure result is created when the closure throws, and a success result is created when the closure
/// succeeds without throwing an error.
///
/// func someString() throws -> String { ... }
///
/// let result = CLDNResult(value: {
/// return try someString()
/// })
///
/// // The type of result is CLDNResult
///
/// The trailing closure syntax is also supported:
///
/// let result = CLDNResult { try someString() }
///
/// - parameter value: The closure to execute and create the result for.
internal init(value: () throws -> Value) {
do {
self = try .success(value())
} catch {
self = .failure(error)
}
}
/// Returns the success value, or throws the failure error.
///
/// let possibleString: Result = .success("success")
/// try print(possibleString.unwrap())
/// // Prints "success"
///
/// let noString: CLDNResult = .failure(error)
/// try print(noString.unwrap())
/// // Throws error
internal func unwrap() throws -> Value {
switch self {
case .success(let value):
return value
case .failure(let error):
throw error
}
}
/// Evaluates the specified closure when the `CLDNResult` is a success, passing the unwrapped value as a parameter.
///
/// Use the `map` method with a closure that does not throw. For example:
///
/// let possibleData: CLDNResult = .success(Data())
/// let possibleInt = possibleData.map { $0.count }
/// try print(possibleInt.unwrap())
/// // Prints "0"
///
/// let noData: CLDNResult = .failure(error)
/// let noInt = noData.map { $0.count }
/// try print(noInt.unwrap())
/// // Throws error
///
/// - parameter transform: A closure that takes the success value of the `CLDNResult` instance.
///
/// - returns: A `CLDNResult` containing the result of the given closure. If this instance is a failure, returns the
/// same failure.
internal func map(_ transform: (Value) -> T) -> CLDNResult {
switch self {
case .success(let value):
return .success(transform(value))
case .failure(let error):
return .failure(error)
}
}
/// Evaluates the specified closure when the `CLDNResult` is a success, passing the unwrapped value as a parameter.
///
/// Use the `flatMap` method with a closure that may throw an error. For example:
///
/// let possibleData: CLDNResult = .success(Data(...))
/// let possibleObject = possibleData.flatMap {
/// try JSONSerialization.jsonObject(with: $0)
/// }
///
/// - parameter transform: A closure that takes the success value of the instance.
///
/// - returns: A `CLDNResult` containing the result of the given closure. If this instance is a failure, returns the
/// same failure.
internal func flatMap(_ transform: (Value) throws -> T) -> CLDNResult {
switch self {
case .success(let value):
do {
return try .success(transform(value))
} catch {
return .failure(error)
}
case .failure(let error):
return .failure(error)
}
}
/// Evaluates the specified closure when the `CLDNResult` is a failure, passing the unwrapped error as a parameter.
///
/// Use the `mapError` function with a closure that does not throw. For example:
///
/// let possibleData: CLDNResult = .failure(someError)
/// let withMyError: CLDNResult = possibleData.mapError { MyError.error($0) }
///
/// - Parameter transform: A closure that takes the error of the instance.
/// - Returns: A `CLDNResult` instance containing the result of the transform. If this instance is a success, returns
/// the same instance.
internal func mapError(_ transform: (Error) -> T) -> CLDNResult {
switch self {
case .failure(let error):
return .failure(transform(error))
case .success:
return self
}
}
/// Evaluates the specified closure when the `CLDNResult` is a failure, passing the unwrapped error as a parameter.
///
/// Use the `flatMapError` function with a closure that may throw an error. For example:
///
/// let possibleData: CLDNResult = .success(Data(...))
/// let possibleObject = possibleData.flatMapError {
/// try someFailableFunction(taking: $0)
/// }
///
/// - Parameter transform: A throwing closure that takes the error of the instance.
///
/// - Returns: A `CLDNResult` instance containing the result of the transform. If this instance is a success, returns
/// the same instance.
internal func flatMapError(_ transform: (Error) throws -> T) -> CLDNResult {
switch self {
case .failure(let error):
do {
return try .failure(transform(error))
} catch {
return .failure(error)
}
case .success:
return self
}
}
/// Evaluates the specified closure when the `CLDNResult` is a success, passing the unwrapped value as a parameter.
///
/// Use the `withValue` function to evaluate the passed closure without modifying the `CLDNResult` instance.
///
/// - Parameter closure: A closure that takes the success value of this instance.
/// - Returns: This `CLDNResult` instance, unmodified.
@discardableResult
internal func withValue(_ closure: (Value) throws -> Void) rethrows -> CLDNResult {
if case let .success(value) = self { try closure(value) }
return self
}
/// Evaluates the specified closure when the `CLDNResult` is a failure, passing the unwrapped error as a parameter.
///
/// Use the `withError` function to evaluate the passed closure without modifying the `CLDNResult` instance.
///
/// - Parameter closure: A closure that takes the success value of this instance.
/// - Returns: This `CLDNResult` instance, unmodified.
@discardableResult
internal func withError(_ closure: (Error) throws -> Void) rethrows -> CLDNResult {
if case let .failure(error) = self { try closure(error) }
return self
}
/// Evaluates the specified closure when the `CLDNResult` is a success.
///
/// Use the `ifSuccess` function to evaluate the passed closure without modifying the `CLDNResult` instance.
///
/// - Parameter closure: A `Void` closure.
/// - Returns: This `CLDNResult` instance, unmodified.
@discardableResult
internal func ifSuccess(_ closure: () throws -> Void) rethrows -> CLDNResult {
if isSuccess { try closure() }
return self
}
/// Evaluates the specified closure when the `CLDNResult` is a failure.
///
/// Use the `ifFailure` function to evaluate the passed closure without modifying the `CLDNResult` instance.
///
/// - Parameter closure: A `Void` closure.
/// - Returns: This `CLDNResult` instance, unmodified.
@discardableResult
internal func ifFailure(_ closure: () throws -> Void) rethrows -> CLDNResult {
if isFailure { try closure() }
return self
}
}
================================================
FILE: Cloudinary/Classes/Core/BaseNetwork/CLDNSessionDelegate.swift
================================================
//
// CLDNSessionDelegate.swift
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/// Responsible for handling all delegate callbacks for the underlying session.
internal class CLDNSessionDelegate: NSObject {
// MARK: URLSessionDelegate Overrides
/// Overrides default behavior for URLSessionDelegate method `urlSession(_:didBecomeInvalidWithError:)`.
internal var sessionDidBecomeInvalidWithError: ((URLSession, Error?) -> Void)?
/// Overrides default behavior for URLSessionDelegate method `urlSession(_:didReceive:completionHandler:)`.
internal var sessionDidReceiveChallenge: ((URLSession, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?))?
/// Overrides all behavior for URLSessionDelegate method `urlSession(_:didReceive:completionHandler:)` and requires the caller to call the `completionHandler`.
internal var sessionDidReceiveChallengeWithCompletion: ((URLSession, URLAuthenticationChallenge, @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void)?
/// Overrides default behavior for URLSessionDelegate method `urlSessionDidFinishEvents(forBackgroundURLSession:)`.
internal var sessionDidFinishEventsForBackgroundURLSession: ((URLSession) -> Void)?
// MARK: URLSessionTaskDelegate Overrides
/// Overrides default behavior for URLSessionTaskDelegate method `urlSession(_:task:willPerformHTTPRedirection:newRequest:completionHandler:)`.
internal var taskWillPerformHTTPRedirection: ((URLSession, URLSessionTask, HTTPURLResponse, URLRequest) -> URLRequest?)?
/// Overrides all behavior for URLSessionTaskDelegate method `urlSession(_:task:willPerformHTTPRedirection:newRequest:completionHandler:)` and
/// requires the caller to call the `completionHandler`.
internal var taskWillPerformHTTPRedirectionWithCompletion: ((URLSession, URLSessionTask, HTTPURLResponse, URLRequest, @escaping (URLRequest?) -> Void) -> Void)?
/// Overrides default behavior for URLSessionTaskDelegate method `urlSession(_:task:didReceive:completionHandler:)`.
internal var taskDidReceiveChallenge: ((URLSession, URLSessionTask, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?))?
/// Overrides all behavior for URLSessionTaskDelegate method `urlSession(_:task:didReceive:completionHandler:)` and
/// requires the caller to call the `completionHandler`.
internal var taskDidReceiveChallengeWithCompletion: ((URLSession, URLSessionTask, URLAuthenticationChallenge, @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void)?
/// Overrides default behavior for URLSessionTaskDelegate method `urlSession(_:task:needNewBodyStream:)`.
internal var taskNeedNewBodyStream: ((URLSession, URLSessionTask) -> InputStream?)?
/// Overrides all behavior for URLSessionTaskDelegate method `urlSession(_:task:needNewBodyStream:)` and
/// requires the caller to call the `completionHandler`.
internal var taskNeedNewBodyStreamWithCompletion: ((URLSession, URLSessionTask, @escaping (InputStream?) -> Void) -> Void)?
/// Overrides default behavior for URLSessionTaskDelegate method `urlSession(_:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:)`.
internal var taskDidSendBodyData: ((URLSession, URLSessionTask, Int64, Int64, Int64) -> Void)?
/// Overrides default behavior for URLSessionTaskDelegate method `urlSession(_:task:didCompleteWithError:)`.
internal var taskDidComplete: ((URLSession, URLSessionTask, Error?) -> Void)?
// MARK: URLSessionDataDelegate Overrides
/// Overrides default behavior for URLSessionDataDelegate method `urlSession(_:dataTask:didReceive:completionHandler:)`.
internal var dataTaskDidReceiveResponse: ((URLSession, URLSessionDataTask, URLResponse) -> URLSession.ResponseDisposition)?
/// Overrides all behavior for URLSessionDataDelegate method `urlSession(_:dataTask:didReceive:completionHandler:)` and
/// requires caller to call the `completionHandler`.
internal var dataTaskDidReceiveResponseWithCompletion: ((URLSession, URLSessionDataTask, URLResponse, @escaping (URLSession.ResponseDisposition) -> Void) -> Void)?
/// Overrides default behavior for URLSessionDataDelegate method `urlSession(_:dataTask:didBecome:)`.
internal var dataTaskDidBecomeDownloadTask: ((URLSession, URLSessionDataTask, URLSessionDownloadTask) -> Void)?
/// Overrides default behavior for URLSessionDataDelegate method `urlSession(_:dataTask:didReceive:)`.
internal var dataTaskDidReceiveData: ((URLSession, URLSessionDataTask, Data) -> Void)?
/// Overrides default behavior for URLSessionDataDelegate method `urlSession(_:dataTask:willCacheResponse:completionHandler:)`.
internal var dataTaskWillCacheResponse: ((URLSession, URLSessionDataTask, CachedURLResponse) -> CachedURLResponse?)?
/// Overrides all behavior for URLSessionDataDelegate method `urlSession(_:dataTask:willCacheResponse:completionHandler:)` and
/// requires caller to call the `completionHandler`.
internal var dataTaskWillCacheResponseWithCompletion: ((URLSession, URLSessionDataTask, CachedURLResponse, @escaping (CachedURLResponse?) -> Void) -> Void)?
// MARK: URLSessionDownloadDelegate Overrides
/// Overrides default behavior for URLSessionDownloadDelegate method `urlSession(_:downloadTask:didFinishDownloadingTo:)`.
internal var downloadTaskDidFinishDownloadingToURL: ((URLSession, URLSessionDownloadTask, URL) -> Void)?
/// Overrides default behavior for URLSessionDownloadDelegate method `urlSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)`.
internal var downloadTaskDidWriteData: ((URLSession, URLSessionDownloadTask, Int64, Int64, Int64) -> Void)?
/// Overrides default behavior for URLSessionDownloadDelegate method `urlSession(_:downloadTask:didResumeAtOffset:expectedTotalBytes:)`.
internal var downloadTaskDidResumeAtOffset: ((URLSession, URLSessionDownloadTask, Int64, Int64) -> Void)?
// MARK: URLSessionStreamDelegate Overrides
// MARK: Properties
var retrier: CLDNRequestRetrier?
weak var sessionManager: CLDNSessionManager?
var requests: [Int: CLDNRequest] = [:]
private let lock = NSLock()
/// Access the task delegate for the specified task in a thread-safe manner.
internal subscript(task: URLSessionTask) -> CLDNRequest? {
get {
lock.lock() ; defer { lock.unlock() }
return requests[task.taskIdentifier]
}
set {
lock.lock() ; defer { lock.unlock() }
requests[task.taskIdentifier] = newValue
}
}
// MARK: Lifecycle
/// Initializes the `CLDNSessionDelegate` instance.
///
/// - returns: The new `CLDNSessionDelegate` instance.
internal override init() {
super.init()
}
// MARK: NSObject Overrides
/// Returns a `Bool` indicating whether the `CLDNSessionDelegate` implements or inherits a method that can respond
/// to a specified message.
///
/// - parameter selector: A selector that identifies a message.
///
/// - returns: `true` if the receiver implements or inherits a method that can respond to selector, otherwise `false`.
internal override func responds(to selector: Selector) -> Bool {
#if !os(macOS)
if selector == #selector(URLSessionDelegate.urlSessionDidFinishEvents(forBackgroundURLSession:)) {
return sessionDidFinishEventsForBackgroundURLSession != nil
}
#endif
switch selector {
case #selector(URLSessionDelegate.urlSession(_:didBecomeInvalidWithError:)):
return sessionDidBecomeInvalidWithError != nil
case #selector(URLSessionDelegate.urlSession(_:didReceive:completionHandler:)):
return (sessionDidReceiveChallenge != nil || sessionDidReceiveChallengeWithCompletion != nil)
case #selector(URLSessionTaskDelegate.urlSession(_:task:willPerformHTTPRedirection:newRequest:completionHandler:)):
return (taskWillPerformHTTPRedirection != nil || taskWillPerformHTTPRedirectionWithCompletion != nil)
case #selector(URLSessionDataDelegate.urlSession(_:dataTask:didReceive:completionHandler:)):
return (dataTaskDidReceiveResponse != nil || dataTaskDidReceiveResponseWithCompletion != nil)
default:
return type(of: self).instancesRespond(to: selector)
}
}
}
// MARK: - URLSessionDelegate
extension CLDNSessionDelegate: URLSessionDelegate {
/// Tells the delegate that the session has been invalidated.
///
/// - parameter session: The session object that was invalidated.
/// - parameter error: The error that caused invalidation, or nil if the invalidation was explicit.
internal func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
sessionDidBecomeInvalidWithError?(session, error)
}
/// Requests credentials from the delegate in response to a session-level authentication request from the
/// remote server.
///
/// - parameter session: The session containing the task that requested authentication.
/// - parameter challenge: An object that contains the request for authentication.
/// - parameter completionHandler: A handler that your delegate method must call providing the disposition
/// and credential.
internal func urlSession(
_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
{
guard sessionDidReceiveChallengeWithCompletion == nil else {
sessionDidReceiveChallengeWithCompletion?(session, challenge, completionHandler)
return
}
var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling
var credential: URLCredential?
if let sessionDidReceiveChallenge = sessionDidReceiveChallenge {
(disposition, credential) = sessionDidReceiveChallenge(session, challenge)
}
completionHandler(disposition, credential)
}
#if !os(macOS)
/// Tells the delegate that all messages enqueued for a session have been delivered.
///
/// - parameter session: The session that no longer has any outstanding requests.
internal func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
sessionDidFinishEventsForBackgroundURLSession?(session)
}
#endif
}
// MARK: - URLSessionTaskDelegate
extension CLDNSessionDelegate: URLSessionTaskDelegate {
/// Tells the delegate that the remote server requested an HTTP redirect.
///
/// - parameter session: The session containing the task whose request resulted in a redirect.
/// - parameter task: The task whose request resulted in a redirect.
/// - parameter response: An object containing the server’s response to the original request.
/// - parameter request: A URL request object filled out with the new location.
/// - parameter completionHandler: A closure that your handler should call with either the value of the request
/// parameter, a modified URL request object, or NULL to refuse the redirect and
/// return the body of the redirect response.
internal func urlSession(
_ session: URLSession,
task: URLSessionTask,
willPerformHTTPRedirection response: HTTPURLResponse,
newRequest request: URLRequest,
completionHandler: @escaping (URLRequest?) -> Void)
{
guard taskWillPerformHTTPRedirectionWithCompletion == nil else {
taskWillPerformHTTPRedirectionWithCompletion?(session, task, response, request, completionHandler)
return
}
var redirectRequest: URLRequest? = request
if let taskWillPerformHTTPRedirection = taskWillPerformHTTPRedirection {
redirectRequest = taskWillPerformHTTPRedirection(session, task, response, request)
}
completionHandler(redirectRequest)
}
/// Requests credentials from the delegate in response to an authentication request from the remote server.
///
/// - parameter session: The session containing the task whose request requires authentication.
/// - parameter task: The task whose request requires authentication.
/// - parameter challenge: An object that contains the request for authentication.
/// - parameter completionHandler: A handler that your delegate method must call providing the disposition
/// and credential.
internal func urlSession(
_ session: URLSession,
task: URLSessionTask,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
{
guard taskDidReceiveChallengeWithCompletion == nil else {
taskDidReceiveChallengeWithCompletion?(session, task, challenge, completionHandler)
return
}
if let taskDidReceiveChallenge = taskDidReceiveChallenge {
let result = taskDidReceiveChallenge(session, task, challenge)
completionHandler(result.0, result.1)
} else if let delegate = self[task]?.delegate {
delegate.urlSession(
session,
task: task,
didReceive: challenge,
completionHandler: completionHandler
)
} else {
urlSession(session, didReceive: challenge, completionHandler: completionHandler)
}
}
/// Tells the delegate when a task requires a new request body stream to send to the remote server.
///
/// - parameter session: The session containing the task that needs a new body stream.
/// - parameter task: The task that needs a new body stream.
/// - parameter completionHandler: A completion handler that your delegate method should call with the new body stream.
internal func urlSession(
_ session: URLSession,
task: URLSessionTask,
needNewBodyStream completionHandler: @escaping (InputStream?) -> Void)
{
guard taskNeedNewBodyStreamWithCompletion == nil else {
taskNeedNewBodyStreamWithCompletion?(session, task, completionHandler)
return
}
if let taskNeedNewBodyStream = taskNeedNewBodyStream {
completionHandler(taskNeedNewBodyStream(session, task))
} else if let delegate = self[task]?.delegate {
delegate.urlSession(session, task: task, needNewBodyStream: completionHandler)
}
}
/// Periodically informs the delegate of the progress of sending body content to the server.
///
/// - parameter session: The session containing the data task.
/// - parameter task: The data task.
/// - parameter bytesSent: The number of bytes sent since the last time this delegate method was called.
/// - parameter totalBytesSent: The total number of bytes sent so far.
/// - parameter totalBytesExpectedToSend: The expected length of the body data.
internal func urlSession(
_ session: URLSession,
task: URLSessionTask,
didSendBodyData bytesSent: Int64,
totalBytesSent: Int64,
totalBytesExpectedToSend: Int64)
{
if let taskDidSendBodyData = taskDidSendBodyData {
taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalBytesExpectedToSend)
} else if let delegate = self[task]?.delegate as? CLDNUploadTaskDelegate {
delegate.URLSession(
session,
task: task,
didSendBodyData: bytesSent,
totalBytesSent: totalBytesSent,
totalBytesExpectedToSend: totalBytesExpectedToSend
)
}
}
#if !os(watchOS)
/// Tells the delegate that the session finished collecting metrics for the task.
///
/// - parameter session: The session collecting the metrics.
/// - parameter task: The task whose metrics have been collected.
/// - parameter metrics: The collected metrics.
@available(iOS 10.0, macOS 10.12, tvOS 10.0, *)
@objc(URLSession:task:didFinishCollectingMetrics:)
internal func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) {
self[task]?.delegate.metrics = metrics
}
#endif
/// Tells the delegate that the task finished transferring data.
///
/// - parameter session: The session containing the task whose request finished transferring data.
/// - parameter task: The task whose request finished transferring data.
/// - parameter error: If an error occurred, an error object indicating how the transfer failed, otherwise nil.
internal func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
/// Executed after it is determined that the request is not going to be retried
let completeTask: (URLSession, URLSessionTask, Error?) -> Void = { [weak self] session, task, error in
guard let strongSelf = self else { return }
strongSelf.taskDidComplete?(session, task, error)
strongSelf[task]?.delegate.urlSession(session, task: task, didCompleteWithError: error)
strongSelf[task] = nil
}
guard let request = self[task], let sessionManager = sessionManager else {
completeTask(session, task, error)
return
}
// Run all validations on the request before checking if an error occurred
request.validations.forEach { $0() }
// Determine whether an error has occurred
var error: Error? = error
if request.delegate.error != nil {
error = request.delegate.error
}
/// If an error occurred and the retrier is set, asynchronously ask the retrier if the request
/// should be retried. Otherwise, complete the task by notifying the task delegate.
if let retrier = retrier, let error = error {
retrier.CLDN_Should(sessionManager, retry: request, with: error) { [weak self] shouldRetry, timeDelay in
guard shouldRetry else { completeTask(session, task, error) ; return }
DispatchQueue.CLDNUtility.CLDN_after(timeDelay) { [weak self] in
guard let strongSelf = self else { return }
let retrySucceeded = strongSelf.sessionManager?.retry(request) ?? false
if retrySucceeded, let task = request.task {
strongSelf[task] = request
return
} else {
completeTask(session, task, error)
}
}
}
} else {
completeTask(session, task, error)
}
}
}
// MARK: - URLSessionDataDelegate
extension CLDNSessionDelegate: URLSessionDataDelegate {
/// Tells the delegate that the data task received the initial reply (headers) from the server.
///
/// - parameter session: The session containing the data task that received an initial reply.
/// - parameter dataTask: The data task that received an initial reply.
/// - parameter response: A URL response object populated with headers.
/// - parameter completionHandler: A completion handler that your code calls to continue the transfer, passing a
/// constant to indicate whether the transfer should continue as a data task or
/// should become a download task.
internal func urlSession(
_ session: URLSession,
dataTask: URLSessionDataTask,
didReceive response: URLResponse,
completionHandler: @escaping (URLSession.ResponseDisposition) -> Void)
{
guard dataTaskDidReceiveResponseWithCompletion == nil else {
dataTaskDidReceiveResponseWithCompletion?(session, dataTask, response, completionHandler)
return
}
var disposition: URLSession.ResponseDisposition = .allow
if let dataTaskDidReceiveResponse = dataTaskDidReceiveResponse {
disposition = dataTaskDidReceiveResponse(session, dataTask, response)
}
completionHandler(disposition)
}
/// Tells the delegate that the data task has received some of the expected data.
///
/// - parameter session: The session containing the data task that provided data.
/// - parameter dataTask: The data task that provided data.
/// - parameter data: A data object containing the transferred data.
internal func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
if let dataTaskDidReceiveData = dataTaskDidReceiveData {
dataTaskDidReceiveData(session, dataTask, data)
} else if let delegate = self[dataTask]?.delegate as? CLDNDataTaskDelegate {
delegate.urlSession(session, dataTask: dataTask, didReceive: data)
}
}
/// Asks the delegate whether the data (or upload) task should store the response in the cache.
///
/// - parameter session: The session containing the data (or upload) task.
/// - parameter dataTask: The data (or upload) task.
/// - parameter proposedResponse: The default caching behavior. This behavior is determined based on the current
/// caching policy and the values of certain received headers, such as the Pragma
/// and Cache-Control headers.
/// - parameter completionHandler: A block that your handler must call, providing either the original proposed
/// response, a modified version of that response, or NULL to prevent caching the
/// response. If your delegate implements this method, it must call this completion
/// handler; otherwise, your app leaks memory.
internal func urlSession(
_ session: URLSession,
dataTask: URLSessionDataTask,
willCacheResponse proposedResponse: CachedURLResponse,
completionHandler: @escaping (CachedURLResponse?) -> Void)
{
guard dataTaskWillCacheResponseWithCompletion == nil else {
dataTaskWillCacheResponseWithCompletion?(session, dataTask, proposedResponse, completionHandler)
return
}
if let dataTaskWillCacheResponse = dataTaskWillCacheResponse {
completionHandler(dataTaskWillCacheResponse(session, dataTask, proposedResponse))
} else if let delegate = self[dataTask]?.delegate as? CLDNDataTaskDelegate {
delegate.urlSession(
session,
dataTask: dataTask,
willCacheResponse: proposedResponse,
completionHandler: completionHandler
)
} else {
completionHandler(proposedResponse)
}
}
}
// MARK: - URLSessionDownloadDelegate
extension CLDNSessionDelegate: URLSessionDownloadDelegate {
/// Tells the delegate that a download task has finished downloading.
///
/// - parameter session: The session containing the download task that finished.
/// - parameter downloadTask: The download task that finished.
/// - parameter location: A file URL for the temporary file. Because the file is temporary, you must either
/// internal the file for reading or move it to a permanent location in your app’s sandbox
/// container directory before returning from this delegate method.
internal func urlSession(
_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didFinishDownloadingTo location: URL)
{
if let downloadTaskDidFinishDownloadingToURL = downloadTaskDidFinishDownloadingToURL {
downloadTaskDidFinishDownloadingToURL(session, downloadTask, location)
}
}
/// Periodically informs the delegate about the download’s progress.
///
/// - parameter session: The session containing the download task.
/// - parameter downloadTask: The download task.
/// - parameter bytesWritten: The number of bytes transferred since the last time this delegate
/// method was called.
/// - parameter totalBytesWritten: The total number of bytes transferred so far.
/// - parameter totalBytesExpectedToWrite: The expected length of the file, as provided by the Content-Length
/// header. If this header was not provided, the value is
/// `NSURLSessionTransferSizeUnknown`.
internal func urlSession(
_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didWriteData bytesWritten: Int64,
totalBytesWritten: Int64,
totalBytesExpectedToWrite: Int64)
{
if let downloadTaskDidWriteData = downloadTaskDidWriteData {
downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite)
}
}
/// Tells the delegate that the download task has resumed downloading.
///
/// - parameter session: The session containing the download task that finished.
/// - parameter downloadTask: The download task that resumed. See explanation in the discussion.
/// - parameter fileOffset: If the file's cache policy or last modified date prevents reuse of the
/// existing content, then this value is zero. Otherwise, this value is an
/// integer representing the number of bytes on disk that do not need to be
/// retrieved again.
/// - parameter expectedTotalBytes: The expected length of the file, as provided by the Content-Length header.
/// If this header was not provided, the value is NSURLSessionTransferSizeUnknown.
internal func urlSession(
_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didResumeAtOffset fileOffset: Int64,
expectedTotalBytes: Int64)
{
if let downloadTaskDidResumeAtOffset = downloadTaskDidResumeAtOffset {
downloadTaskDidResumeAtOffset(session, downloadTask, fileOffset, expectedTotalBytes)
}
}
}
================================================
FILE: Cloudinary/Classes/Core/BaseNetwork/CLDNSessionManager.swift
================================================
//
// CLDNSessionManager.swift
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/// Responsible for creating and managing `CLDNRequest` objects, as well as their underlying `NSURLSession`.
internal class CLDNSessionManager {
// MARK: - Helper Types
/// Defines whether the `CLDNMultipartFormData` encoding was successful and contains result of the encoding as
/// associated values.
///
/// - Success: Represents a successful `CLDNMultipartFormData` encoding and contains the new `CLDNUploadRequest` along with
/// streaming information.
/// - Failure: Used to represent a failure in the `CLDNMultipartFormData` encoding and also contains the encoding
/// error.
internal enum MultipartFormDataEncodingResult {
case success(request: CLDNUploadRequest, streamingFromDisk: Bool, streamFileURL: URL?)
case failure(Error)
}
// MARK: - Properties
/// A default instance of `SessionManager`, used by top-level Cloudinary request methods, and suitable for use
/// directly for any ad hoc requests.
internal static let `default`: CLDNSessionManager = {
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = CLDNSessionManager.defaultHTTPHeaders
return CLDNSessionManager(configuration: configuration)
}()
/// Creates default values for the "Accept-Encoding", "Accept-Language" and "User-Agent" headers.
internal static let defaultHTTPHeaders: CLDNHTTPHeaders = {
// Accept-Encoding HTTP Header; see https://tools.ietf.org/html/rfc7230#section-4.2.3
let acceptEncoding: String = "gzip;q=1.0, compress;q=0.5"
// Accept-Language HTTP Header; see https://tools.ietf.org/html/rfc7231#section-5.3.5
let acceptLanguage = Locale.preferredLanguages.prefix(6).enumerated().map { index, languageCode in
let quality = 1.0 - (Double(index) * 0.1)
return "\(languageCode);q=\(quality)"
}.joined(separator: ", ")
// User-Agent Header; see https://tools.ietf.org/html/rfc7231#section-5.5.3
// Example: `iOS Example/1.0 (org.cloudinary.iOS-Example; build:1; iOS 10.0.0) cloudinary/4.0.0`
let userAgent: String = {
if let info = Bundle.main.infoDictionary {
let executable = info[kCFBundleExecutableKey as String] as? String ?? "Unknown"
let bundle = info[kCFBundleIdentifierKey as String] as? String ?? "Unknown"
let appVersion = info["CFBundleShortVersionString"] as? String ?? "Unknown"
let appBuild = info[kCFBundleVersionKey as String] as? String ?? "Unknown"
let osNameVersion: String = {
let version = ProcessInfo.processInfo.operatingSystemVersion
let versionString = "\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)"
let osName: String = {
#if os(iOS)
return "iOS"
#elseif os(watchOS)
return "watchOS"
#elseif os(tvOS)
return "tvOS"
#elseif os(macOS)
return "OS X"
#elseif os(Linux)
return "Linux"
#else
return "Unknown"
#endif
}()
return "\(osName) \(versionString)"
}()
let cloudinaryVersion: String = {
guard
let afInfo = Bundle(for: CLDNSessionManager.self).infoDictionary,
let build = afInfo["CFBundleShortVersionString"]
else { return "Unknown" }
return "Cloudinary/\(build)"
}()
return "\(executable)/\(appVersion) (\(bundle); build:\(appBuild); \(osNameVersion)) \(cloudinaryVersion)"
}
return "Cloudinary"
}()
return [
"Accept-Encoding": acceptEncoding,
"Accept-Language": acceptLanguage,
"User-Agent": userAgent
]
}()
/// Default memory threshold used when encoding `MultipartFormData` in bytes.
internal static let multipartFormDataEncodingMemoryThreshold: UInt64 = 10_000_000
/// The underlying session.
internal let session: URLSession
/// The session delegate handling all the task and session delegate callbacks.
internal let delegate: CLDNSessionDelegate
/// Whether to start requests immediately after being constructed. `true` by default.
internal var startRequestsImmediately: Bool = true
/// The request adapter called each time a new request is created.
internal var adapter: CLDNRequestAdapter?
/// The request retrier called each time a request encounters an error to determine whether to retry the request.
internal var retrier: CLDNRequestRetrier? {
get { return delegate.retrier }
set { delegate.retrier = newValue }
}
/// The background completion handler closure provided by the UIApplicationDelegate
/// `application:handleEventsForBackgroundURLSession:completionHandler:` method. By setting the background
/// completion handler, the SessionDelegate `sessionDidFinishEventsForBackgroundURLSession` closure implementation
/// will automatically call the handler.
///
/// If you need to handle your own events before the handler is called, then you need to override the
/// SessionDelegate `sessionDidFinishEventsForBackgroundURLSession` and manually call the handler when finished.
///
/// `nil` by default.
internal var backgroundCompletionHandler: (() -> Void)?
let queue = DispatchQueue(label: "com.cloudinary.session-manager." + UUID().uuidString)
// MARK: - Lifecycle
/// Creates an instance with the specified `configuration`, `delegate` and `serverTrustPolicyManager`.
///
/// - parameter configuration: The configuration used to construct the managed session.
/// `URLSessionConfiguration.default` by default.
/// - parameter delegate: The delegate used when initializing the session. `CLDNSessionDelegate()` by
/// default.
/// - parameter serverTrustPolicyManager: The server trust policy manager to use for evaluating all server trust
/// challenges. `nil` by default.
///
/// - returns: The new `CLDNSessionManager` instance.
internal init(
configuration: URLSessionConfiguration = URLSessionConfiguration.default,
delegate: CLDNSessionDelegate = CLDNSessionDelegate())
{
configuration.urlCredentialStorage = nil;
self.delegate = delegate
self.session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: nil)
commonInit()
}
/// Creates an instance with the specified `session`, `delegate` and `serverTrustPolicyManager`.
///
/// - parameter session: The URL session.
/// - parameter delegate: The delegate of the URL session. Must equal the URL session's delegate.
/// - parameter serverTrustPolicyManager: The server trust policy manager to use for evaluating all server trust
/// challenges. `nil` by default.
///
/// - returns: The new `CLDNSessionManager` instance if the URL session's delegate matches; `nil` otherwise.
internal init?(
session: URLSession,
delegate: CLDNSessionDelegate)
{
guard delegate === session.delegate else { return nil }
self.delegate = delegate
self.session = session
commonInit()
}
private func commonInit() {
delegate.sessionManager = self
delegate.sessionDidFinishEventsForBackgroundURLSession = { [weak self] session in
guard let strongSelf = self else { return }
DispatchQueue.main.async { strongSelf.backgroundCompletionHandler?() }
}
}
deinit {
session.invalidateAndCancel()
}
// MARK: - Data Request
/// Creates a `CLDNDataRequest` to retrieve the contents of the specified `url`, `method`, `parameters`, `encoding`
/// and `headers`.
///
/// - parameter url: The URL.
/// - parameter method: The HTTP method. `.get` by default.
/// - parameter parameters: The parameters. `nil` by default.
/// - parameter encoding: The parameter encoding. `CLDNURLEncoding.default` by default.
/// - parameter headers: The HTTP headers. `nil` by default.
///
/// - returns: The created `CLDNDataRequest`.
@discardableResult
internal func request(
_ url: CLDNURLConvertible,
method: CLDNHTTPMethod = .get,
parameters: CLDNParameters? = nil,
encoding: CLDNParameterEncoding = CLDNURLEncoding.default,
headers: CLDNHTTPHeaders? = nil)
-> CLDNDataRequest
{
var originalRequest: URLRequest?
do {
var parameters = parameters
let timeoutParamater = parameters?.removeValue(forKey: CLDConfiguration.ConfigParam.Timeout.description)
originalRequest = try URLRequest(url: url, method: method, headers: headers)
var encodedURLRequest = try encoding.CLDN_Encode(originalRequest!, with: parameters)
if let timeout = timeoutParamater as? NSNumber {
encodedURLRequest.timeoutInterval = timeout.doubleValue
}
return request(encodedURLRequest)
} catch {
return request(originalRequest, failedWith: error)
}
}
/// Creates a `CLDNDataRequest` to retrieve the contents of a URL based on the specified `urlRequest`.
///
/// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
///
/// - parameter urlRequest: The URL request.
///
/// - returns: The created `CLDNDataRequest`.
@discardableResult
internal func request(_ urlRequest: CLDNURLRequestConvertible) -> CLDNDataRequest {
var originalRequest: URLRequest?
do {
originalRequest = try urlRequest.CLDN_AsURLRequest()
let originalTask = CLDNDataRequest.Requestable(urlRequest: originalRequest!)
let task = try originalTask.CLDN_Task(session: session, adapter: adapter, queue: queue)
let request = CLDNDataRequest(session: session, requestTask: .data(originalTask, task))
delegate[task] = request
if startRequestsImmediately { request.resume() }
return request
} catch {
return request(originalRequest, failedWith: error)
}
}
// MARK: Private - Request Implementation
private func request(_ urlRequest: URLRequest?, failedWith error: Error) -> CLDNDataRequest {
var requestTask: CLDNRequest.RequestTask = .data(nil, nil)
if let urlRequest = urlRequest {
let originalTask = CLDNDataRequest.Requestable(urlRequest: urlRequest)
requestTask = .data(originalTask, nil)
}
let underlyingError = error.underlyingAdaptError ?? error
let request = CLDNDataRequest(session: session, requestTask: requestTask, error: underlyingError)
if let retrier = retrier, error is AdaptError {
allowRetrier(retrier, toRetry: request, with: underlyingError)
} else {
if startRequestsImmediately { request.resume() }
}
return request
}
// MARK: - Upload Request
// MARK: File
/// Creates an `CLDNUploadRequest` from the specified `url`, `method` and `headers` for uploading the `file`.
///
/// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
///
/// - parameter file: The file to upload.
/// - parameter url: The URL.
/// - parameter method: The HTTP method. `.post` by default.
/// - parameter headers: The HTTP headers. `nil` by default.
///
/// - returns: The created `CLDNUploadRequest`.
@discardableResult
internal func upload(
_ fileURL: URL,
to url: CLDNURLConvertible,
method: CLDNHTTPMethod = .post,
headers: CLDNHTTPHeaders? = nil)
-> CLDNUploadRequest
{
do {
let urlRequest = try URLRequest(url: url, method: method, headers: headers)
return upload(fileURL, with: urlRequest)
} catch {
return upload(nil, failedWith: error)
}
}
/// Creates a `CLDNUploadRequest` from the specified `urlRequest` for uploading the `file`.
///
/// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
///
/// - parameter file: The file to upload.
/// - parameter urlRequest: The URL request.
///
/// - returns: The created `CLDNUploadRequest`.
@discardableResult
internal func upload(_ fileURL: URL, with urlRequest: CLDNURLRequestConvertible) -> CLDNUploadRequest {
do {
let urlRequest = try urlRequest.CLDN_AsURLRequest()
return upload(.file(fileURL, urlRequest))
} catch {
return upload(nil, failedWith: error)
}
}
// MARK: Data
/// Creates an `CLDNUploadRequest` from the specified `url`, `method` and `headers` for uploading the `data`.
///
/// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
///
/// - parameter data: The data to upload.
/// - parameter url: The URL.
/// - parameter method: The HTTP method. `.post` by default.
/// - parameter headers: The HTTP headers. `nil` by default.
///
/// - returns: The created `CLDNUploadRequest`.
@discardableResult
internal func upload(
_ data: Data,
to url: CLDNURLConvertible,
method: CLDNHTTPMethod = .post,
headers: CLDNHTTPHeaders? = nil)
-> CLDNUploadRequest
{
do {
let urlRequest = try URLRequest(url: url, method: method, headers: headers)
return upload(data, with: urlRequest)
} catch {
return upload(nil, failedWith: error)
}
}
/// Creates an `CLDNUploadRequest` from the specified `urlRequest` for uploading the `data`.
///
/// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
///
/// - parameter data: The data to upload.
/// - parameter urlRequest: The URL request.
///
/// - returns: The created `CLDNUploadRequest`.
@discardableResult
internal func upload(_ data: Data, with urlRequest: CLDNURLRequestConvertible) -> CLDNUploadRequest {
do {
let urlRequest = try urlRequest.CLDN_AsURLRequest()
return upload(.data(data, urlRequest))
} catch {
return upload(nil, failedWith: error)
}
}
// MARK: InputStream
/// Creates an `CLDNUploadRequest` from the specified `url`, `method` and `headers` for uploading the `stream`.
///
/// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
///
/// - parameter stream: The stream to upload.
/// - parameter url: The URL.
/// - parameter method: The HTTP method. `.post` by default.
/// - parameter headers: The HTTP headers. `nil` by default.
///
/// - returns: The created `CLDNUploadRequest`.
@discardableResult
internal func upload(
_ stream: InputStream,
to url: CLDNURLConvertible,
method: CLDNHTTPMethod = .post,
headers: CLDNHTTPHeaders? = nil)
-> CLDNUploadRequest
{
do {
let urlRequest = try URLRequest(url: url, method: method, headers: headers)
return upload(stream, with: urlRequest)
} catch {
return upload(nil, failedWith: error)
}
}
/// Creates an `CLDNUploadRequest` from the specified `urlRequest` for uploading the `stream`.
///
/// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
///
/// - parameter stream: The stream to upload.
/// - parameter urlRequest: The URL request.
///
/// - returns: The created `CLDNUploadRequest`.
@discardableResult
internal func upload(_ stream: InputStream, with urlRequest: CLDNURLRequestConvertible) -> CLDNUploadRequest {
do {
let urlRequest = try urlRequest.CLDN_AsURLRequest()
return upload(.stream(stream, urlRequest))
} catch {
return upload(nil, failedWith: error)
}
}
// MARK: CLDNMultipartFormData
/// Encodes `multipartFormData` using `encodingMemoryThreshold` and calls `encodingCompletion` with new
/// `CLDNUploadRequest` using the `url`, `method` and `headers`.
///
/// It is important to understand the memory implications of uploading `CLDNMultipartFormData`. If the cummulative
/// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most
/// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to
/// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory
/// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be
/// used for larger payloads such as video content.
///
/// The `encodingMemoryThreshold` parameter allows Cloudinary to automatically determine whether to encode in-memory
/// or stream from disk. If the content length of the `CLDNMultipartFormData` is below the `encodingMemoryThreshold`,
/// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk
/// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding
/// technique was used.
///
/// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
///
/// - parameter multipartFormData: The closure used to append body parts to the `CLDNMultipartFormData`.
/// - parameter encodingMemoryThreshold: The encoding memory threshold in bytes.
/// `multipartFormDataEncodingMemoryThreshold` by default.
/// - parameter url: The URL.
/// - parameter method: The HTTP method. `.post` by default.
/// - parameter headers: The HTTP headers. `nil` by default.
/// - parameter encodingCompletion: The closure called when the `CLDNMultipartFormData` encoding is complete.
internal func upload(
multipartFormData: @escaping (CLDNMultipartFormData) -> Void,
usingThreshold encodingMemoryThreshold: UInt64 = CLDNSessionManager.multipartFormDataEncodingMemoryThreshold,
to url: CLDNURLConvertible,
method: CLDNHTTPMethod = .post,
headers: CLDNHTTPHeaders? = nil,
queue: DispatchQueue? = nil,
timeout: NSNumber? = nil,
encodingCompletion: ((MultipartFormDataEncodingResult) -> Void)?)
{
do {
var urlRequest = try URLRequest(url: url, method: method, headers: headers)
if let timeout = timeout {
urlRequest.timeoutInterval = timeout.doubleValue
}
return upload(
multipartFormData: multipartFormData,
usingThreshold: encodingMemoryThreshold,
with: urlRequest,
queue: queue,
encodingCompletion: encodingCompletion
)
} catch {
(queue ?? DispatchQueue.main).async { encodingCompletion?(.failure(error)) }
}
}
/// Encodes `multipartFormData` using `encodingMemoryThreshold` and calls `encodingCompletion` with new
/// `CLDNUploadRequest` using the `urlRequest`.
///
/// It is important to understand the memory implications of uploading `CLDNMultipartFormData`. If the cummulative
/// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most
/// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to
/// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory
/// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be
/// used for larger payloads such as video content.
///
/// The `encodingMemoryThreshold` parameter allows Cloudinary to automatically determine whether to encode in-memory
/// or stream from disk. If the content length of the `CLDNMultipartFormData` is below the `encodingMemoryThreshold`,
/// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk
/// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding
/// technique was used.
///
/// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
///
/// - parameter multipartFormData: The closure used to append body parts to the `CLDNMultipartFormData`.
/// - parameter encodingMemoryThreshold: The encoding memory threshold in bytes.
/// `multipartFormDataEncodingMemoryThreshold` by default.
/// - parameter urlRequest: The URL request.
/// - parameter encodingCompletion: The closure called when the `CLDNMultipartFormData` encoding is complete.
internal func upload(
multipartFormData: @escaping (CLDNMultipartFormData) -> Void,
usingThreshold encodingMemoryThreshold: UInt64 = CLDNSessionManager.multipartFormDataEncodingMemoryThreshold,
with urlRequest: CLDNURLRequestConvertible,
queue: DispatchQueue? = nil,
encodingCompletion: ((MultipartFormDataEncodingResult) -> Void)?)
{
DispatchQueue.global(qos: .utility).async {
let formData = CLDNMultipartFormData()
multipartFormData(formData)
var tempFileURL: URL?
do {
var urlRequestWithContentType = try urlRequest.CLDN_AsURLRequest()
urlRequestWithContentType.setValue(formData.contentType, forHTTPHeaderField: "Content-Type")
let isBackgroundSession = self.session.configuration.identifier != nil
if formData.contentLength < encodingMemoryThreshold && !isBackgroundSession {
let data = try formData.encode()
let encodingResult = MultipartFormDataEncodingResult.success(
request: self.upload(data, with: urlRequestWithContentType),
streamingFromDisk: false,
streamFileURL: nil
)
(queue ?? DispatchQueue.main).async { encodingCompletion?(encodingResult) }
} else {
let fileManager = FileManager.default
let tempDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory())
let directoryURL = tempDirectoryURL.appendingPathComponent("org.cloudinary.manager/multipart.form.data")
let fileName = UUID().uuidString
let fileURL = directoryURL.appendingPathComponent(fileName)
tempFileURL = fileURL
var directoryError: Error?
// Create directory inside serial queue to ensure two threads don't do this in parallel
self.queue.sync {
do {
try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil)
} catch {
directoryError = error
}
}
if let directoryError = directoryError { throw directoryError }
try formData.writeEncodedData(to: fileURL)
let upload = self.upload(fileURL, with: urlRequestWithContentType)
// Cleanup the temp file once the upload is complete
upload.delegate.queue.addOperation {
do {
try FileManager.default.removeItem(at: fileURL)
} catch {
// No-op
}
}
(queue ?? DispatchQueue.main).async {
let encodingResult = MultipartFormDataEncodingResult.success(
request: upload,
streamingFromDisk: true,
streamFileURL: fileURL
)
encodingCompletion?(encodingResult)
}
}
} catch {
// Cleanup the temp file in the event that the multipart form data encoding failed
if let tempFileURL = tempFileURL {
do {
try FileManager.default.removeItem(at: tempFileURL)
} catch {
// No-op
}
}
(queue ?? DispatchQueue.main).async { encodingCompletion?(.failure(error)) }
}
}
}
// MARK: Private - Upload Implementation
private func upload(_ uploadable: CLDNUploadRequest.Uploadable) -> CLDNUploadRequest {
do {
let task = try uploadable.CLDN_Task(session: session, adapter: adapter, queue: queue)
let upload = CLDNUploadRequest(session: session, requestTask: .upload(uploadable, task))
if case let .stream(inputStream, _) = uploadable {
upload.delegate.taskNeedNewBodyStream = { _, _ in inputStream }
}
delegate[task] = upload
if startRequestsImmediately { upload.resume() }
return upload
} catch {
return upload(uploadable, failedWith: error)
}
}
private func upload(_ uploadable: CLDNUploadRequest.Uploadable?, failedWith error: Error) -> CLDNUploadRequest {
var uploadTask: CLDNRequest.RequestTask = .upload(nil, nil)
if let uploadable = uploadable {
uploadTask = .upload(uploadable, nil)
}
let underlyingError = error.underlyingAdaptError ?? error
let upload = CLDNUploadRequest(session: session, requestTask: uploadTask, error: underlyingError)
if let retrier = retrier, error is AdaptError {
allowRetrier(retrier, toRetry: upload, with: underlyingError)
} else {
if startRequestsImmediately { upload.resume() }
}
return upload
}
// MARK: - Internal - Retry Request
func retry(_ request: CLDNRequest) -> Bool {
guard let originalTask = request.originalTask else { return false }
do {
let task = try originalTask.CLDN_Task(session: session, adapter: adapter, queue: queue)
if let originalTask = request.task {
delegate[originalTask] = nil // removes the old request to avoid endless growth
}
request.delegate.task = task // resets all task delegate data
request.retryCount += 1
request.startTime = CFAbsoluteTimeGetCurrent()
request.endTime = nil
task.resume()
return true
} catch {
request.delegate.error = error.underlyingAdaptError ?? error
return false
}
}
private func allowRetrier(_ retrier: CLDNRequestRetrier, toRetry request: CLDNRequest, with error: Error) {
DispatchQueue.CLDNUtility.async { [weak self] in
guard let strongSelf = self else { return }
retrier.CLDN_Should(strongSelf, retry: request, with: error) { shouldRetry, timeDelay in
guard let strongSelf = self else { return }
guard shouldRetry else {
if strongSelf.startRequestsImmediately { request.resume() }
return
}
DispatchQueue.CLDNUtility.CLDN_after(timeDelay) {
guard let strongSelf = self else { return }
let retrySucceeded = strongSelf.retry(request)
if retrySucceeded, let task = request.task {
strongSelf.delegate[task] = request
} else {
if strongSelf.startRequestsImmediately { request.resume() }
}
}
}
}
}
}
================================================
FILE: Cloudinary/Classes/Core/BaseNetwork/CLDNTaskDelegate.swift
================================================
//
// CLDNTaskDelegate.swift
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/// The task delegate is responsible for handling all delegate callbacks for the underlying task as well as
/// executing all operations attached to the serial operation queue upon task completion.
internal class CLDNTaskDelegate: NSObject {
// MARK: Properties
/// The serial operation queue used to execute all operations after the task completes.
internal let queue: OperationQueue
/// The data returned by the server.
internal var data: Data? { return nil }
/// The error generated throughout the lifecyle of the task.
internal var error: Error?
var task: URLSessionTask? {
set {
taskLock.lock(); defer { taskLock.unlock() }
_task = newValue
}
get {
taskLock.lock(); defer { taskLock.unlock() }
return _task
}
}
var initialResponseTime: CFAbsoluteTime?
var credential: URLCredential?
var metrics: AnyObject? // URLSessionTaskMetrics
private var _task: URLSessionTask? {
didSet { reset() }
}
private let taskLock = NSLock()
// MARK: Lifecycle
init(task: URLSessionTask?) {
_task = task
self.queue = {
let operationQueue = OperationQueue()
operationQueue.name = "com.cloudinary.CLDNTaskDelegateOperationQueue"
operationQueue.maxConcurrentOperationCount = 1
operationQueue.isSuspended = true
operationQueue.qualityOfService = .utility
return operationQueue
}()
}
func reset() {
error = nil
initialResponseTime = nil
}
// MARK: URLSessionTaskDelegate
var taskWillPerformHTTPRedirection: ((URLSession, URLSessionTask, HTTPURLResponse, URLRequest) -> URLRequest?)?
var taskDidReceiveChallenge: ((URLSession, URLSessionTask, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?))?
var taskNeedNewBodyStream: ((URLSession, URLSessionTask) -> InputStream?)?
var taskDidCompleteWithError: ((URLSession, URLSessionTask, Error?) -> Void)?
@objc(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)
func urlSession(
_ session: URLSession,
task: URLSessionTask,
willPerformHTTPRedirection response: HTTPURLResponse,
newRequest request: URLRequest,
completionHandler: @escaping (URLRequest?) -> Void)
{
var redirectRequest: URLRequest? = request
if let taskWillPerformHTTPRedirection = taskWillPerformHTTPRedirection {
redirectRequest = taskWillPerformHTTPRedirection(session, task, response, request)
}
completionHandler(redirectRequest)
}
@objc(URLSession:task:didReceiveChallenge:completionHandler:)
func urlSession(
_ session: URLSession,
task: URLSessionTask,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
{
var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling
var credential: URLCredential?
if let taskDidReceiveChallenge = taskDidReceiveChallenge {
(disposition, credential) = taskDidReceiveChallenge(session, task, challenge)
} else {
if challenge.previousFailureCount > 0 {
disposition = .rejectProtectionSpace
} else {
credential = self.credential ?? session.configuration.urlCredentialStorage?.defaultCredential(for: challenge.protectionSpace)
if credential != nil {
disposition = .useCredential
}
}
}
completionHandler(disposition, credential)
}
@objc(URLSession:task:needNewBodyStream:)
func urlSession(
_ session: URLSession,
task: URLSessionTask,
needNewBodyStream completionHandler: @escaping (InputStream?) -> Void)
{
var bodyStream: InputStream?
if let taskNeedNewBodyStream = taskNeedNewBodyStream {
bodyStream = taskNeedNewBodyStream(session, task)
}
completionHandler(bodyStream)
}
@objc(URLSession:task:didCompleteWithError:)
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let taskDidCompleteWithError = taskDidCompleteWithError {
taskDidCompleteWithError(session, task, error)
} else {
if let error = error {
if self.error == nil { self.error = error }
}
queue.isSuspended = false
}
}
}
// MARK: -
class CLDNDataTaskDelegate: CLDNTaskDelegate, URLSessionDataDelegate {
// MARK: Properties
var dataTask: URLSessionDataTask { return task as! URLSessionDataTask }
override var data: Data? {
if dataStream != nil {
return nil
} else {
return mutableData
}
}
var progress: Progress
var progressHandler: (closure: CLDNRequest.ProgressHandler, queue: DispatchQueue)?
var dataStream: ((_ data: Data) -> Void)?
private var totalBytesReceived: Int64 = 0
private var mutableData: Data
private var expectedContentLength: Int64?
// MARK: Lifecycle
override init(task: URLSessionTask?) {
mutableData = Data()
progress = Progress(totalUnitCount: 0)
super.init(task: task)
}
override func reset() {
super.reset()
progress = Progress(totalUnitCount: 0)
totalBytesReceived = 0
mutableData = Data()
expectedContentLength = nil
}
// MARK: URLSessionDataDelegate
var dataTaskDidReceiveResponse: ((URLSession, URLSessionDataTask, URLResponse) -> URLSession.ResponseDisposition)?
var dataTaskDidBecomeDownloadTask: ((URLSession, URLSessionDataTask, URLSessionDownloadTask) -> Void)?
var dataTaskDidReceiveData: ((URLSession, URLSessionDataTask, Data) -> Void)?
var dataTaskWillCacheResponse: ((URLSession, URLSessionDataTask, CachedURLResponse) -> CachedURLResponse?)?
func urlSession(
_ session: URLSession,
dataTask: URLSessionDataTask,
didReceive response: URLResponse,
completionHandler: @escaping (URLSession.ResponseDisposition) -> Void)
{
var disposition: URLSession.ResponseDisposition = .allow
expectedContentLength = response.expectedContentLength
if let dataTaskDidReceiveResponse = dataTaskDidReceiveResponse {
disposition = dataTaskDidReceiveResponse(session, dataTask, response)
}
completionHandler(disposition)
}
func urlSession(
_ session: URLSession,
dataTask: URLSessionDataTask,
didBecome downloadTask: URLSessionDownloadTask)
{
dataTaskDidBecomeDownloadTask?(session, dataTask, downloadTask)
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
if let dataTaskDidReceiveData = dataTaskDidReceiveData {
dataTaskDidReceiveData(session, dataTask, data)
} else {
if let dataStream = dataStream {
dataStream(data)
} else {
mutableData.append(data)
}
let bytesReceived = Int64(data.count)
totalBytesReceived += bytesReceived
let totalBytesExpected = dataTask.response?.expectedContentLength ?? NSURLSessionTransferSizeUnknown
progress.totalUnitCount = totalBytesExpected
progress.completedUnitCount = totalBytesReceived
if let progressHandler = progressHandler {
progressHandler.queue.async { progressHandler.closure(self.progress) }
}
}
}
func urlSession(
_ session: URLSession,
dataTask: URLSessionDataTask,
willCacheResponse proposedResponse: CachedURLResponse,
completionHandler: @escaping (CachedURLResponse?) -> Void)
{
var cachedResponse: CachedURLResponse? = proposedResponse
if let dataTaskWillCacheResponse = dataTaskWillCacheResponse {
cachedResponse = dataTaskWillCacheResponse(session, dataTask, proposedResponse)
}
completionHandler(cachedResponse)
}
}
// MARK: -
class CLDNUploadTaskDelegate: CLDNDataTaskDelegate {
// MARK: Properties
var uploadTask: URLSessionUploadTask { return task as! URLSessionUploadTask }
var uploadProgress: Progress
var uploadProgressHandler: (closure: CLDNRequest.ProgressHandler, queue: DispatchQueue)?
// MARK: Lifecycle
override init(task: URLSessionTask?) {
uploadProgress = Progress(totalUnitCount: 0)
super.init(task: task)
}
override func reset() {
super.reset()
uploadProgress = Progress(totalUnitCount: 0)
}
// MARK: URLSessionTaskDelegate
var taskDidSendBodyData: ((URLSession, URLSessionTask, Int64, Int64, Int64) -> Void)?
func URLSession(
_ session: URLSession,
task: URLSessionTask,
didSendBodyData bytesSent: Int64,
totalBytesSent: Int64,
totalBytesExpectedToSend: Int64)
{
if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
if let taskDidSendBodyData = taskDidSendBodyData {
taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalBytesExpectedToSend)
} else {
uploadProgress.totalUnitCount = totalBytesExpectedToSend
uploadProgress.completedUnitCount = totalBytesSent
if let uploadProgressHandler = uploadProgressHandler {
uploadProgressHandler.queue.async { uploadProgressHandler.closure(self.uploadProgress) }
}
}
}
}
================================================
FILE: Cloudinary/Classes/Core/BaseNetwork/CLDNTimeline.swift
================================================
//
// CLDNTimeline.swift
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/// Responsible for computing the timing metrics for the complete lifecycle of a `CLDNRequest`.
internal struct CLDNTimeline {
/// The time the request was initialized.
internal let requestStartTime: CFAbsoluteTime
/// The time the first bytes were received from or sent to the server.
internal let initialResponseTime: CFAbsoluteTime
/// The time when the request was completed.
internal let requestCompletedTime: CFAbsoluteTime
/// The time when the response serialization was completed.
internal let serializationCompletedTime: CFAbsoluteTime
/// The time interval in seconds from the time the request started to the initial response from the server.
internal let latency: TimeInterval
/// The time interval in seconds from the time the request started to the time the request completed.
internal let requestDuration: TimeInterval
/// The time interval in seconds from the time the request completed to the time response serialization completed.
internal let serializationDuration: TimeInterval
/// The time interval in seconds from the time the request started to the time response serialization completed.
internal let totalDuration: TimeInterval
/// Creates a new `CLDNTimeline` instance with the specified request times.
///
/// - parameter requestStartTime: The time the request was initialized. Defaults to `0.0`.
/// - parameter initialResponseTime: The time the first bytes were received from or sent to the server.
/// Defaults to `0.0`.
/// - parameter requestCompletedTime: The time when the request was completed. Defaults to `0.0`.
/// - parameter serializationCompletedTime: The time when the response serialization was completed. Defaults
/// to `0.0`.
///
/// - returns: The new `CLDNTimeline` instance.
internal init(
requestStartTime: CFAbsoluteTime = 0.0,
initialResponseTime: CFAbsoluteTime = 0.0,
requestCompletedTime: CFAbsoluteTime = 0.0,
serializationCompletedTime: CFAbsoluteTime = 0.0)
{
self.requestStartTime = requestStartTime
self.initialResponseTime = initialResponseTime
self.requestCompletedTime = requestCompletedTime
self.serializationCompletedTime = serializationCompletedTime
self.latency = initialResponseTime - requestStartTime
self.requestDuration = requestCompletedTime - requestStartTime
self.serializationDuration = serializationCompletedTime - requestCompletedTime
self.totalDuration = serializationCompletedTime - requestStartTime
}
}
// MARK: - CustomStringConvertible
extension CLDNTimeline: CustomStringConvertible {
/// The textual representation used when written to an output stream, which includes the latency, the request
/// duration and the total duration.
internal var description: String {
let latency = String(format: "%.3f", self.latency)
let requestDuration = String(format: "%.3f", self.requestDuration)
let serializationDuration = String(format: "%.3f", self.serializationDuration)
let totalDuration = String(format: "%.3f", self.totalDuration)
// NOTE: Had to move to string concatenation due to memory leak filed as rdar://26761490. Once memory leak is
// fixed, we should move back to string interpolation by reverting commit 7d4a43b1.
let timings = [
"\"Latency\": " + latency + " secs",
"\"Request Duration\": " + requestDuration + " secs",
"\"Serialization Duration\": " + serializationDuration + " secs",
"\"Total Duration\": " + totalDuration + " secs"
]
return "Timeline: { " + timings.joined(separator: ", ") + " }"
}
}
// MARK: - CustomDebugStringConvertible
extension CLDNTimeline: CustomDebugStringConvertible {
/// The textual representation used when written to an output stream, which includes the request start time, the
/// initial response time, the request completed time, the serialization completed time, the latency, the request
/// duration and the total duration.
internal var debugDescription: String {
let requestStartTime = String(format: "%.3f", self.requestStartTime)
let initialResponseTime = String(format: "%.3f", self.initialResponseTime)
let requestCompletedTime = String(format: "%.3f", self.requestCompletedTime)
let serializationCompletedTime = String(format: "%.3f", self.serializationCompletedTime)
let latency = String(format: "%.3f", self.latency)
let requestDuration = String(format: "%.3f", self.requestDuration)
let serializationDuration = String(format: "%.3f", self.serializationDuration)
let totalDuration = String(format: "%.3f", self.totalDuration)
// NOTE: Had to move to string concatenation due to memory leak filed as rdar://26761490. Once memory leak is
// fixed, we should move back to string interpolation by reverting commit 7d4a43b1.
let timings = [
"\"Request Start Time\": " + requestStartTime,
"\"Initial Response Time\": " + initialResponseTime,
"\"Request Completed Time\": " + requestCompletedTime,
"\"Serialization Completed Time\": " + serializationCompletedTime,
"\"Latency\": " + latency + " secs",
"\"Request Duration\": " + requestDuration + " secs",
"\"Serialization Duration\": " + serializationDuration + " secs",
"\"Total Duration\": " + totalDuration + " secs"
]
return "Timeline: { " + timings.joined(separator: ", ") + " }"
}
}
================================================
FILE: Cloudinary/Classes/Core/BaseNetwork/CLDNValidation.swift
================================================
//
// CLDNValidation.swift
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
extension CLDNRequest {
// MARK: Helper Types
fileprivate typealias ErrorReason = CLDNError.ResponseValidationFailureReason
/// Used to represent whether validation was successful or encountered an error resulting in a failure.
///
/// - success: The validation was successful.
/// - failure: The validation failed encountering the provided error.
internal enum ValidationResult {
case success
case failure(Error)
}
fileprivate struct MIMEType {
let type: String
let subtype: String
var isWildcard: Bool { return type == "*" && subtype == "*" }
init?(_ string: String) {
let components: [String] = {
let stripped = string.trimmingCharacters(in: .whitespacesAndNewlines)
#if swift(>=3.2)
let split = stripped[..<(stripped.range(of: ";")?.lowerBound ?? stripped.endIndex)]
#else
let split = stripped.substring(to: stripped.range(of: ";")?.lowerBound ?? stripped.endIndex)
#endif
return split.components(separatedBy: "/")
}()
if let type = components.first, let subtype = components.last {
self.type = type
self.subtype = subtype
} else {
return nil
}
}
func matches(_ mime: MIMEType) -> Bool {
switch (type, subtype) {
case (mime.type, mime.subtype), (mime.type, "*"), ("*", mime.subtype), ("*", "*"):
return true
default:
return false
}
}
}
// MARK: Properties
internal var acceptableStatusCodes: [Int] { return Array(200..<300) }
fileprivate var acceptableContentTypes: [String] {
if let accept = request?.value(forHTTPHeaderField: "Accept") {
return accept.components(separatedBy: ",")
}
return ["*/*"]
}
// MARK: Status Code
fileprivate func validate(
statusCode acceptableStatusCodes: S,
response: HTTPURLResponse)
-> ValidationResult
where S.Iterator.Element == Int
{
if acceptableStatusCodes.contains(response.statusCode) {
return .success
} else {
let reason: ErrorReason = .unacceptableStatusCode(code: response.statusCode)
return .failure(CLDNError.responseValidationFailed(reason: reason))
}
}
// MARK: Content Type
fileprivate func validate(
contentType acceptableContentTypes: S,
response: HTTPURLResponse,
data: Data?)
-> ValidationResult
where S.Iterator.Element == String
{
guard let data = data, data.count > 0 else { return .success }
guard
let responseContentType = response.mimeType,
let responseMIMEType = MIMEType(responseContentType)
else {
for contentType in acceptableContentTypes {
if let mimeType = MIMEType(contentType), mimeType.isWildcard {
return .success
}
}
let error: CLDNError = {
let reason: ErrorReason = .missingContentType(acceptableContentTypes: Array(acceptableContentTypes))
return CLDNError.responseValidationFailed(reason: reason)
}()
return .failure(error)
}
for contentType in acceptableContentTypes {
if let acceptableMIMEType = MIMEType(contentType), acceptableMIMEType.matches(responseMIMEType) {
return .success
}
}
let error: CLDNError = {
let reason: ErrorReason = .unacceptableContentType(
acceptableContentTypes: Array(acceptableContentTypes),
responseContentType: responseContentType
)
return CLDNError.responseValidationFailed(reason: reason)
}()
return .failure(error)
}
}
// MARK: -
extension CLDNDataRequest {
/// A closure used to validate a request that takes a URL request, a URL response and data, and returns whether the
/// request was valid.
internal typealias Validation = (URLRequest?, HTTPURLResponse, Data?) -> ValidationResult
/// Validates the request, using the specified closure.
///
/// If validation fails, subsequent calls to response handlers will have an associated error.
///
/// - parameter validation: A closure to validate the request.
///
/// - returns: The request.
@discardableResult
internal func validate(_ validation: @escaping Validation) -> Self {
let validationExecution: () -> Void = { [unowned self] in
if
let response = self.response,
self.delegate.error == nil,
case let .failure(error) = validation(self.request, response, self.delegate.data)
{
self.delegate.error = error
}
}
validations.append(validationExecution)
return self
}
/// Validates that the response has a status code in the specified sequence.
///
/// If validation fails, subsequent calls to response handlers will have an associated error.
///
/// - parameter range: The range of acceptable status codes.
///
/// - returns: The request.
@discardableResult
internal func validate(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int {
return validate { [unowned self] _, response, _ in
return self.validate(statusCode: acceptableStatusCodes, response: response)
}
}
/// Validates that the response has a content type in the specified sequence.
///
/// If validation fails, subsequent calls to response handlers will have an associated error.
///
/// - parameter contentType: The acceptable content types, which may specify wildcard types and/or subtypes.
///
/// - returns: The request.
@discardableResult
internal func validate(contentType acceptableContentTypes: S) -> Self where S.Iterator.Element == String {
return validate { [unowned self] _, response, data in
return self.validate(contentType: acceptableContentTypes, response: response, data: data)
}
}
/// Validates that the response has a status code in the default acceptable range of 200...299, and that the content
/// type matches any specified in the Accept HTTP header field.
///
/// If validation fails, subsequent calls to response handlers will have an associated error.
///
/// - returns: The request.
@discardableResult
internal func validate() -> Self {
let contentTypes = { [unowned self] in
self.acceptableContentTypes
}
return validate(statusCode: acceptableStatusCodes).validate(contentType: contentTypes())
}
}
================================================
FILE: Cloudinary/Classes/Core/BaseNetwork/DispatchQueue+Cloudinary.swift
================================================
//
// DispatchQueue+Cloudinary.swift
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Dispatch
import Foundation
extension DispatchQueue {
static var CLDNUserInteractive: DispatchQueue { return DispatchQueue.global(qos: .userInteractive) }
static var CLDNUserInitiated: DispatchQueue { return DispatchQueue.global(qos: .userInitiated) }
static var CLDNUtility: DispatchQueue { return DispatchQueue.global(qos: .utility) }
static var CLDNBackground: DispatchQueue { return DispatchQueue.global(qos: .background) }
func CLDN_after(_ delay: TimeInterval, execute closure: @escaping () -> Void) {
asyncAfter(deadline: .now() + delay, execute: closure)
}
}
================================================
FILE: Cloudinary/Classes/Core/CLDAnalytics.swift
================================================
//
// CLDAnalytics.swift
// Cloudinary
//
// Created by Adi Mizrahi on 13/07/2022.
//
import Foundation
import UIKit
@objcMembers open class CLDAnalytics: NSObject {
private final let ALGO_VERSION = "D"
private final let PRODUCT = "A"
private final let SDK = "E"
private final let OS_TYPE = "B"
private final let ERROR_SIGNATURE = "E"
private var sdkVersion: String? = nil
private var techVersion: String? = nil
private var osVersion: String? = nil
private var osType: String? = nil
private var featureFlag = "0"
public init(sdkVersion: String? = nil, techVersion: String? = nil, osType: String? = "B", osVersion: String? = nil, featureFlag: String? = nil) {
super.init()
self.sdkVersion = sdkVersion ?? CLDNetworkCoordinator.getVersion()
self.techVersion = techVersion ?? getiOSVersion()
self.osType = osType
self.osVersion = osVersion ?? getiOSVersion()
self.featureFlag = featureFlag ?? "0"
}
public func generateAnalyticsSignature(sdkVersion: String? = nil, techVersion: String? = nil, osType: String? = "B", osVersion: String? = nil, featureFlag: String? = nil) -> String {
let sdkVersion = sdkVersion ?? self.sdkVersion
let techVersion = techVersion ?? self.techVersion
let osType = osType ?? self.osType
let osVersion = osVersion ?? self.osVersion
let featureFlag = featureFlag ?? self.featureFlag
let swiftVersionArray = techVersion!.split(usingRegex: "\\.|\\-")
guard swiftVersionArray.count > 1, let techVersionString = generateVersionString(major: String(swiftVersionArray[0]), minor: String(swiftVersionArray[1]), patch: "") else {
return ERROR_SIGNATURE
}
let osVersionArray = osVersion!.split(usingRegex: "\\.|\\-")
guard let osVersionString = generateOSVersionString(major: String(osVersionArray[0]), minor: String(osVersionArray[1])) else {
return ERROR_SIGNATURE
}
let sdkVersionArray = sdkVersion!.split(usingRegex: "\\.|\\-")
guard sdkVersionArray.count > 1, let sdkVersionString = generateVersionString(major: String(sdkVersionArray[0]), minor: String(sdkVersionArray[1]), patch: String(sdkVersionArray[2])) else {
return ERROR_SIGNATURE
}
return "\(ALGO_VERSION)\(PRODUCT)\(SDK)\(sdkVersionString)\(techVersionString)\(osType ?? OS_TYPE)\(osVersionString)\(featureFlag)"
}
public func setSDKVersion(version: String) {
sdkVersion = version
}
public func setTechVersion(version: String) {
techVersion = version
}
public func setOsVersion(version: String) {
osVersion = version
}
public func setFeatureFlag(flag: String? = nil) {
guard let flag = flag else {
featureFlag = "0"
return
}
featureFlag = flag
}
private func generateOSVersionString(major: String, minor: String) -> String? {
let majorVersionString = major.leftPadding(toLength: 2, withPad: "0")
let minorVersionString = minor.leftPadding(toLength: 2, withPad: "0")
guard let majorDoubleValue = Int(majorVersionString), let minorDoubleValue = Int(minorVersionString) else {
return nil
}
let majorString = String(majorDoubleValue, radix: 2)
let minorString = String(minorDoubleValue, radix: 2)
let majorStr = majorString.toAnalyticsVersionStr()
let minorStr = minorString.toAnalyticsVersionStr()
return "\(majorStr)\(minorStr)"
}
private func generateVersionString(major: String, minor: String, patch: String) -> String? {
let versionString = (patch.leftPadding(toLength: 2, withPad: "0") + minor.leftPadding(toLength: 2, withPad: "0") + major.leftPadding(toLength: 2, withPad: "0"))
guard let doubleValue = Int(versionString) else {
return nil
}
let hexString = String(doubleValue, radix: 2).leftPadding(toLength: 18, withPad: "0")
var patchStr = ""
if (!patch.isEmpty) {
patchStr = String(hexString[hexString.startIndex.. String {
return UIDevice.current.systemVersion
}
}
extension String {
func split(usingRegex pattern: String) -> [String] {
//### Crashes when you pass invalid `pattern`
let regex = try! NSRegularExpression(pattern: pattern)
let matches = regex.matches(in: self, range: NSRange(0.. ()
public typealias CLDAssetCompletionHandler = (_ responseAsset: Data?, _ error: NSError?) -> ()
public typealias CLDUploadCompletionHandler = (_ response: CLDUploadResult?, _ error: NSError?) -> ()
@objcMembers open class CLDCloudinary: NSObject {
/**
Holds the configuration parameters to be used by the `CLDCloudinary` instance.
*/
open fileprivate(set) var config: CLDConfiguration
/**
The network coordinator coordinates between the SDK's API level classes to its network adapter layer.
*/
fileprivate var networkCoordinator: CLDNetworkCoordinator
/**
The network download coordinator coordinates between the SDK's API level classes to its network adapter layer.
*/
fileprivate var downloadCoordinator: CLDDownloadCoordinator
// MARK: - SDK Configurations
// MARK: Log Level
/**
Sets Cloudinary SDK's log level, default level is set to **None**.
*/
public static var logLevel: CLDLogLevel {
get {
return CLDLogManager.minimumLogLevel
}
set {
CLDLogManager.minimumLogLevel = newValue
}
}
// MARK: Image Cache
open var enableUrlCache: Bool {
get {
return CLDDownloadCoordinator.enableCache
}
set {
CLDDownloadCoordinator.enableCache = newValue
}
}
/**
Sets Cloudinary SDK's image cache maximum disk capacity.
default is 150 MB.
*/
open var cacheMaxDiskCapacity: Int {
get {
return CLDDownloadCoordinator.urlCache.diskCapacity
}
set {
CLDDownloadCoordinator.urlCache.diskCapacity = newValue
}
}
/**
Sets Cloudinary SDK's image cache maximum memory total cost.
default is 30 MB.
*/
open var cacheMaxMemoryTotalCost: Int {
get {
return CLDDownloadCoordinator.urlCache.memoryCapacity
}
set {
CLDDownloadCoordinator.urlCache.memoryCapacity = newValue
}
}
// MARK: - Init
/**
Initializes the `CLDCloudinary` instance with the specified configuration and network adapter.
- parameter configuration: The configuration used by this CLDCloudinary instance.
- parameter networkAdapter: A network adapter that implements `CLDNetworkAdapter`. CLDNetworkDelegate() by default.
- returns: The new `CLDCloudinary` instance.
*/
public convenience init(configuration: CLDConfiguration, networkAdapter: CLDNetworkAdapter? = nil, sessionConfiguration: URLSessionConfiguration? = nil) {
self.init(configuration: configuration, networkAdapter: networkAdapter, downloadAdapter: nil, sessionConfiguration: sessionConfiguration, downloadSessionConfiguration: nil)
}
/**
Initializes the `CLDCloudinary` instance with the specified configuration and network adapter.
- parameter configuration: The configuration used by this CLDCloudinary instance.
- parameter networkAdapter: A network adapter that implements `CLDNetworkAdapter`.
- parameter downloadAdapter: A download adapter that implements `CLDNetworkAdapter`.
- parameter sessionConfiguration: A session configuration that implements `URLSessionConfiguration`.
- parameter downloadSessionConfiguration: A download session configuration that implements `URLSessionConfiguration`.
CLDNetworkDelegate() by default.
- returns: The new `CLDCloudinary` instance.
*/
public init(configuration: CLDConfiguration, networkAdapter: CLDNetworkAdapter? = nil, downloadAdapter: CLDNetworkAdapter? = nil, sessionConfiguration: URLSessionConfiguration? = nil, downloadSessionConfiguration: URLSessionConfiguration? = nil) {
config = configuration
if let customNetworkAdapter = networkAdapter {
networkCoordinator = CLDNetworkCoordinator(configuration: config, networkAdapter: customNetworkAdapter)
} else {
if let sessionConfiguration = sessionConfiguration {
networkCoordinator = CLDNetworkCoordinator(configuration: config, sessionConfiguration: sessionConfiguration)
} else {
networkCoordinator = CLDNetworkCoordinator(configuration: config)
}
}
if let customDownloadAdapter = downloadAdapter {
downloadCoordinator = CLDDownloadCoordinator(configuration: config, networkAdapter: customDownloadAdapter)
} else {
if let downloadSessionConfiguration = downloadSessionConfiguration {
downloadCoordinator = CLDDownloadCoordinator(configuration: config, sessionConfiguration: downloadSessionConfiguration)
} else {
downloadCoordinator = CLDDownloadCoordinator(configuration: config)
}
}
super.init()
}
// MARK: Factory Methods
/**
A factory method to create a new `CLDUrl` instance
- returns: A new `CLDUrl` instance.
*/
open func createUrl() -> CLDUrl {
return CLDUrl(configuration: config)
}
/**
A factory method to create a new `CLDUploader` instance
- returns: A new `CLDUploader` instance.
*/
open func createUploader() -> CLDUploader {
return CLDUploader(networkCoordinator: networkCoordinator)
}
/**
A factory method to create a new `CLDDownloader` instance
- returns: A new `CLDDownloader` instance.
*/
open func createDownloader() -> CLDDownloader {
return CLDDownloader(downloadCoordinator: downloadCoordinator)
}
/**
A factory method to create a new `CLDAdminApi` instance
- returns: A new `CLDAdminApi` instance.
*/
open func createManagementApi() -> CLDManagementApi {
return CLDManagementApi(networkCoordinator: networkCoordinator)
}
// MARK: - Network Adapter
/**
The maximum number of queued downloads that can execute at the same time.
default is NSOperationQueueDefaultMaxConcurrentOperationCount.
- parameter maxConcurrentDownloads: The maximum concurrent downloads to allow.
*/
@available(iOS 8.0, *)
open func setMaxConcurrentDownloads(_ maxConcurrentDownloads: Int) {
networkCoordinator.setMaxConcurrentDownloads(maxConcurrentDownloads)
}
// MARK: Background Session
/**
Set a completion handler provided by the UIApplicationDelegate `application:handleEventsForBackgroundURLSession:completionHandler:` method.
The handler will be called automatically once the session finishes its events for background URL session.
default is `nil`.
*/
@available(iOS 8.0, *)
open func setBackgroundCompletionHandler(_ newValue: (() -> ())?) {
networkCoordinator.setBackgroundCompletionHandler(newValue)
}
// MARK: - Advanced
/**
Sets the "USER-AGENT" HTTP header on all network requests to be **"PlatformName/ver CloudinaryiOS/ver"**
By default the header is set to **"CloudinaryiOS/ver"**
*/
open func setUserPlatform(_ platformName: String, version: String) {
config.setUserPlatform(platformName, version: version)
}
open func setExtraHeaderes(_ extraHeaders: [String: String]) {
networkCoordinator.setExtraHeaders(extraHeaders)
}
}
================================================
FILE: Cloudinary/Classes/Core/CLDCompatibility.swift
================================================
//
// Created by Amir on 05/11/2017.
// Copyright (c) 2017 Cloudinary. All rights reserved.
//
import Foundation
#if swift(>=4.0)
internal extension NSTextCheckingResult {
func rangeAt(_ idx:Int) -> NSRange {
return range(at: idx)
}
}
#endif
================================================
FILE: Cloudinary/Classes/Core/CLDConfiguration.swift
================================================
//
// CLDConfiguration.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
The CLDConfiguration class holds the configuration parameters to be used by the `CLDCloudinary` instance.
*/
@objcMembers open class CLDConfiguration: NSObject {
fileprivate struct Defines {
fileprivate static let ENV_VAR_CLOUDINARY_URL = "CLOUDINARY_URL"
}
/**
Your account's cloud name on Cloudinary.
*/
open fileprivate(set) var cloudName: String!
/**
Your account's API key, can be found in your account's dashboard on Cloudinary as part of the account details.
*/
open fileprivate(set) var apiKey: String?
/**
Your account's API secret, can be found in your account's dashboard on Cloudinary as part of the account details.
*/
open fileprivate(set) var apiSecret: String?
/**
A boolean value specifying whether or not to use a private CDN. false by default.
*/
open fileprivate(set) var privateCdn: Bool = false
/**
A boolean value specifying whether or not to use a secure CDN connection. false by default.
*/
open fileprivate(set) var secure: Bool = true
/**
A boolean value specifying whether or not to use a CDN subdomain. false by default.
*/
open fileprivate(set) var cdnSubdomain: Bool = false
/**
A boolean value specifying whether or not to use a secure connection with a CDN subdomain. false by default.
*/
open fileprivate(set) var secureCdnSubdomain: Bool = false
/**
A boolean value specifying whether or not to use long encryption. false by default.
*/
open fileprivate(set) var longUrlSignature: Bool = false
/**
An enum value specifying the desired hash algorithm. sha1 by default.
*/
open fileprivate(set) var signatureAlgorithm: SignatureAlgorithm = .sha1
/**
A int value specifying the signature version to use.
*/
open fileprivate(set) var signatureVesion : Int = 2
/**
Your secure distribution domain to be set when using a secure distribution (advanced plan only). nil by default.
*/
open fileprivate(set) var secureDistribution: String?
/**
Your custom domain. nil by default.
*/
open fileprivate(set) var cname: String?
/**
A custom upload prefix to be used instead of Cloudinary's default API prefix. nil by default.
*/
open fileprivate(set) var uploadPrefix: String?
/**
A custom timeout in milliseconds to be used instead of Cloudinary's default timeout. nil by default.
*/
open fileprivate(set) var timeout: NSNumber?
/**
A boolean value specifying whether or not to use analytics. true by default.
*/
open var analytics: Bool = true
/**
An analytics object
*/
open var analyticsObject: CLDAnalytics = CLDAnalytics()
internal var userPlatform: CLDUserPlatform?
// MARK: - Init
fileprivate override init() {
super.init()
}
/**
Initializes a CLDConfiguration instance, using the URL specified in the environment parameters under `CLOUDINARY_URL`.
The URL should be in this form: `cloudinary://:@`.
Extra parameters may be added to the url: `secure` (boolean), `cdn_subdomain` (boolean), `secure_cdn_distribution` (boolean), `long_url_signature`(boolean), `cname`, `upload_prefix`, `signature_algorithm`
- returns: A new `CLDConfiguration` instance if the environment parameter URL exists and is valid, otherwise returns nil.
*/
public static func initWithEnvParams() -> CLDConfiguration? {
let dict = ProcessInfo.processInfo.environment
if let url = dict[Defines.ENV_VAR_CLOUDINARY_URL], validateUrl(url: url) {
return CLDConfiguration(cloudinaryUrl: url)
}
return nil
}
internal static func validateUrl(url: String) -> Bool{
return url.starts(with: "cloudinary://")
}
public init?(options: [String : AnyObject]) {
guard let
cloudName = options[ConfigParam.CloudName.rawValue] as? String else {
return nil
}
self.cloudName = cloudName
for key in options.keys {
if let option = ConfigParam(rawValue: key) {
switch option {
case .Secure:
if let value = options[ConfigParam.Secure.rawValue] as? Bool {
secure = value
}
else if let value = options[ConfigParam.Secure.rawValue] as? String {
secure = value.cldAsBool()
}
break
case .CdnSubdomain:
if let value = options[ConfigParam.CdnSubdomain.rawValue] as? Bool {
cdnSubdomain = value
}
else if let value = options[ConfigParam.CdnSubdomain.rawValue] as? String {
cdnSubdomain = value.cldAsBool()
}
break
case .SecureCdnSubdomain:
if let value = options[ConfigParam.SecureCdnSubdomain.rawValue] as? Bool {
secureCdnSubdomain = value
}
else if let value = options[ConfigParam.SecureCdnSubdomain.rawValue] as? String {
secureCdnSubdomain = value.cldAsBool()
}
break
case .LongUrlSignature:
if let value = options[ConfigParam.LongUrlSignature.rawValue] as? Bool {
longUrlSignature = value
}
else if let value = options[ConfigParam.LongUrlSignature.rawValue] as? String {
longUrlSignature = value.cldAsBool()
}
break
case .SignatureAlgorithm:
if let value = options[ConfigParam.SignatureAlgorithm.rawValue] as? SignatureAlgorithm {
signatureAlgorithm = value
}
else if let value = options[ConfigParam.SignatureAlgorithm.rawValue] as? String {
signatureAlgorithm = SignatureAlgorithm(stringValue: value)
}
break
case .SignatureVersion:
if let value = options[ConfigParam.SignatureVersion.rawValue] as? Int {
signatureVesion = value
}
case .CName:
if let value = options[ConfigParam.CName.rawValue] as? String {
cname = value
}
break
case .UploadPrefix:
if let value = options[ConfigParam.UploadPrefix.rawValue] as? String {
uploadPrefix = value
}
break
case .APISecret:
if let value = options[ConfigParam.APISecret.rawValue] as? String {
apiSecret = value
}
break
case .APIKey:
if let value = options[ConfigParam.APIKey.rawValue] as? String {
apiKey = value
}
break
case .PrivateCdn:
if let value = options[ConfigParam.PrivateCdn.rawValue] as? Bool {
privateCdn = value
}
else if let value = options[ConfigParam.PrivateCdn.rawValue] as? String {
privateCdn = value.cldAsBool()
}
break
case .SecureDistribution:
if let value = options[ConfigParam.SecureDistribution.rawValue] as? String {
secureDistribution = value
}
break
case .Timeout:
if let value = options[ConfigParam.Timeout.rawValue] as? NSNumber {
timeout = value
}
else if let value = options[ConfigParam.Timeout.rawValue] as? String {
timeout = value.cldAsNSNumber()
}
break
default:
break
}
}
}
}
/**
Initializes a CLDConfiguration instance with the specified parameters.
- parameter cloudName: Your account's cloud name on Cloudinary.
- parameter apiKey: Your account's API key, can be found in your account's dashboard on Cloudinary as part of the account details.
- parameter apiSecret: Your account's API secret, can be found in your account's dashboard on Cloudinary as part of the account details.
- parameter privateCdn: A boolean value specifying whether or not to use a private CDN. false by default.
- parameter secure: A boolean value specifying whether or not to use a secure CDN connection. false by default.
- parameter cdnSubdomain: A boolean value specifying whether or not to use a CDN subdomain. false by default.
- parameter secureCdnSubdomain: A boolean value specifying whether or not to use a secure connection with a CDN subdomain. false by default.
- parameter longUrlSignature: A boolean value specifying whether or not to use long encryption. false by default.
- parameter signatureAlgorithm: An enum value specifying the desired hash algorithm. sha1 by default.
- parameter secureDistribution: Set your secure distribution domain to be set when using a secure distribution (advanced plan only). nil by default.
- parameter cname: Set your custom domain. nil by default.
- parameter uploadPrefix: Set a custom upload prefix to be used instead of Cloudinary's default API prefix. nil by default.
- parameter timeout: A custom timeout in milliseconds to be used instead of Cloudinary's default timeout. nil by default.
- returns: A new `CLDConfiguration` instance.
*/
public init(
cloudName: String,
apiKey: String? = nil,
apiSecret: String? = nil,
privateCdn: Bool = false,
secure: Bool = false,
cdnSubdomain: Bool = false,
secureCdnSubdomain: Bool = false,
longUrlSignature: Bool = false,
signatureAlgorithm: SignatureAlgorithm = .sha1,
signatureVersion: Int = 2,
secureDistribution: String? = nil,
cname: String? = nil,
uploadPrefix: String? = nil,
timeout: NSNumber? = nil,
analytics: Bool = true
) {
self.cloudName = cloudName
self.apiKey = apiKey
self.apiSecret = apiSecret
self.privateCdn = privateCdn
self.secure = secure
self.cdnSubdomain = cdnSubdomain
self.secureCdnSubdomain = secureCdnSubdomain
self.longUrlSignature = longUrlSignature
self.signatureAlgorithm = signatureAlgorithm
self.signatureVesion = signatureVersion
self.secureDistribution = secureDistribution
self.cname = cname
self.uploadPrefix = uploadPrefix
self.timeout = timeout
self.analytics = analytics
super.init()
}
/**
Initializes a CLDConfiguration instance, using a given URL.
The URL should be in this form: `cloudinary://:@`.
Extra parameters may be added to the url: `secure` (boolean), `cdn_subdomain` (boolean), `secure_cdn_distribution` (boolean), `long_url_signature`(boolean), `cname`, `upload_prefix`, `signature_algorithm`
- returns: A new `CLDConfiguration` instance if the URL is valid, otherwise returns nil.
*/
public init?(cloudinaryUrl: String) {
super.init()
guard let
uri = URL(string: cloudinaryUrl),
CLDConfiguration.validateUrl(url: cloudinaryUrl),
let cloudName = uri.host
else {
return nil
}
self.cloudName = cloudName
if let apiKey = uri.user {
self.apiKey = apiKey
}
if let apiSecret = uri.password {
self.apiSecret = apiSecret
}
if !uri.path.isEmpty {
privateCdn = true
let index1 = uri.path.index(uri.path.startIndex, offsetBy: 1)
secureDistribution = String(uri.path[index1...])
}
if let queryItems = URLComponents(url: uri, resolvingAgainstBaseURL: false)?.queryItems {
for item in queryItems
{
guard let value = item.value else { continue }
switch ConfigParam(rawValue: item.name) {
case .Secure: secure = value.cldAsBool()
case .CdnSubdomain: cdnSubdomain = value.cldAsBool()
case .SecureCdnSubdomain: secureCdnSubdomain = value.cldAsBool()
case .LongUrlSignature: longUrlSignature = value.cldAsBool()
case .SignatureAlgorithm: signatureAlgorithm = SignatureAlgorithm(stringValue: value)
case .SignatureVersion: signatureVesion = Int(value) ?? 2
case .CName: cname = value
case .UploadPrefix: uploadPrefix = value
case .Timeout: timeout = value.cldAsNSNumber()
case .Analytics: analytics = value.cldAsBool()
default:
continue
}
}
}
}
// MARK: Optional Url Params
internal enum ConfigParam: String, CustomStringConvertible {
case Secure = "secure"
case CdnSubdomain = "cdn_subdomain"
case SecureCdnSubdomain = "secure_cdn_subdomain"
case LongUrlSignature = "long_url_signature"
case SignatureAlgorithm = "signature_algorithm"
case SignatureVersion = "signature_version"
case CName = "cname"
case UploadPrefix = "upload_prefix"
case APIKey = "api_key"
case APISecret = "api_secret"
case CloudName = "cloud_name"
case PrivateCdn = "private_cdn"
case SecureDistribution = "secure_distribution"
case Timeout = "timeout"
case Analytics = "analytics"
internal var description: String {
switch self {
case .Secure: return "secure"
case .CdnSubdomain: return "cdn_subdomain"
case .SecureCdnSubdomain: return "secure_cdn_subdomain"
case .LongUrlSignature: return "long_url_signature"
case .SignatureAlgorithm: return "signature_algorithm"
case .SignatureVersion: return "signature_version"
case .CName: return "cname"
case .UploadPrefix: return "upload_prefix"
case .APIKey: return "api_key"
case .APISecret: return "api_secret"
case .CloudName: return "cloud_name"
case .PrivateCdn: return "private_cdn"
case .SecureDistribution: return "secure_distribution"
case .Timeout: return "timeout"
case .Analytics: return "analytics"
}
}
}
// MARK: signatureAlgorithm
@objc public enum SignatureAlgorithm: Int, CustomStringConvertible {
case sha1 = 0
case sha256 = 1
public init(stringValue: String) {
switch stringValue {
case "sha1": self = .sha1
case "sha256": self = .sha256
default: self = .sha1
}
}
public var description: String {
switch self {
case .sha1: return "sha1"
case .sha256: return "sha256"
}
}
}
// MARK: User Platform
internal func setUserPlatform(_ platformName: String, version: String) {
userPlatform = CLDUserPlatform(platform: platformName, version: version)
}
internal func clearUserPlatform() {
userPlatform = nil
}
internal struct CLDUserPlatform {
var platform: String
var version: String
init(platform: String, version: String) {
self.platform = platform
self.version = version
}
}
}
================================================
FILE: Cloudinary/Classes/Core/CLDEagerTransformation.swift
================================================
//
// Created by Nitzan Jaitman on 09/05/2018.
// Copyright (c) 2018 Cloudinary. All rights reserved.
//
import Foundation
open class CLDEagerTransformation: CLDTransformation {
/**
Set the format for the eager transformation
*/
fileprivate var eagerFormat: String?
open func setFormat(_ format: String?) -> Self{
self.eagerFormat = format
return self;
}
override func getStringRepresentationFromParams(_ params: [String: String]) -> String? {
// return the original transformation string with a /format if set.
var result = ""
if let baseTransformation = super.getStringRepresentationFromParams(params) {
result += baseTransformation
}
if let format = eagerFormat {
result += "/\(format)"
}
return result
}
}
================================================
FILE: Cloudinary/Classes/Core/CLDResponsiveParams.swift
================================================
//
// CLDResponsiveParams.swift
//
// Copyright (c) 2018 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDResponsiveParams: NSObject {
public static let defaultStepSize = 50
public static let defaultMaxDimension = 350
public static let defaultMinDimension = 50
public static let defaultReloadOnSizeChange = false
internal let autoWidth: Bool
internal let autoHeight: Bool
internal let cropMode: CLDTransformation.CLDCrop?
internal let gravity: CLDTransformation.CLDGravity?
internal var shouldReloadOnSizeChange = defaultReloadOnSizeChange
internal var stepSizePoints = defaultStepSize
internal var maxDimensionPoints = defaultMaxDimension
internal var minDimensionPoints = defaultMinDimension
// MARK: - Init
public init(autoWidth: Bool, autoHeight: Bool, cropMode: CLDTransformation.CLDCrop?, gravity: CLDTransformation.CLDGravity?) {
self.autoWidth = autoWidth
self.autoHeight = autoHeight
self.cropMode = cropMode
self.gravity = gravity
}
// MARK: - Presets
public static func fit () -> CLDResponsiveParams {
return CLDResponsiveParams(autoWidth: true, autoHeight: true, cropMode: CLDTransformation.CLDCrop.fit, gravity: nil)
}
public static func autoFill () -> CLDResponsiveParams {
return CLDResponsiveParams(autoWidth: true, autoHeight: true, cropMode: CLDTransformation.CLDCrop.fill, gravity: CLDTransformation.CLDGravity.auto)
}
// MARK: - Setters
public func setStepSize(_ stepSize:Int) -> Self {
self.stepSizePoints = stepSize
return self
}
public func setMaxDimension(_ maxDimension:Int) -> Self {
self.maxDimensionPoints = maxDimension
return self
}
public func setMinDimension(_ minDimension:Int) -> Self {
self.minDimensionPoints = minDimension
return self
}
public func setReloadOnSizeChange(_ reload: Bool) -> Self {
self.shouldReloadOnSizeChange = reload
return self
}
}
================================================
FILE: Cloudinary/Classes/Core/Cloudinary.h
================================================
//
// Cloudinary.h
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#import
//! Project version number for Cloudinary.
FOUNDATION_EXPORT double CloudinaryVersionNumber;
//! Project version string for Cloudinary.
FOUNDATION_EXPORT const unsigned char CloudinaryVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import
================================================
FILE: Cloudinary/Classes/Core/Features/CacheSystem/Enum/HTTPStatusCode.swift
================================================
//
// HTTPStatusCode.swift
//
// Copyright (c) 2020 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
internal enum HTTPStatusCode : Int
{
case `continue` = 100
case switchingProtocols = 101
case processing = 102
case checkpoint = 103
case ok = 200
case created = 201
case accepted = 202
case nonAuthoritativeInformation = 203
case noContent = 204
case resetContent = 205
case partialContent = 206
case multiStatus = 207
case alreadyReported = 208
case imUsed = 226
case multipleChoices = 300
case movedPermenantly = 301
case found = 302
case seeOther = 303
case notModified = 304
case useProxy = 305
case temporaryRedirect = 307
case permanentRedirect = 308
case badRequest = 400
case unauthorized = 401
case paymentRequired = 402
case forbidden = 403
case notFound = 404
case methodNotAllowed = 405
case notAcceptable = 406
case proxyAuthenticationRequired = 407
case requestTimeout = 408
case conflict = 409
case gone = 410
case lengthRequired = 411
case preconditionFailed = 412
case payloadTooLarge = 413
case uriTooLong = 414
case unsupportedMediaType = 415
case rangeNotSatisfiable = 416
case expectationFailed = 417
case imATeapot = 418
case misdirectedRequest = 421
case unprocesssableEntity = 422
case locked = 423
case failedDependency = 424
case urpgradeRequired = 426
case preconditionRequired = 428
case tooManyRequests = 429
case requestHeadersFieldTooLarge = 431
case iisLoginTimeout = 440
case nginxNoResponse = 444
case iisRetryWith = 449
case blockedByWindowsParentalControls = 450
case unavailableForLegalReasons = 451
case nginxSSLCertificateError = 495
case nginxHTTPToHTTPS = 497
case tokenExpired = 498
case nginxClientClosedRequest = 499
case internalServerError = 500
case notImplemented = 501
case badGateway = 502
case serviceUnavailable = 503
case gatewayTimeout = 504
case httpVersionNotSupported = 505
case variantAlsoNegotiates = 506
case insufficientStorage = 507
case loopDetected = 508
case bandwidthLimitExceeded = 509
case notExtended = 510
case networkAuthenticationRequired = 511
case siteIsFrozen = 530
}
extension HTTPStatusCode
{
internal var isInformational : Bool {
switch rawValue {
case 100..<200: return true
default: return false
}
}
internal var isSuccess : Bool {
switch rawValue {
case 200..<300: return true
default: return false
}
}
internal var isRedirection : Bool {
switch rawValue {
case 300..<400: return true
default: return false
}
}
internal var isClientError : Bool {
switch rawValue {
case 400..<500: return true
default: return false
}
}
internal var isServerError : Bool {
switch rawValue {
case 500..<600: return true
default: return false
}
}
internal var isError : Bool {
return self.isClientError || isServerError
}
internal var localizedReason : String {
return HTTPURLResponse.localizedString(forStatusCode: rawValue)
}
}
// MARK: - CustomStringConvertible
extension HTTPStatusCode : CustomStringConvertible
{
internal var description : String {
return "\(rawValue) \(localizedReason)"
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Downloader/CLDDownloader.swift
================================================
//
// CLDDownloader.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
The CLDDownloader class is used to asynchronously fetch images either from the image cache if they exist or download them from a remote source.
*/
@objcMembers open class CLDDownloader: CLDBaseNetworkObject {
// MARK: - Init
internal fileprivate(set) var downloadCoordinator: CLDDownloadCoordinator!
fileprivate override init() {
super.init()
}
internal init(downloadCoordinator: CLDDownloadCoordinator) {
self.downloadCoordinator = downloadCoordinator
super.init(networkCoordinator: downloadCoordinator)
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/CLDBaseNetworkObject.swift
================================================
//
// CLDBaseNetworkObject.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDBaseNetworkObject: NSObject {
internal fileprivate(set) var networkCoordinator: CLDNetworkCoordinator!
internal override init() {
super.init()
}
internal init(networkCoordinator: CLDNetworkCoordinator) {
self.networkCoordinator = networkCoordinator
super.init()
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/CLDConditionExpression.swift
================================================
//
// CLDConditionExpression.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDConditionExpression : CLDExpression {
var relatedTransformation: CLDTransformation
// MARK: - Init
private init(_ expression: CLDExpression) {
self.relatedTransformation = CLDTransformation()
super.init(value: expression.asInternalString())
}
public override init() {
self.relatedTransformation = CLDTransformation()
super.init()
}
public override init(value: String) {
self.relatedTransformation = CLDTransformation()
super.init(value: value)
}
// MARK: - Public methods
@discardableResult
public func and() -> Self {
appendOperatorToCurrentValue(.and)
return self
}
@objc(andString:)
@discardableResult
public func and(_ value: String) -> Self {
appendingStringToCurrentValue(cldoperator: .and, inputValue: value)
return self
}
@objc(andExpression:)
@discardableResult
public func and(_ value: CLDExpression) -> Self {
return and(value.asInternalString())
}
@discardableResult
public func or() -> Self {
appendOperatorToCurrentValue(.or)
return self
}
@objc(orString:)
@discardableResult
public func or(_ value: String) -> Self {
appendingStringToCurrentValue(cldoperator: .or, inputValue: value)
return self
}
@objc(orExpression:)
@discardableResult
public func or(_ value: CLDExpression) -> Self {
return or(value.asInternalString())
}
@objc(equalToInt:)
@discardableResult
public func equal(to value: Int) -> Self {
return equal(to: String(value))
}
@objc(equalToFloat:)
@discardableResult
public func equal(to value: Float) -> Self {
return equal(to: value.cldFloatFormat())
}
@objc(equalToString:)
@discardableResult
public func equal(to value: String) -> Self {
appendingStringToCurrentValue(cldoperator: .equal, inputValue: value)
return self
}
@objc(equalToExpression:)
@discardableResult
public func equal(to value: CLDExpression) -> Self {
return equal(to: value.asInternalString())
}
@objc(notEqualToInt:)
@discardableResult
public func notEqual(to value: Int) -> Self {
return notEqual(to: String(value))
}
@objc(notEqualToFloat:)
@discardableResult
public func notEqual(to value: Float) -> Self {
return notEqual(to: value.cldFloatFormat())
}
@objc(notEqualToString:)
@discardableResult
public func notEqual(to value: String) -> Self {
appendingStringToCurrentValue(cldoperator: .notEqual, inputValue: value)
return self
}
@objc(notEqualToExpression:)
@discardableResult
public func notEqual(to value: CLDExpression) -> Self {
return notEqual(to: value.asInternalString())
}
@objc(lessThenInt:)
@discardableResult
public func less(then value: Int) -> Self {
return less(then: String(value))
}
@objc(lessThenFloat:)
@discardableResult
public func less(then value: Float) -> Self {
return less(then: value.cldFloatFormat())
}
@objc(lessThenString:)
@discardableResult
public func less(then value: String) -> Self {
appendingStringToCurrentValue(cldoperator: .lessThen, inputValue: value)
return self
}
@objc(lessThenExpression:)
@discardableResult
public func less(then value: CLDExpression) -> Self {
return less(then: value.asInternalString())
}
@objc(greaterThenInt:)
@discardableResult
public func greater(then value: Int) -> Self {
return greater(then: String(value))
}
@objc(greaterThenFloat:)
@discardableResult
public func greater(then value: Float) -> Self {
return greater(then: value.cldFloatFormat())
}
@objc(greaterThenString:)
@discardableResult
public func greater(then value: String) -> Self {
appendingStringToCurrentValue(cldoperator: .greaterThen, inputValue: value)
return self
}
@objc(greaterThenExpression:)
@discardableResult
public func greater(then value: CLDExpression) -> Self {
return greater(then: value.asInternalString())
}
@objc(lessOrEqualToInt:)
@discardableResult
public func lessOrEqual(to value: Int) -> Self {
return lessOrEqual(to: String(value))
}
@objc(lessOrEqualToFloat:)
@discardableResult
public func lessOrEqual(to value: Float) -> Self {
return lessOrEqual(to: value.cldFloatFormat())
}
@objc(lessOrEqualToString:)
@discardableResult
public func lessOrEqual(to value: String) -> Self {
appendingStringToCurrentValue(cldoperator: .lessOrEqual, inputValue: value)
return self
}
@objc(lessOrEqualToExpression:)
@discardableResult
public func lessOrEqual(to value: CLDExpression) -> Self {
return lessOrEqual(to: value.asInternalString())
}
@objc(greaterOrEqualToInt:)
@discardableResult
public func greaterOrEqual(to value: Int) -> Self {
return greaterOrEqual(to: String(value))
}
@objc(greaterOrEqualToFloat:)
@discardableResult
public func greaterOrEqual(to value: Float) -> Self {
return greaterOrEqual(to: value.cldFloatFormat())
}
@objc(greaterOrEqualToString:)
@discardableResult
public func greaterOrEqual(to value: String) -> Self {
appendingStringToCurrentValue(cldoperator: .greaterOrEqual, inputValue: value)
return self
}
@objc(greaterOrEqualToExpression:)
@discardableResult
public func greaterOrEqual(to value: CLDExpression) -> Self {
return greaterOrEqual(to: value.asInternalString())
}
@objc(insideString:)
@discardableResult
public func inside(_ expression: String) -> Self {
guard !expression.isEmpty else { return self }
appendingStringToCurrentValue(cldoperator: .inside, inputValue: expression)
return self
}
@objc(insideExpression:)
@discardableResult
public func inside(_ expression: CLDExpression) -> Self {
return inside(expression.asInternalString())
}
@objc(notInsideString:)
@discardableResult
public func notInside(_ expression: String) -> Self {
guard !expression.isEmpty else { return self }
appendingStringToCurrentValue(cldoperator: .notInside, inputValue: expression)
return self
}
@objc(notInsideExpression:)
@discardableResult
public func notInside(_ expression: CLDExpression) -> Self {
return notInside(expression.asInternalString())
}
@objc(valueFromString:)
@discardableResult
public func value(_ expression: String) -> Self {
let expressionObject = CLDExpression.init(value: expression)
let value: String
if expressionObject.currentValue.isEmpty {
value = expression
} else {
value = expressionObject.asString()
}
if !expressionObject.currentValue.isEmpty && currentKey.isEmpty {
currentKey = expressionObject.currentKey
currentValue = expressionObject.currentValue
}
else if expressionObject.currentValue.isEmpty && currentKey.isEmpty {
currentKey = value
}
else if currentValue.isEmpty {
currentValue.append(value)
} else {
currentValue.append(CLDVariable.elementsSeparator + value)
}
return self
}
@objc(valueFromExpression:)
@discardableResult
public func value(_ expression: CLDExpression) -> Self {
return value(expression.asInternalString())
}
// MARK: - then
@objc(then)
public func then() -> CLDTransformation {
return relatedTransformation.ifCondition(self)
}
// MARK: - Class Func
public override class func width() -> Self {
return CLDConditionExpression(super.width()) as! Self
}
public override class func height() -> Self {
return CLDConditionExpression(super.height()) as! Self
}
public override class func aspectRatio() -> Self {
return CLDConditionExpression(super.aspectRatio()) as! Self
}
public override class func initialWidth() -> Self {
return CLDConditionExpression(super.initialWidth()) as! Self
}
public override class func initialHeight() -> Self {
return CLDConditionExpression(super.initialHeight()) as! Self
}
public override class func initialAspectRatio() -> Self {
return CLDConditionExpression(super.initialAspectRatio()) as! Self
}
public override class func pageCount() -> Self {
return CLDConditionExpression(super.pageCount()) as! Self
}
public override class func faceCount() -> Self {
return CLDConditionExpression(super.faceCount()) as! Self
}
public override class func tags() -> Self {
return CLDConditionExpression(super.tags()) as! Self
}
public override class func pageXOffset() -> Self {
return CLDConditionExpression(super.pageXOffset()) as! Self
}
public override class func pageYOffset() -> Self {
return CLDConditionExpression(super.pageYOffset()) as! Self
}
public override class func illustrationScore() -> Self {
return CLDConditionExpression(super.illustrationScore()) as! Self
}
public override class func currentPageIndex() -> Self {
return CLDConditionExpression(super.currentPageIndex()) as! Self
}
public override class func duration() -> Self {
return CLDConditionExpression(super.duration()) as! Self
}
public override class func initialDuration() -> Self {
return CLDConditionExpression(super.initialDuration()) as! Self
}
// MARK: - helper methods
@objc(width:intValue:)
@discardableResult
public func width(_ operatorString: String, _ object: Int) -> Self {
return predicate(expressionKey: .width, operatorString: operatorString, inputValue: String(object))
}
@objc(width:floatValue:)
@discardableResult
public func width(_ operatorString: String, _ object: Float) -> Self {
return predicate(expressionKey: .width, operatorString: operatorString, inputValue: String(object))
}
@objc(width:string:)
@discardableResult
public func width(_ operatorString: String, _ object: String) -> Self {
return predicate(expressionKey: .width, operatorString: operatorString, inputValue: object)
}
@objc(width:expression:)
@discardableResult
public func width(_ operatorString: String, _ object: CLDExpression) -> Self {
return predicate(expressionKey: .width, operatorString: operatorString, inputValue: object.asInternalString())
}
@objc(width:conditionExpression:)
@discardableResult
public func width(_ operatorString: String, _ object: CLDConditionExpression) -> Self {
return predicate(expressionKey: .width, operatorString: operatorString, inputValue: object.asString())
}
@objc(height:intValue:)
@discardableResult
public func height(_ operatorString: String, _ object: Int) -> Self {
return predicate(expressionKey: .height, operatorString: operatorString, inputValue: String(object))
}
@objc(height:floatValue:)
@discardableResult
public func height(_ operatorString: String, _ object: Float) -> Self {
return predicate(expressionKey: .height, operatorString: operatorString, inputValue: String(object))
}
@objc(height:string:)
@discardableResult
public func height(_ operatorString: String, _ object: String) -> Self {
return predicate(expressionKey: .height, operatorString: operatorString, inputValue: object)
}
@objc(height:expression:)
@discardableResult
public func height(_ operatorString: String, _ object: CLDExpression) -> Self {
return predicate(expressionKey: .height, operatorString: operatorString, inputValue: object.asInternalString())
}
@objc(height:conditionExpression:)
@discardableResult
public func height(_ operatorString: String, _ object: CLDConditionExpression) -> Self {
return predicate(expressionKey: .height, operatorString: operatorString, inputValue: object.asString())
}
@objc(aspectRatio:intValue:)
@discardableResult
public func aspectRatio(_ operatorString: String, _ object: Int) -> Self {
return predicate(expressionKey: .aspectRatio, operatorString: operatorString, inputValue: String(object))
}
@objc(aspectRatio:floatValue:)
@discardableResult
public func aspectRatio(_ operatorString: String, _ object: Float) -> Self {
return predicate(expressionKey: .aspectRatio, operatorString: operatorString, inputValue: String(object))
}
@objc(aspectRatio:string:)
@discardableResult
public func aspectRatio(_ operatorString: String, _ object: String) -> Self {
return predicate(expressionKey: .aspectRatio, operatorString: operatorString, inputValue: object)
}
@objc(aspectRatio:expression:)
@discardableResult
public func aspectRatio(_ operatorString: String, _ object: CLDExpression) -> Self {
return predicate(expressionKey: .aspectRatio, operatorString: operatorString, inputValue: object.asInternalString())
}
@objc(aspectRatio:conditionExpression:)
@discardableResult
public func aspectRatio(_ operatorString: String, _ object: CLDConditionExpression) -> Self {
return predicate(expressionKey: .aspectRatio, operatorString: operatorString, inputValue: object.asString())
}
@objc(initialWidth:intValue:)
@discardableResult
public func initialWidth(_ operatorString: String, _ object: Int) -> Self {
return predicate(expressionKey: .initialWidth, operatorString: operatorString, inputValue: String(object))
}
@objc(initialWidth:floatValue:)
@discardableResult
public func initialWidth(_ operatorString: String, _ object: Float) -> Self {
return predicate(expressionKey: .initialWidth, operatorString: operatorString, inputValue: String(object))
}
@objc(initialWidth:string:)
@discardableResult
public func initialWidth(_ operatorString: String, _ object: String) -> Self {
return predicate(expressionKey: .initialWidth, operatorString: operatorString, inputValue: object)
}
@objc(initialWidth:expression:)
@discardableResult
public func initialWidth(_ operatorString: String, _ object: CLDExpression) -> Self {
return predicate(expressionKey: .initialWidth, operatorString: operatorString, inputValue: object.asInternalString())
}
@objc(initialWidth:conditionExpression:)
@discardableResult
public func initialWidth(_ operatorString: String, _ object: CLDConditionExpression) -> Self {
return predicate(expressionKey: .initialWidth, operatorString: operatorString, inputValue: object.asString())
}
@objc(initialHeight:intValue:)
@discardableResult
public func initialHeight(_ operatorString: String, _ object: Int) -> Self {
return predicate(expressionKey: .initialHeight, operatorString: operatorString, inputValue: String(object))
}
@objc(initialHeight:floatValue:)
@discardableResult
public func initialHeight(_ operatorString: String, _ object: Float) -> Self {
return predicate(expressionKey: .initialHeight, operatorString: operatorString, inputValue: String(object))
}
@objc(initialHeight:string:)
@discardableResult
public func initialHeight(_ operatorString: String, _ object: String) -> Self {
return predicate(expressionKey: .initialHeight, operatorString: operatorString, inputValue: object)
}
@objc(initialHeight:expression:)
@discardableResult
public func initialHeight(_ operatorString: String, _ object: CLDExpression) -> Self {
return predicate(expressionKey: .initialHeight, operatorString: operatorString, inputValue: object.asInternalString())
}
@objc(initialHeight:conditionExpression:)
@discardableResult
public func initialHeight(_ operatorString: String, _ object: CLDConditionExpression) -> Self {
return predicate(expressionKey: .initialHeight, operatorString: operatorString, inputValue: object.asString())
}
@objc(initialAspectRatio:intValue:)
@discardableResult
public func initialAspectRatio(_ operatorString: String, _ object: Int) -> Self {
return predicate(expressionKey: .initialAspectRatio, operatorString: operatorString, inputValue: String(object))
}
@objc(initialAspectRatio:floatValue:)
@discardableResult
public func initialAspectRatio(_ operatorString: String, _ object: Float) -> Self {
return predicate(expressionKey: .initialAspectRatio, operatorString: operatorString, inputValue: String(object))
}
@objc(initialAspectRatio:string:)
@discardableResult
public func initialAspectRatio(_ operatorString: String, _ object: String) -> Self {
return predicate(expressionKey: .initialAspectRatio, operatorString: operatorString, inputValue: object)
}
@objc(initialAspectRatio:expression:)
@discardableResult
public func initialAspectRatio(_ operatorString: String, _ object: CLDExpression) -> Self {
return predicate(expressionKey: .initialAspectRatio, operatorString: operatorString, inputValue: object.asInternalString())
}
@objc(initialAspectRatio:conditionExpression:)
@discardableResult
public func initialAspectRatio(_ operatorString: String, _ object: CLDConditionExpression) -> Self {
return predicate(expressionKey: .initialAspectRatio, operatorString: operatorString, inputValue: object.asString())
}
@objc(pageCount:intValue:)
@discardableResult
public func pageCount(_ operatorString: String, _ object: Int) -> Self {
return predicate(expressionKey: .pageCount, operatorString: operatorString, inputValue: String(object))
}
@objc(pageCount:floatValue:)
@discardableResult
public func pageCount(_ operatorString: String, _ object: Float) -> Self {
return predicate(expressionKey: .pageCount, operatorString: operatorString, inputValue: String(object))
}
@objc(pageCount:string:)
@discardableResult
public func pageCount(_ operatorString: String, _ object: String) -> Self {
return predicate(expressionKey: .pageCount, operatorString: operatorString, inputValue: object)
}
@objc(pageCount:expression:)
@discardableResult
public func pageCount(_ operatorString: String, _ object: CLDExpression) -> Self {
return predicate(expressionKey: .pageCount, operatorString: operatorString, inputValue: object.asInternalString())
}
@objc(pageCount:conditionExpression:)
@discardableResult
public func pageCount(_ operatorString: String, _ object: CLDConditionExpression) -> Self {
return predicate(expressionKey: .pageCount, operatorString: operatorString, inputValue: object.asString())
}
@objc(faceCount:intValue:)
@discardableResult
public func faceCount(_ operatorString: String, _ object: Int) -> Self {
return predicate(expressionKey: .faceCount, operatorString: operatorString, inputValue: String(object))
}
@objc(faceCount:floatValue:)
@discardableResult
public func faceCount(_ operatorString: String, _ object: Float) -> Self {
return predicate(expressionKey: .faceCount, operatorString: operatorString, inputValue: String(object))
}
@objc(faceCount:string:)
@discardableResult
public func faceCount(_ operatorString: String, _ object: String) -> Self {
return predicate(expressionKey: .faceCount, operatorString: operatorString, inputValue: object)
}
@objc(faceCount:expression:)
@discardableResult
public func faceCount(_ operatorString: String, _ object: CLDExpression) -> Self {
return predicate(expressionKey: .faceCount, operatorString: operatorString, inputValue: object.asInternalString())
}
@objc(faceCount:conditionExpression:)
@discardableResult
public func faceCount(_ operatorString: String, _ object: CLDConditionExpression) -> Self {
return predicate(expressionKey: .faceCount, operatorString: operatorString, inputValue: object.asString())
}
@objc(tags:intValue:)
@discardableResult
public func tags(_ operatorString: String, _ object: Int) -> Self {
return predicate(expressionKey: .tags, operatorString: operatorString, inputValue: String(object))
}
@objc(tags:floatValue:)
@discardableResult
public func tags(_ operatorString: String, _ object: Float) -> Self {
return predicate(expressionKey: .tags, operatorString: operatorString, inputValue: String(object))
}
@objc(tags:string:)
@discardableResult
public func tags(_ operatorString: String, _ object: String) -> Self {
return predicate(expressionKey: .tags, operatorString: operatorString, inputValue: object)
}
@objc(tags:expression:)
@discardableResult
public func tags(_ operatorString: String, _ object: CLDExpression) -> Self {
return predicate(expressionKey: .tags, operatorString: operatorString, inputValue: object.asInternalString())
}
@objc(tags:conditionExpression:)
@discardableResult
public func tags(_ operatorString: String, _ object: CLDConditionExpression) -> Self {
return predicate(expressionKey: .tags, operatorString: operatorString, inputValue: object.asString())
}
@objc(pageXOffset:intValue:)
@discardableResult
public func pageXOffset(_ operatorString: String, _ object: Int) -> Self {
return predicate(expressionKey: .pageX, operatorString: operatorString, inputValue: String(object))
}
@objc(pageXOffset:floatValue:)
@discardableResult
public func pageXOffset(_ operatorString: String, _ object: Float) -> Self {
return predicate(expressionKey: .pageX, operatorString: operatorString, inputValue: String(object))
}
@objc(pageXOffset:string:)
@discardableResult
public func pageXOffset(_ operatorString: String, _ object: String) -> Self {
return predicate(expressionKey: .pageX, operatorString: operatorString, inputValue: object)
}
@objc(pageXOffset:expression:)
@discardableResult
public func pageXOffset(_ operatorString: String, _ object: CLDExpression) -> Self {
return predicate(expressionKey: .pageX, operatorString: operatorString, inputValue: object.asInternalString())
}
@objc(pageXOffset:conditionExpression:)
@discardableResult
public func pageXOffset(_ operatorString: String, _ object: CLDConditionExpression) -> Self {
return predicate(expressionKey: .pageX, operatorString: operatorString, inputValue: object.asString())
}
@objc(pageYOffset:intValue:)
@discardableResult
public func pageYOffset(_ operatorString: String, _ object: Int) -> Self {
return predicate(expressionKey: .pageY, operatorString: operatorString, inputValue: String(object))
}
@objc(pageYOffset:floatValue:)
@discardableResult
public func pageYOffset(_ operatorString: String, _ object: Float) -> Self {
return predicate(expressionKey: .pageY, operatorString: operatorString, inputValue: String(object))
}
@objc(pageYOffset:string:)
@discardableResult
public func pageYOffset(_ operatorString: String, _ object: String) -> Self {
return predicate(expressionKey: .pageY, operatorString: operatorString, inputValue: object)
}
@objc(pageYOffset:expression:)
@discardableResult
public func pageYOffset(_ operatorString: String, _ object: CLDExpression) -> Self {
return predicate(expressionKey: .pageY, operatorString: operatorString, inputValue: object.asInternalString())
}
@objc(pageYOffset:conditionExpression:)
@discardableResult
public func pageYOffset(_ operatorString: String, _ object: CLDConditionExpression) -> Self {
return predicate(expressionKey: .pageY, operatorString: operatorString, inputValue: object.asString())
}
@objc(illustrationScore:intValue:)
@discardableResult
public func illustrationScore(_ operatorString: String, _ object: Int) -> Self {
return predicate(expressionKey: .illustrationScore, operatorString: operatorString, inputValue: String(object))
}
@objc(illustrationScore:floatValue:)
@discardableResult
public func illustrationScore(_ operatorString: String, _ object: Float) -> Self {
return predicate(expressionKey: .illustrationScore, operatorString: operatorString, inputValue: String(object))
}
@objc(illustrationScore:string:)
@discardableResult
public func illustrationScore(_ operatorString: String, _ object: String) -> Self {
return predicate(expressionKey: .illustrationScore, operatorString: operatorString, inputValue: object)
}
@objc(illustrationScore:expression:)
@discardableResult
public func illustrationScore(_ operatorString: String, _ object: CLDExpression) -> Self {
return predicate(expressionKey: .illustrationScore, operatorString: operatorString, inputValue: object.asInternalString())
}
@objc(illustrationScore:conditionExpression:)
@discardableResult
public func illustrationScore(_ operatorString: String, _ object: CLDConditionExpression) -> Self {
return predicate(expressionKey: .illustrationScore, operatorString: operatorString, inputValue: object.asString())
}
@objc(currentPageIndex:intValue:)
@discardableResult
public func currentPageIndex(_ operatorString: String, _ object: Int) -> Self {
return predicate(expressionKey: .currentPage, operatorString: operatorString, inputValue: String(object))
}
@objc(currentPageIndex:floatValue:)
@discardableResult
public func currentPageIndex(_ operatorString: String, _ object: Float) -> Self {
return predicate(expressionKey: .currentPage, operatorString: operatorString, inputValue: String(object))
}
@objc(currentPageIndex:string:)
@discardableResult
public func currentPageIndex(_ operatorString: String, _ object: String) -> Self {
return predicate(expressionKey: .currentPage, operatorString: operatorString, inputValue: object)
}
@objc(currentPageIndex:expression:)
@discardableResult
public func currentPageIndex(_ operatorString: String, _ object: CLDExpression) -> Self {
return predicate(expressionKey: .currentPage, operatorString: operatorString, inputValue: object.asInternalString())
}
@objc(currentPageIndex:conditionExpression:)
@discardableResult
public func currentPageIndex(_ operatorString: String, _ object: CLDConditionExpression) -> Self {
return predicate(expressionKey: .currentPage, operatorString: operatorString, inputValue: object.asString())
}
@objc(duration:intValue:)
@discardableResult
public func duration(_ operatorString: String, _ object: Int) -> Self {
return predicate(expressionKey: .duration, operatorString: operatorString, inputValue: String(object))
}
@objc(duration:floatValue:)
@discardableResult
public func duration(_ operatorString: String, _ object: Float) -> Self {
return predicate(expressionKey: .duration, operatorString: operatorString, inputValue: String(object))
}
@objc(duration:string:)
@discardableResult
public func duration(_ operatorString: String, _ object: String) -> Self {
return predicate(expressionKey: .duration, operatorString: operatorString, inputValue: object)
}
@objc(duration:expression:)
@discardableResult
public func duration(_ operatorString: String, _ object: CLDExpression) -> Self {
return predicate(expressionKey: .duration, operatorString: operatorString, inputValue: object.asInternalString())
}
@objc(duration:conditionExpression:)
@discardableResult
public func duration(_ operatorString: String, _ object: CLDConditionExpression) -> Self {
return predicate(expressionKey: .duration, operatorString: operatorString, inputValue: object.asString())
}
@objc(initialDuration:intValue:)
@discardableResult
public func initialDuration(_ operatorString: String, _ object: Int) -> Self {
return predicate(expressionKey: .initialDuration, operatorString: operatorString, inputValue: String(object))
}
@objc(initialDuration:floatValue:)
@discardableResult
public func initialDuration(_ operatorString: String, _ object: Float) -> Self {
return predicate(expressionKey: .initialDuration, operatorString: operatorString, inputValue: String(object))
}
@objc(initialDuration:string:)
@discardableResult
public func initialDuration(_ operatorString: String, _ object: String) -> Self {
return predicate(expressionKey: .initialDuration, operatorString: operatorString, inputValue: object)
}
@objc(initialDuration:expression:)
@discardableResult
public func initialDuration(_ operatorString: String, _ object: CLDExpression) -> Self {
return predicate(expressionKey: .initialDuration, operatorString: operatorString, inputValue: object.asInternalString())
}
@objc(initialDuration:conditionExpression:)
@discardableResult
public func initialDuration(_ operatorString: String, _ object: CLDConditionExpression) -> Self {
return predicate(expressionKey: .initialDuration, operatorString: operatorString, inputValue: object.asString())
}
// MARK: - Private Methods
private func appendingStringToCurrentValue(cldoperator: CLDOperators, inputValue: String) {
let expression = CLDExpression.init(value: inputValue)
let value: String
if expression.currentValue.isEmpty {
value = inputValue
} else {
value = expression.asString()
}
appendOperatorToCurrentValue(cldoperator, inputValue: value)
}
fileprivate func predicate(expressionKey: ExpressionKeys, operatorString: String, inputValue: String) -> Self {
let stringPredicate = "\(expressionKey.asString) \(operatorString) \(inputValue)"
return value(stringPredicate)
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/CLDDefinitions.swift
================================================
//
// CLDDefinitions.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
// MARK: URL Type
@objc public enum CLDType: Int, CustomStringConvertible {
case upload, fetch, facebook, twitter, twitterName, sprite, `private`, authenticated
public var description: String {
get {
switch self {
case .upload: return "upload"
case .facebook: return "facebook"
case .fetch: return "fetch"
case .twitter: return "twitter"
case .twitterName: return "twitter_name"
case .sprite: return "sprite"
case .private: return "private"
case .authenticated: return "authenticated"
}
}
}
}
// MARK: Resource Type
@objc public enum CLDUrlResourceType: Int, CustomStringConvertible {
case image, raw, video, auto
public var description: String {
get {
switch self {
case .image: return "image"
case .raw: return "raw"
case .video: return "video"
case .auto: return "auto"
}
}
}
}
// MARK: - Upload Params
@objc public enum CLDModeration: Int, CustomStringConvertible {
case manual, webpurify
public var description: String {
get {
switch self {
case .manual: return "manual"
case .webpurify: return "webpurify"
}
}
}
}
// MARK: - Text Params
@objc public enum CLDFontWeight: Int, CustomStringConvertible {
case normal, bold
public var description: String {
get {
switch self {
case .normal: return "normal"
case .bold: return "bold"
}
}
}
}
@objc public enum CLDFontStyle: Int, CustomStringConvertible {
case normal, italic
public var description: String {
get {
switch self {
case .normal: return "normal"
case .italic: return "italic"
}
}
}
}
@objc public enum CLDTextDecoration: Int, CustomStringConvertible {
case none, underline
public var description: String {
get {
switch self {
case .none: return "none"
case .underline: return "underline"
}
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/CLDExpression.swift
================================================
//
// CLDExpression.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDExpression: NSObject {
internal enum ExpressionKeys : String, CaseIterable {
case width
case height
case initial_width
case initialWidth
case initial_height
case initialHeight
case initial_aspect_ratio
case aspect_ratio
case aspectRatio
case initialAspectRatio
case page_count
case pageCount
case face_count
case faceCount
case illustration_score
case illustrationScore
case current_page
case currentPage
case tags
case pageX
case pageY
case duration
case initial_duration
case initialDuration
var asString: String {
switch self {
case .width : return "w"
case .height: return "h"
case .initial_width : return "iw"
case .initialWidth : return "iw"
case .initial_height: return "ih"
case .initialHeight : return "ih"
case .initial_aspect_ratio: return "iar"
case .aspect_ratio : return "ar"
case .aspectRatio : return "ar"
case .initialAspectRatio : return "iar"
case .page_count: return "pc"
case .pageCount : return "pc"
case .face_count: return "fc"
case .faceCount : return "fc"
case .illustration_score: return "ils"
case .illustrationScore : return "ils"
case .current_page: return "cp"
case .currentPage : return "cp"
case .tags: return "tags"
case .pageX: return "px"
case .pageY: return "py"
case .duration: return "du"
case .initial_duration: return "idu"
case .initialDuration : return "idu"
}
}
}
internal var currentValue : String
internal var currentKey : String
private var allSpaceAndOrDash : Bool = false
private let consecutiveDashesRegex : String = "[ _]+"
private let userVariableRegex : String = "\\$_*[^_]+"
// MARK: - Init
public override init() {
self.currentKey = String()
self.currentValue = String()
super.init()
}
public init(value: String) {
var components = value.components(separatedBy: .whitespacesAndNewlines)
self.currentKey = components.removeFirst()
self.currentValue = components.joined(separator: CLDVariable.elementsSeparator)
let range = NSRange(location: 0, length: value.utf16.count)
let regex = try? NSRegularExpression(pattern: "^" + consecutiveDashesRegex)
self.allSpaceAndOrDash = !(regex?.firstMatch(in: value, options: [], range: range) == nil)
super.init()
}
fileprivate init(expressionKey: ExpressionKeys) {
self.currentKey = expressionKey.asString
self.currentValue = String()
super.init()
}
// MARK: - class func
public class func width() -> CLDExpression {
return CLDExpression(expressionKey: .width)
}
public class func height() -> CLDExpression {
return CLDExpression(expressionKey: .height)
}
public class func initialWidth() -> CLDExpression {
return CLDExpression(expressionKey: .initialWidth)
}
public class func initialHeight() -> CLDExpression {
return CLDExpression(expressionKey: .initialHeight)
}
public class func aspectRatio() -> CLDExpression {
return CLDExpression(expressionKey: .aspectRatio)
}
public class func initialAspectRatio() -> CLDExpression {
return CLDExpression(expressionKey: .initialAspectRatio)
}
public class func pageCount() -> CLDExpression {
return CLDExpression(expressionKey: .pageCount)
}
public class func faceCount() -> CLDExpression {
return CLDExpression(expressionKey: .faceCount)
}
public class func tags() -> CLDExpression {
return CLDExpression(expressionKey: .tags)
}
public class func pageXOffset() -> CLDExpression {
return CLDExpression(expressionKey: .pageX)
}
public class func pageYOffset() -> CLDExpression {
return CLDExpression(expressionKey: .pageY)
}
public class func illustrationScore() -> CLDExpression {
return CLDExpression(expressionKey: .illustrationScore)
}
public class func currentPageIndex() -> CLDExpression {
return CLDExpression(expressionKey: .currentPage)
}
public class func duration() -> CLDExpression {
return CLDExpression(expressionKey: .duration)
}
public class func initialDuration() -> CLDExpression {
return CLDExpression(expressionKey: .initialDuration)
}
// MARK: - Public methods
@objc(addByInt:)
@discardableResult
public func add(by number: Int) -> Self {
appendOperatorToCurrentValue(.add, inputValue: "\(number)")
return self
}
@objc(addByFloat:)
@discardableResult
public func add(by number: Float) -> Self {
appendOperatorToCurrentValue(.add, inputValue: number.cldFloatFormat())
return self
}
@objc(addByString:)
@discardableResult
public func add(by number: String) -> Self {
appendOperatorToCurrentValue(.add, inputValue: number)
return self
}
@objc(subtractByInt:)
@discardableResult
public func subtract(by number: Int) -> Self {
appendOperatorToCurrentValue(.subtract, inputValue: "\(number)")
return self
}
@objc(subtractByFloat:)
@discardableResult
public func subtract(by number: Float) -> Self {
appendOperatorToCurrentValue(.subtract, inputValue: number.cldFloatFormat())
return self
}
@objc(subtractByString:)
@discardableResult
public func subtract(by number: String) -> Self {
appendOperatorToCurrentValue(.subtract, inputValue: number)
return self
}
@objc(multipleByInt:)
@discardableResult
public func multiple(by number: Int) -> Self {
appendOperatorToCurrentValue(.multiple, inputValue: "\(number)")
return self
}
@objc(multipleByFloat:)
@discardableResult
public func multiple(by number: Float) -> Self {
appendOperatorToCurrentValue(.multiple, inputValue: number.cldFloatFormat())
return self
}
@objc(multipleByString:)
@discardableResult
public func multiple(by number: String) -> Self {
appendOperatorToCurrentValue(.multiple, inputValue: number)
return self
}
@objc(divideByInt:)
@discardableResult
public func divide(by number: Int) -> Self {
appendOperatorToCurrentValue(.divide, inputValue: "\(number)")
return self
}
@objc(divideByFloat:)
@discardableResult
public func divide(by number: Float) -> Self {
appendOperatorToCurrentValue(.divide, inputValue: number.cldFloatFormat())
return self
}
@objc(divideByString:)
@discardableResult
public func divide(by number: String) -> Self {
appendOperatorToCurrentValue(.divide, inputValue: number)
return self
}
@objc(powerByInt:)
@discardableResult
public func power(by number: Int) -> Self {
appendOperatorToCurrentValue(.power, inputValue: "\(number)")
return self
}
@objc(powerByFloat:)
@discardableResult
public func power(by number: Float) -> Self {
appendOperatorToCurrentValue(.power, inputValue: number.cldFloatFormat())
return self
}
@objc(powerByString:)
@discardableResult
public func power(by number: String) -> Self {
appendOperatorToCurrentValue(.power, inputValue: number)
return self
}
// MARK: - provide content
public func asString() -> String {
guard !currentKey.isEmpty else {
if allSpaceAndOrDash {
return "_"
}
return String()
}
let key = removeExtraDashes(from: replaceAllExpressionKeys(in: currentKey))
if currentValue.isEmpty {
return "\(key)"
}
let value = removeExtraDashes(from: replaceAllUnencodeChars(in: currentValue))
return "\(key)_\(value)"
}
public func asParams() -> [String : String] {
guard !currentKey.isEmpty && !currentValue.isEmpty else {
return [String : String]()
}
let key = replaceAllExpressionKeys(in: currentKey)
let value = removeExtraDashes(from: replaceAllUnencodeChars(in: currentValue))
return [key:value]
}
internal func asInternalString() -> String {
guard !currentValue.isEmpty else {
return "\(currentKey)"
}
return "\(currentKey) \(currentValue)"
}
// MARK: - Private methods
private func replaceAllUnencodeChars(in string: String) -> String {
var wipString = string
wipString = replaceAllOperators(in: string)
wipString = replaceAllExpressionKeys(in: wipString)
return wipString
}
private func replaceAllOperators(in string: String) -> String {
var wipString = string
CLDOperators.allCases.forEach {
wipString = replace(cldOperator: $0, in: wipString)
}
return wipString
}
private func replace(cldOperator: CLDOperators, in string: String) -> String {
return string.replacingOccurrences(of: cldOperator.rawValue, with: cldOperator.asString())
}
private func replaceAllExpressionKeys(in string: String) -> String {
var wipString = string
ExpressionKeys.allCases.forEach {
wipString = replace(expressionKey: $0, in: wipString)
}
return wipString
}
private func replace(expressionKey: ExpressionKeys, in string: String) -> String {
var result : String!
let string = removeExtraDashes(from: string)
if string.contains(CLDVariable.variableNamePrefix) {
let range = NSRange(location: 0, length: string.utf16.count)
let regex = try? NSRegularExpression(pattern: userVariableRegex)
let allRanges = regex?.matches(in: string, options: [], range: range).map({ $0.range }) ?? []
// Replace substring in between user variables. e.x $initial_aspect_ratio_$width, only '_aspect_ratio_' will be addressed.
for (index, range) in allRanges.enumerated() {
let location = range.length + range.location
var length = range.length
if index + 1 == allRanges.count {
length = string.count
} else {
let nextRange = allRanges[index + 1]
length = nextRange.location
}
if let stringRange = Range(NSRange(location: location, length: length - location), in: string) {
result = string.replacingOccurrences(of: expressionKey.rawValue, with: expressionKey.asString, options: .regularExpression, range: stringRange)
}
}
} else {
result = string.replacingOccurrences(of: expressionKey.rawValue, with: expressionKey.asString)
}
return result
}
internal func appendOperatorToCurrentValue(_ cldoperator: CLDOperators, inputValue: String = String()) {
appendOperatorToCurrentValue(cldoperator.asString(), inputValue: inputValue)
}
internal func appendOperatorToCurrentValue(_ cldoperator: String, inputValue: String = String()) {
var stringValue = String()
if !currentValue.isEmpty {
stringValue.append(CLDVariable.elementsSeparator)
}
stringValue.append(cldoperator)
if !inputValue.isEmpty {
stringValue.append(CLDVariable.elementsSeparator + inputValue)
}
currentValue.append(stringValue)
}
private func removeExtraDashes(from string: String) -> String {
return string.replacingOccurrences(of: consecutiveDashesRegex, with: CLDVariable.elementsSeparator, options: .regularExpression, range: nil)
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/CLDOperators.swift
================================================
//
// CLDOperators.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
public enum CLDOperators: String , CaseIterable {
case and = "&&"
case or = "||"
case multiple = "*"
case divide = "/"
case add = "+"
case subtract = "-"
case power = "^"
case notInside = "notInside"
case inside = "inside"
case lessOrEqual = "<="
case greaterOrEqual = ">="
case lessThen = "<"
case greaterThen = ">"
case notEqual = "!="
case equal = "="
func asString() -> String {
switch self {
case .equal : return "eq"
case .notEqual : return "ne"
case .lessThen : return "lt"
case .greaterThen : return "gt"
case .lessOrEqual : return "lte"
case .greaterOrEqual: return "gte"
case .inside : return "in"
case .notInside : return "nin"
case .and: return "and"
case .or : return "or"
case .multiple: return "mul"
case .divide : return "div"
case .add : return "add"
case .subtract: return "sub"
case .power : return "pow"
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/CLDTransformation.swift
================================================
//
// CLDTransformation.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreGraphics
/**
The CLDTransformation class represents a full transformation performed by Cloudinay on-the-fly on a certain asset.
*/
@objcMembers open class CLDTransformation: NSObject {
fileprivate typealias Transformation = [String : String]
static fileprivate let transformationContentSeparator: String = ","
static fileprivate let elementsSeparator : String = "_"
fileprivate var currentTransformationParams: Transformation
fileprivate var transformations : [Transformation]
fileprivate var currentCondition: CLDConditionExpression?
// MARK: - Init
public override init() {
self.currentTransformationParams = Transformation()
self.transformations = [Transformation]()
self.currentCondition = nil
super.init()
}
public init(input: [CLDTransformation]) {
self.currentTransformationParams = Transformation()
self.transformations = [Transformation]()
self.currentCondition = nil
super.init()
input.forEach {
$0.chain()
transformations.append(contentsOf: $0.transformations)
}
}
// MARK: - Get Values
open var width: String? {
return getParam(.WIDTH)
}
open var height: String? {
return getParam(.HEIGHT)
}
open var named: String? {
return getParam(.NAMED)
}
open var crop: String? {
return getParam(.CROP)
}
open var background: String? {
return getParam(.BACKGROUND)
}
open var color: String? {
return getParam(.COLOR)
}
open var effect: String? {
return getParam(.EFFECT)
}
open var angle: String? {
return getParam(.ANGLE)
}
open var opacity: String? {
return getParam(.OPACITY)
}
open var border: String? {
return getParam(.BORDER)
}
open var x: String? {
return getParam(.X)
}
open var y: String? {
return getParam(.Y)
}
open var radius: String? {
return getParam(.RADIUS)
}
open var quality: String? {
return getParam(.QUALITY)
}
open var defaultImage: String? {
return getParam(.DEFAULT_IMAGE)
}
open var gravity: String? {
return getParam(.GRAVITY)
}
open var colorSpace: String? {
return getParam(.COLOR_SPACE)
}
open var prefix: String? {
return getParam(.PREFIX)
}
open var overlay: String? {
return getParam(.OVERLAY)
}
open var underlay: String? {
return getParam(.UNDERLAY)
}
open var fetchFormat: String? {
return getParam(.FETCH_FORMAT)
}
open var density: String? {
return getParam(.DENSITY)
}
open var page: String? {
return getParam(.PAGE)
}
open var delay: String? {
return getParam(.DELAY)
}
open var rawTransformation: String? {
return getParam(.RAW_TRANSFORMATION)
}
open var variables: String? {
return getParam(.VARIABLES)
}
open var ifParam: String? {
return getParam(.IF_PARAM)
}
open var flags: String? {
return getParam(.FLAGS)
}
open var dpr: String? {
return getParam(.DPR)
}
open var zoom: String? {
return getParam(.ZOOM)
}
open var aspectRatio: String? {
return getParam(.ASPECT_RATIO)
}
open var customPreFunction: String? {
guard let base = getParam(.CUSTOM_FUNCTION) else { return nil }
return base.removePrefix("pre:")
}
open var customFunction: String? {
return getParam(.CUSTOM_FUNCTION)
}
open var audioCodec: String? {
return getParam(.AUDIO_CODEC)
}
open var audioFrequency: String? {
return getParam(.AUDIO_FREQUENCY)
}
open var bitRate: String? {
return getParam(.BIT_RATE)
}
open var videoSampling: String? {
return getParam(.VIDEO_SAMPLING)
}
open var duration: String? {
return getParam(.DURATION)
}
open var startOffset: String? {
return getParam(.START_OFFSET)
}
open var endOffset: String? {
return getParam(.END_OFFSET)
}
open var offset: [String]? {
guard let
start = startOffset,
let end = endOffset else {
return nil
}
return [start, end]
}
open var videoCodec: String? {
return getParam(.VIDEO_CODEC)
}
open var fps: String? {
return getParam(.FPS)
}
open var keyframeInterval: String? {
return getParam(.KEYFRAME_INTERVAL)
}
open var streamingProfile: String? {
return getParam(.STREAMING_PROFILE)
}
fileprivate func getParam(_ param: TransformationParam) -> String? {
return getParam(param.rawValue)
}
open func getParam(_ param: String) -> String? {
return currentTransformationParams[param]
}
// MARK: - Set Values - Variable
/**
Set a variable.
- parameter name: The variable's name.
- parameter value: The variable's value.
- returns: The same instance of CLDTransformation.
*/
@objc(setVariable:intValue:)
@discardableResult
public func setVariable(_ name: String, int value: Int) -> Self {
let variable = CLDVariable(name: name, value: String(value))
return setVariable(variable)
}
/**
Set a variable.
- parameter name: The variable's name.
- parameter value: The variable's value.
- returns: The same instance of CLDTransformation.
*/
@objc(setVariable:floatValue:)
@discardableResult
public func setVariable(_ name: String, float value: Float) -> Self {
let variable = CLDVariable(name: name, value: value.cldFloatFormat())
return setVariable(variable)
}
/**
Set a variable.
- parameter name: The variable's name.
- parameter value: The variable's value.
- returns: The same instance of CLDTransformation.
*/
@objc(setVariable:string:)
@discardableResult
public func setVariable(_ name: String, string value: String) -> Self {
let variable = CLDVariable(name: name, value: value)
return setVariable(variable)
}
/**
Set a variable.
- parameter name: The variable's name.
- parameter values: The variable's value.
- returns: The same instance of CLDTransformation.
*/
@objc(setVariable:valuesArray:)
@discardableResult
public func setVariable(_ name: String, values: [String]) -> Self {
let variable = CLDVariable(name: name, values: values)
return setVariable(variable)
}
/**
Set a variable.
- parameter variable: The variable to set.
- returns: The same instance of CLDTransformation.
*/
@objc(setVariableWithVariable:)
@discardableResult
public func setVariable(_ variable: CLDVariable) -> Self {
guard variable.isValid else { return self }
return setParam(variable.name, value: variable.value)
}
/**
Set an array of variables.
- parameter variables: The variables to set.
- returns: The same instance of CLDTransformation.
*/
@objc(setVariablesWithVariablesArray:)
@discardableResult
public func setVariables(_ variables: [CLDVariable]) -> Self {
let joined = variables.filter {
$0.isValid
}.map {
$0.asString()
}.joined(separator: CLDTransformation.transformationContentSeparator)
setParam(TransformationParam.VARIABLES.rawValue, value: joined)
return self
}
// MARK: - Set Values - Width
/**
Set the image width.
- parameter width: The width to set.
- returns: The same instance of CLDTransformation.
*/
@objc(setWidthWithInt:)
@discardableResult
open func setWidth(_ width: Int) -> Self {
return setWidth(String(width))
}
/**
Set the image width.
- parameter width: The width to set.
- returns: The same instance of CLDTransformation.
*/
@objc(setWidthWithFloat:)
@discardableResult
open func setWidth(_ width: Float) -> Self {
return setWidth(width.cldFloatFormat())
}
/**
Set the image width.
- parameter width: The width to set.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setWidth(_ width: String) -> Self {
return setParam(TransformationParam.WIDTH, expression: CLDExpression(value: width), fallback: width)
}
// MARK: - Set Values - Height
/**
Set the image height.
- parameter height: The height to set.
- returns: The same instance of CLDTransformation.
*/
@objc(setHeightWithInt:)
@discardableResult
open func setHeight(_ height: Int) -> Self {
return setHeight(String(height))
}
/**
Set the image height.
- parameter height: The height to set.
- returns: The same instance of CLDTransformation.
*/
@objc(setHeightWithFloat:)
@discardableResult
open func setHeight(_ height: Float) -> Self {
return setHeight(height.cldFloatFormat())
}
/**
Set the image height.
- parameter height: The height to set.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setHeight(_ height: String) -> Self {
return setParam(TransformationParam.HEIGHT, expression: CLDExpression(value: height), fallback: height)
}
// MARK: - Set Values - Named
/**
A named transformation is a set of image transformations that has been given a custom name for easy reference.
It is useful to define a named transformation when you have a set of relatively complex transformations that you use often and that you want to easily reference.
- parameter names: The names of the transformations to set.
- returns: The same instance of CLDTransformation.
*/
@objc(setNamedWithArray:)
@discardableResult
open func setNamed(_ names: [String]) -> Self {
return setNamed(names.joined(separator: "."))
}
/**
A named transformation is a set of image transformations that has been given a custom name for easy reference.
It is useful to define a named transformation when you have a set of relatively complex transformations that you use often and that you want to easily reference.
- parameter names: The names of the transformations to set.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setNamed(_ names: String) -> Self {
return setParam(TransformationParam.NAMED, value: names)
}
// MARK: - Set Values - Crop
/**
Set the image crop.
- parameter crop: The crop to set.
- returns: The same instance of CLDTransformation.
*/
@objc(setCropWithCrop:)
@discardableResult
open func setCrop(_ crop: CLDCrop) -> Self {
return setCrop(String(describing: crop))
}
/**
Set the image crop.
- parameter crop: The crop to set.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setCrop(_ crop: String) -> Self {
return setParam(TransformationParam.CROP, value: crop)
}
// MARK: - Set Values - Background
/**
Defines the background color to use instead of transparent background areas when converting to JPG format or using the pad crop mode.
The background color can be set as an RGB hex triplet (e.g. '#3e2222'), a 3 character RGB hex (e.g. '#777') or a named color (e.g. 'green').
- parameter background: The background color to set.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setBackground(_ background: String) -> Self {
return setParam(TransformationParam.BACKGROUND, value: background.replacingOccurrences(of: "#", with: "rgb:"))
}
// MARK: - Set Values - Color
/**
Customize the color to use together with: text captions, the shadow effect and the colorize effect.
The color can be set as an RGB hex triplet (e.g. '#3e2222'), a 3 character RGB hex (e.g. '#777') or a named color (e.g. 'green').
- parameter color: The color to set.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setColor(_ color: String) -> Self {
return setParam(TransformationParam.COLOR, value: color.replacingOccurrences(of: "#", with: "rgb:"))
}
// MARK: - Set Values - Effect
/**
Apply a filter or an effect on an image.
- parameter effect: The effect to apply.
- returns: The same instance of CLDTransformation.
*/
@objc(setEffectWithEffect:)
@discardableResult
open func setEffect(_ effect: CLDEffect) -> Self {
return setEffect(String(describing: effect))
}
/**
Apply a filter or an effect on an image.
The value includes the name of the effect and an additional parameter that controls the behavior of the specific effect.
- parameter effect: The effect to apply.
- returns: The same instance of CLDTransformation.
*/
@objc(setEffectWithArtFilter:)
@discardableResult
open func setEffect(_ effect: CLDArtFilters) -> CLDTransformation {
return setEffect(.art, param: String(describing: effect))
}
/**
Apply a filter or an effect on an image.
The value includes the name of the effect and an additional parameter that controls the behavior of the specific effect.
- parameter effect: The effect to apply.
- parameter param: The effect value to apply.
- returns: The same instance of CLDTransformation.
*/
@objc(setEffectWithEffect:param:)
@discardableResult
open func setEffect(_ effect: CLDEffect, param: String) -> Self {
return setEffect(String(describing: effect), param: param)
}
/**
Apply a filter or an effect on an image.
The value includes the name of the effect and an additional parameter that controls the behavior of the specific effect.
- parameter effect: The effect to apply.
- parameter param: The effect value to apply.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setEffect(_ effect: String, param: String) -> Self {
return setEffect("\(effect):\(param)")
}
/**
Apply a filter or an effect on an image.
- parameter effect: The effect to apply.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setEffect(_ effect: String) -> Self {
return setParam(TransformationParam.EFFECT, value: effect)
}
// MARK: - Set Values - Angle
/**
Rotate or flip an image by the given degrees or automatically according to its orientation or available meta-data.
- parameter angle: The angle to rotate.
- returns: The same instance of CLDTransformation.
*/
@objc(setAngleWithInt:)
@discardableResult
open func setAngle(_ angle: Int) -> Self {
return setAngle(String(angle))
}
/**
Rotate or flip an image by the given degrees or automatically according to its orientation or available meta-data.
- parameter angles: The angles to rotate.
- returns: The same instance of CLDTransformation.
*/
@objc(setAngleWithArray:)
@discardableResult
open func setAngle(_ angles: [String]) -> Self {
return setAngle(angles.joined(separator: "."))
}
/**
Rotate or flip an image by the given degrees or automatically according to its orientation or available meta-data.
- parameter angle: The angle to rotate.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setAngle(_ angle: String) -> Self {
return setParam(TransformationParam.ANGLE, value: angle)
}
// MARK: - Set Values - Opacity
/**
Adjust the opacity of the image and make it semi-transparent. 100 means opaque, while 0 is completely transparent.
- parameter opacity: The opacity level to apply.
- returns: The same instance of CLDTransformation.
*/
@objc(setOpacityWithInt:)
@discardableResult
open func setOpacity(_ opacity: Int) -> Self {
return setOpacity(String(opacity))
}
/**
Adjust the opacity of the image and make it semi-transparent. 100 means opaque, while 0 is completely transparent.
- parameter opacity: The opacity level to apply.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setOpacity(_ opacity: String) -> Self {
return setParam(TransformationParam.OPACITY, value: opacity)
}
// MARK: - Set Values - Border
/**
Add a solid border around the image.
- parameter width: The border width.
- parameter color: The border color.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setBorder(_ width: Int, color: String) -> Self {
return setBorder("\(width)px_solid_\(color)")
}
/**
Add a solid border around the image.
Should conform to the form: [width]px_solid_[color], e.g - 5px_solid_#111111 or 5px_solid_red
- parameter border: The border to add.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setBorder(_ border: String) -> Self {
return setParam(TransformationParam.BORDER, value: border.replacingOccurrences(of: "#", with: "rgb:"))
}
// MARK: - Set Values - X
/**
Horizontal position for custom-coordinates based cropping, overlay placement and certain region related effects.
- parameter x: The x position to add.
- returns: The same instance of CLDTransformation.
*/
@objc(setXFromInt:)
@discardableResult
open func setX(_ x: Int) -> Self {
return setX(String(x))
}
/**
Horizontal position for custom-coordinates based cropping, overlay placement and certain region related effects.
- parameter x: The x position to add.
- returns: The same instance of CLDTransformation.
*/
@objc(setXFromFloat:)
@discardableResult
open func setX(_ x: Float) -> Self {
return setX(x.cldFloatFormat())
}
/**
Horizontal position for custom-coordinates based cropping, overlay placement and certain region related effects.
- parameter x: The x position to add.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setX(_ x: String) -> Self {
return setParam(TransformationParam.X, expression: CLDExpression(value: x), fallback: x)
}
// MARK: - Set Values - Y
/**
Vertical position for custom-coordinates based cropping and overlay placement.
- parameter y: The y position to add.
- returns: The same instance of CLDTransformation.
*/
@objc(setYFromInt:)
@discardableResult
open func setY(_ y: Int) -> Self {
return setY(String(y))
}
/**
Vertical position for custom-coordinates based cropping and overlay placement.
- parameter y: The y position to add.
- returns: The same instance of CLDTransformation.
*/
@objc(setYFromFloat:)
@discardableResult
open func setY(_ y: Float) -> Self {
return setY(y.cldFloatFormat())
}
/**
Vertical position for custom-coordinates based cropping and overlay placement.
- parameter y: The y position to add.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setY(_ y: String) -> Self {
return setParam(TransformationParam.Y, expression: CLDExpression(value: y), fallback: y)
}
// MARK: - Set Values - Radius
/**
Round the corners of an image or make it completely circular or oval (ellipse).
- parameter radius: The radius to apply.
- returns: The same instance of CLDTransformation.
*/
@objc(setRadiusFromInt:)
@discardableResult
open func setRadius(_ radius: Int) -> Self {
return setRadius(String(radius))
}
/**
Round the corners of an image or make it completely circular or oval (ellipse).
- parameter radius: The radius to apply.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setRadius(_ radius: String) -> Self {
return setParam(TransformationParam.RADIUS, expression: CLDExpression(value: radius), fallback: radius)
}
/**
Support an array value for radius
- parameter radius: - The radius to apply- an array of values
- returns: The same instance of CLDTransformation.
*/
@objc(setRadiusFromArray:)
@discardableResult
open func setRadius(_ radius: [Any]) -> Self {
return setRadius(radius.map{String(describing: $0)}.joined(separator:":"))
}
// MARK: - Set Values - Quality
/**
Set the image quality for the transformation, see CLDQuality for options.
- parameter quality: A CLDQuality instance containing the quality settings.
- returns: The same instance of CLDTransformation.
*/
@objc(setQualityFromQuality:)
@discardableResult
open func setQuality(_ quality: CLDQuality) -> Self {
return setQuality(quality.description)
}
/**
Control the JPEG, WebP, GIF, JPEG XR and JPEG 2000 compression quality. 1 is the lowest quality and 100 is the highest. Reducing quality generates JPG images much smaller in file size. The default values are:
* JPEG: 90
* WebP: 80 (100 quality for WebP is lossless)
* GIF: lossless by default. 80 if the `lossy` flag is added
* JPEG XR and JPEG 2000: 70
- parameter quality: The quality to apply.
- returns: The same instance of CLDTransformation.
*/
@objc(setQualityFromInt:)
@discardableResult
@available(*, deprecated, message: "Use setQuality(CLDQuality)")
open func setQuality(_ quality: Int) -> Self {
return setQuality(String(quality))
}
/**
Control the JPEG, WebP, GIF, JPEG XR and JPEG 2000 compression quality. 1 is the lowest quality and 100 is the highest. Reducing quality generates JPG images much smaller in file size. The default values are:
* JPEG: 90
* WebP: 80 (100 quality for WebP is lossless)
* GIF: lossless by default. 80 if the `lossy` flag is added
* JPEG XR and JPEG 2000: 70
- parameter quality: The quality to apply.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setQuality(_ quality: String) -> Self {
return setParam(TransformationParam.QUALITY, value: quality)
}
// MARK: - Set Values - DefaultImage
/**
Specify the public ID of a placeholder image to use if the requested image or social network picture does not exist.
- parameter defaultImage: The identifier of the default image.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setDefaultImage(_ defaultImage: String) -> Self {
return setParam(TransformationParam.DEFAULT_IMAGE, value: defaultImage)
}
// MARK: - Set Values - Gravity
/**
Decides which part of the image to keep while 'crop', 'pad' and 'fill' crop modes are used.
- parameter gravity: The gravity to apply.
- returns: The same instance of CLDTransformation.
*/
@objc(setGravityWithGravity:)
@discardableResult
open func setGravity(_ gravity: CLDGravity) -> Self {
return setGravity(String(describing: gravity))
}
/**
Decides which part of the image to keep while 'crop', 'pad' and 'fill' crop modes are used.
- parameter gravity: The gravity to apply.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setGravity(_ gravity: String) -> Self {
return setParam(TransformationParam.GRAVITY, value: gravity)
}
// MARK: - Set Values - ColorSpace
/**
Set the transformation color space.
- parameter colorSpace: The color space to set.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setColorSpace(_ colorSpace: String) -> Self {
return setParam(TransformationParam.COLOR_SPACE, value: colorSpace)
}
// MARK: - Set Values - Prefix
/**
Set the transformation prefix.
- parameter prefix: The prefix to set.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setPrefix(_ prefix: String) -> Self {
return setParam(TransformationParam.PREFIX, value: prefix)
}
// MARK: - Set Values - Overlay
/**
Add an overlay over the base image. You can control the dimension and position of the overlay using the width, height, x, y and gravity parameters.
The overlay can take one of the following forms:
identifier can be a public ID of an uploaded image or a specific image kind, public ID and settings.
**You can use the convenience method `addOverlayWithLayer`**
- parameter overlay: The overlay to set.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setOverlay(_ overlay: String) -> Self {
return setParam(TransformationParam.OVERLAY, value: overlay)
}
// MARK: - Set Values - Underlay
/**
Add an underlay image below a base partially-transparent image.
You can control the dimensions and position of the underlay using the width, height, x, y and gravity parameters.
The identifier can be a public ID of an uploaded image or a specific image kind, public ID and settings.
The underlay parameter shares the same features as the overlay parameter.
**You can use the convenience method `addUnderlayWithLayer`**
- parameter underlay: The underlay to set.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setUnderlay(_ underlay: String) -> Self {
return setParam(TransformationParam.UNDERLAY, value: underlay)
}
// MARK: - Set Values - FetchFormat
/**
Force format conversion to the given image format for remote 'fetch' URLs and auto uploaded images that already have a different format as part of their URLs.
- parameter fetchFormat: The fetchFormat to set.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setFetchFormat(_ fetchFormat: String) -> Self {
return setParam(TransformationParam.FETCH_FORMAT, value: fetchFormat)
}
// MARK: - Set Values - Density
/**
Control the density to use while converting a PDF document to images. (range: 50-300, default is 150)
- parameter density: The density to use.
- returns: The same instance of CLDTransformation.
*/
@objc(setDensityWithInt:)
@discardableResult
open func setDensity(_ density: Int) -> Self {
return setDensity(String(density))
}
/**
Control the density to use while converting a PDF document to images. (range: 50-300, default is 150)
- parameter density: The density to use.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setDensity(_ density: String) -> Self {
return setParam(TransformationParam.DENSITY, value: density)
}
// MARK: - Set Values - Page
/**
Given a multi-page file (PDF, animated GIF, TIFF), generate an image of a single page using the given index.
- parameter page: The index of the page to use to use.
- returns: The same instance of CLDTransformation.
*/
@objc(setPageWithInt:)
@discardableResult
open func setPage(_ page: Int) -> Self {
return setPage(String(page))
}
/**
Given a multi-page file (PDF, animated GIF, TIFF), generate an image of a single page using the given index.
- parameter page: The index of the page to use to use.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setPage(_ page: String) -> Self {
return setParam(TransformationParam.PAGE, value: page)
}
// MARK: - Set Values - Delay
/**
Controls the time delay between the frames of an animated image, in milliseconds.
- parameter delay: The delay between the frames of an animated image, in milliseconds.
- returns: The same instance of CLDTransformation.
*/
@objc(setDelayWithInt:)
@discardableResult
open func setDelay(_ delay: Int) -> Self {
return setDelay(String(delay))
}
/**
Controls the time delay between the frames of an animated image, in milliseconds.
- parameter delay: The delay between the frames of an animated image, in milliseconds.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setDelay(_ delay: String) -> Self {
return setParam(TransformationParam.DELAY, value: delay)
}
// MARK: - Set Values - RawTransformation
/**
Add a raw transformation, it will be appended to the other transformation parameter.
the transformation must conform to [Cloudinary's transformation documentation](http://cloudinary.com/documentation/image_transformation_reference)
- parameter rawTransformation: The raw transformation to add.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setRawTransformation(_ rawTransformation: String) -> Self {
return setParam(TransformationParam.RAW_TRANSFORMATION, value: rawTransformation)
}
// MARK: - Set Values - Flags
/**
Set one or more flags that alter the default transformation behavior.
- parameter flags: An array of the flags to apply.
- returns: The same instance of CLDTransformation.
*/
@objc(setFlagsWithArray:)
@discardableResult
open func setFlags(_ flags: [String]) -> Self {
return setFlags(flags.joined(separator: "."))
}
/**
Set one or more flags that alter the default transformation behavior.
- parameter flags: An array of the flags to apply.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setFlags(_ flags: String) -> Self {
return setParam(TransformationParam.FLAGS, value: flags)
}
// MARK: - Set Values - DPR
/**
Deliver the image in the specified device pixel ratio.
- parameter dpr: The DPR ot set.
- returns: The same instance of CLDTransformation.
*/
@objc(setDprWithFloat:)
@discardableResult
open func setDpr(_ dpr: Float) -> Self {
return setDpr(dpr.cldFloatFormat())
}
/**
Deliver the image in the specified device pixel ratio. The parameter accepts any positive float value.
- parameter dpr: The DPR ot set.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setDpr(_ dpr: String) -> Self {
return setParam(TransformationParam.DPR, value: dpr)
}
// MARK: - Set Values - Zoom
/**
Control how much of the original image surrounding the face to keep when using either the 'crop' or 'thumb' cropping modes with face detection. default is 1.0.
- parameter zoom: The zoom ot set.
- returns: The same instance of CLDTransformation.
*/
@objc(setZoomWithFloat:)
@discardableResult
open func setZoom(_ zoom: Float) -> Self {
return setZoom(zoom.cldFloatFormat())
}
/**
Control how much of the original image surrounding the face to keep when using either the 'crop' or 'thumb' cropping modes with face detection. default is 1.0.
- parameter zoom: The zoom ot set.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setZoom(_ zoom: String) -> Self {
return setParam(TransformationParam.ZOOM, value: zoom)
}
// MARK: - Set Values - AspectRatio
/**
Resize or crop the image to a new aspect ratio using a nominator and dominator (e.g. 16 and 9 for 16:9).
This parameter is used together with a specified crop mode that determines how the image is adjusted to the new dimensions.
- parameter nominator: The nominator ot use.
- parameter denominator: The dominator ot use.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setAspectRatio(nominator: Int, denominator: Int) -> Self {
return setAspectRatio("\(nominator):\(denominator)")
}
/**
Resize or crop the image to a new aspect ratio.
This parameter is used together with a specified crop mode that determines how the image is adjusted to the new dimensions.
- parameter aspectRatio: The aspect ratio ot use.
- returns: The same instance of CLDTransformation.
*/
@objc(setAspectRatioWithFloat:)
@discardableResult
open func setAspectRatio(_ aspectRatio: Float) -> Self {
return setAspectRatio(aspectRatio.cldFloatFormat())
}
/**
Resize or crop the image to a new aspect ratio.
This parameter is used together with a specified crop mode that determines how the image is adjusted to the new dimensions.
- parameter aspectRatio: The aspect ratio ot use.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setAspectRatio(_ aspectRatio: String) -> Self {
return setParam(TransformationParam.ASPECT_RATIO, value: aspectRatio)
}
// MARK: - Set Values - CustomPreFunction
/**
Set a custom pre-function, such as a call to a lambda function or a web-assembly function.
- parameter customPreFunction: The custom pre-function to perform, see CLDCustomFunction.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setCustomPreFunction(_ customPreFunction: CLDCustomFunction) -> Self {
return setParam(TransformationParam.CUSTOM_FUNCTION, value: "pre:\(customPreFunction.description)")
}
// MARK: - Set Values - CustomFunction
/**
Set a custom function, such as a call to a lambda function or a web-assembly function.
- parameter customFunction: The custom function to perform, see CLDCustomFunction.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setCustomFunction(_ customFunction: CLDCustomFunction) -> Self {
return setParam(TransformationParam.CUSTOM_FUNCTION, value: customFunction.description)
}
// MARK: - Set Values - AudioCodec
/**
Use the audio_codec parameter to set the audio codec or remove the audio channel completely as follows:
* **none** removes the audio channel
* **aac** (mp4 or flv only)
* **vorbis** (ogv or webm only)
* **mp3** (mp4 or flv only)
- parameter audioCodec: The audio codec ot set.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setAudioCodec(_ audioCodec: String) -> Self {
return setParam(TransformationParam.AUDIO_CODEC, value: audioCodec)
}
// MARK: - Set Values - AudioFrequency
/**
Use the audio_frequency parameter to control the audio sampling frequency.
This parameter represents an integer value in Hz.
See the documentation in the [Video transformations reference table](http://cloudinary.com/documentation/video_manipulation_and_delivery#video_transformations_reference) for the possible values.
- parameter audioFrequency: The audio frequency ot set.
- returns: The same instance of CLDTransformation.
*/
@objc(setAudioFrequencyWithInt:)
@discardableResult
open func setAudioFrequency(_ audioFrequency: Int) -> Self {
return setAudioFrequency(String(audioFrequency))
}
/**
Use the audio_frequency parameter to control the audio sampling frequency.
This parameter represents an integer value in Hz.
See the documentation in the [Video transformations reference table](http://cloudinary.com/documentation/video_manipulation_and_delivery#video_transformations_reference) for the possible values.
- parameter audioFrequency: The audio frequency ot set.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setAudioFrequency(_ audioFrequency: String) -> Self {
return setParam(TransformationParam.AUDIO_FREQUENCY, value: audioFrequency)
}
// MARK: - Set Values - BitRate
/**
Use the bit_rate parameter for advanced control of the video bit rate.
This parameter controls the number of bits used to represent the video data.
The higher the bit rate, the higher the visual quality of the video, but the larger the file size as well.
- parameter bitRate: The bit rate ot set.
- returns: The same instance of CLDTransformation.
*/
@objc(setBitRateWithInt:)
@discardableResult
open func setBitRate(_ bitRate: Int) -> Self {
return setBitRate(String(bitRate))
}
/**
Use the bit_rate parameter for advanced control of the video bit rate.
This parameter controls the number of bits used to represent the video data.
The higher the bit rate, the higher the visual quality of the video, but the larger the file size as well.
- parameter bitRate: The bit rate ot set in kilobytes.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setBitRate(kb bitRate: Int) -> Self {
return setBitRate("\(bitRate)k")
}
/**
Use the bit_rate parameter for advanced control of the video bit rate.
This parameter controls the number of bits used to represent the video data.
The higher the bit rate, the higher the visual quality of the video, but the larger the file size as well.
Bit rate can take one of the following values:
* An integer e.g. 120000.
* A string supporting ‘k’ and ‘m’ (kilobits and megabits respectively) e.g. 500k or 2m.
- parameter bitRate: The bit rate ot set.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setBitRate(_ bitRate: String) -> Self {
return setParam(TransformationParam.BIT_RATE, value: bitRate)
}
// MARK: - Set Values - VideoSampling
/**
The total number of frames to sample from the original video. The frames are spread out over the length of the video, e.g. 20 takes one frame every 5%.
- parameter frames: The video sampling frames ot set.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setVideoSampling(frames: Int) -> Self {
return setVideoSampling(String(frames))
}
/**
Controls the time delay between the frames of an animated image, in milliseconds.
- parameter delay: The delay ot set in milliseconds.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setVideoSampling(delay: Float) -> Self {
return setVideoSampling("\(delay.cldFloatFormat())s")
}
/**
Relevant for conversion of video to animated GIF or WebP.
If not specified, the resulting GIF or WebP samples the whole video (up to 400 frames, at up to 10 frames per second).
By default the duration of the animated image is the same as the duration of the video,
no matter how many frames are sampled from the original video (use the delay parameter to adjust the amount of time between frames).
- parameter videoSampling: The video sampling ot set.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setVideoSampling(_ videoSampling: String) -> Self {
return setParam(TransformationParam.VIDEO_SAMPLING, value: videoSampling)
}
// MARK: - Set Values - Duration
/**
Offset in seconds or percent of a video, normally used together with the start_offset and end_offset parameters. Used to specify:
* The duration the video displays.
* The duration an overlay displays.
- parameter seconds: The duration to set in seconds.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setDuration(seconds: Float) -> Self {
return setDuration(seconds.cldFloatFormat())
}
/**
Offset in seconds or percent of a video, normally used together with the start_offset and end_offset parameters. Used to specify:
* The duration the video displays.
* The duration an overlay displays.
- parameter percent: The duration percent to set.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setDuration(percent: Int) -> Self {
return setDuration("\(percent)p")
}
/**
Offset in seconds or percent of a video, normally used together with the start_offset and end_offset parameters. Used to specify:
* The duration the video displays.
* The duration an overlay displays.
- parameter duration: The duration to set in seconds.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setDuration(_ duration: String) -> Self {
return setParam(TransformationParam.DURATION, value: duration)
}
// MARK: - Set Values - StartOffset
/**
Set an offset in seconds to cut a video at the start.
- parameter seconds: The start time to set.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setStartOffset(seconds: Float) -> Self {
return setStartOffset(seconds.cldFloatFormat())
}
/**
Set an offset in percent to cut a video at the start.
- parameter percent: The percent of time to cut.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setStartOffset(percent: Int) -> Self {
return setStartOffset("\(percent)p")
}
/**
Set an offset in seconds or percent of a video to cut a video at the start.
- parameter duration: The start time to set.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setStartOffset(_ duration: String) -> Self {
return setParam(TransformationParam.START_OFFSET, value: duration)
}
// MARK: - Set Values - EndOffset
/**
Set an offset in seconds to cut a video at the end.
- parameter seconds: The end time to set.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setEndOffset(seconds: Float) -> Self {
return setEndOffset(seconds.cldFloatFormat())
}
/**
Set an offset in percent to cut a video at the end.
- parameter percent: The percent of time to cut.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setEndOffset(percent: Int) -> Self {
return setEndOffset("\(percent)p")
}
/**
Set an offset in seconds or percent of a video to cut a video at the end.
- parameter duration: The end time to set.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setEndOffset(_ duration: String) -> Self {
return setParam(TransformationParam.END_OFFSET, value: duration)
}
// MARK: - Set Values - VideoCodecAndProfileAndLevel
/**
Used to determine the video codec, video profile and level to use.
You can set this parameter to auto instead.
- parameter videoCodec: The video codec to set.
- parameter videoProfile: The video profile to set.
- parameter level: The level to set.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setVideoCodecAndProfileAndLevel(_ videoCodec: String, videoProfile: String, level: String? = nil) -> Self {
return level == nil ? setVideoCodec("\(videoCodec):\(videoProfile)") : setVideoCodec("\(videoCodec):\(videoProfile):\(level!)")
}
// MARK: - Set Values - VideoCodecAndProfileAndLevelAndBframes
/**
Used to determine the video codec, video profile and level to use.
You can set this parameter to auto instead.
- parameter videoCodec: The video codec to set.
- parameter videoProfile: The video profile to set.
- parameter level: The level to set.
- parameter bframes: Should use B-frames.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setVideoCodecAndProfileAndLevelAndBFrames(_ videoCodec: String, videoProfile: String, level: String? = nil, bframes: Bool? = nil) -> Self {
var videoCodec = "\(videoCodec):\(videoProfile)"
if let level = level {
videoCodec.append(":\(level)" + (bframes == false ? ":bframes_no" : ""))
}
return setVideoCodec(videoCodec)
}
// MARK: - Set Values - VideoCodec
/**
Used to determine the video codec to use.
You can set this parameter to auto instead.
- parameter videoCodec: The video codec to set.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setVideoCodec(_ videoCodec: String) -> Self {
return setParam(TransformationParam.VIDEO_CODEC, value: videoCodec)
}
// MARK: Setters
fileprivate func setParam(_ key: TransformationParam, value: String) -> Self {
return setParam(key.rawValue, value: value)
}
fileprivate func setParam(_ key: TransformationParam, expression: CLDExpression, fallback: String) -> Self {
if expression.currentValue.isEmpty {
return setParam(key, value: fallback)
} else {
return setParam(key, value: expression.asString())
}
}
@discardableResult
open func setParam(_ key: String, value: String) -> Self {
currentTransformationParams[key] = value
return self
}
// MARK: - Convenience
/**
A convenience method to set video cutting in seconds. Must provide an array with exactly 2 values: 1. start offset. 2. end offset.
- parameter seconds: The array of both start and end offsets to set.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
func setOffset(seconds: [Float]) -> Self {
guard let
start = seconds.first,
let end = seconds.last else {
return self
}
return setOffset([start.cldFloatFormat(), end.cldFloatFormat()])
}
/**
A convenience method to set video cutting in percent. Must provide an array with exactly 2 values: 1. start offset. 2. end offset.
- parameter seconds: The array of both start and end offsets to set.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
func setOffset(percents: [Int]) -> Self {
guard let
start = percents.first,
let end = percents.last else {
return self
}
return setOffset(["\(start)p", "\(end)p"])
}
/**
A convenience method to set video cutting in seconds or percent.
Should provide an array with 2 values: 1. start offset. 2. end offset. Or one value for both.
- parameter seconds: The array of both start and end offsets to set.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
func setOffset(_ durations: [String]) -> Self {
guard let
start = durations.first,
let end = durations.last else {
return self
}
setStartOffset(start)
setEndOffset(end)
return self
}
/**
Shortcut to set video cutting in seconds using a combination of start_offset and end_offset values.
- parameter startSeconds: The start time to set.
- parameter endSeconds: The end time to set.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setStartOffsetAndEndOffset(startSeconds: Float, endSeconds: Float) -> Self {
return setStartOffset(seconds: startSeconds).setEndOffset(seconds: endSeconds)
}
/**
Shortcut to set video cutting in percent of video using a combination of start_offset and end_offset values.
- parameter startPercent: The start time to set.
- parameter endPercent: The end time to set.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setStartOffsetAndEndOffset(startPercent: Int, endPercent: Int) -> Self {
return setStartOffset(percent: startPercent).setEndOffset(percent: endPercent)
}
/**
Set the frames-per-second of the video.
- parameter fps: A CLDFps configured with the required parameters (see CLDFps for details).
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setFps(_ fps: CLDFps) -> Self {
return setParam(TransformationParam.FPS, value: fps.description)
}
/**
Set the frames-per-second of the video.
- parameter fps: Frames per second as string.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
@objc(setFpsWithString:)
open func setFps(_ fps: String) -> Self {
return setFps(CLDFps.fromString(fps))
}
/**
Set the frames-per-second of the video.
- parameter fps: Frames per seconds as float.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
@objc(setFpsWithFloat:)
open func setFps(_ fps: Float) -> Self {
return setFps(CLDFps.fromFloat(fps))
}
/**
Set an overlay using the helper class CLDLayer.
- parameter layer: The layer to add as an overlay.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setOverlayWithLayer(_ layer: CLDLayer) -> Self {
if let layerString = layer.asString() {
return setOverlay(layerString)
}
else {
return setOverlay("")
}
}
/**
Set an underlay using the helper class CLDLayer.
- parameter layer: The layer to add as an underlay.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setUnderlayWithLayer(_ layer: CLDLayer) -> Self {
if let layerString = layer.asString() {
return setUnderlay(layerString)
}
else {
return setUnderlay("")
}
}
/**
A convenience method to set the transformation X and Y parameters.
- parameter point: The top left pont to set.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setTopLeftPoint(_ point: CGPoint) -> Self {
return setX(Float(point.x)).setY(Float(point.y))
}
@discardableResult
open func setKeyframeInterval(interval: Float) -> Self {
return setKeyframeInterval(interval.description)
}
@discardableResult
open func setKeyframeInterval(_ interval: String) -> Self {
return setParam(TransformationParam.KEYFRAME_INTERVAL, value: interval)
}
@discardableResult
open func setStreamingProfile(_ streamingProfile: String) -> Self {
return setParam(TransformationParam.STREAMING_PROFILE, value: streamingProfile)
}
// MARK: - Actions
/**
Cloudinary supports powerful image transformations that are applied on the fly using dynamic URLs,
and you can also combine multiple transformations together as part of a single delivery request, e.g. crop an image and then add a border.
In certain cases you may want to perform additional transformations on the result of a single transformation request.
In order to do that, you can chain the transformations together.
In practice, the chain allows you to start seting properties to a new transformation,
which will be chained to the transformation you worked on, even though you still use the same CLDTransformation instance.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func chain() -> Self {
guard !currentTransformationParams.isEmpty else { return self }
transformations.append(currentTransformationParams)
currentTransformationParams = [:]
return self
}
public func asString() -> String? {
chain()
var components: [String] = []
for params in self.transformations {
if let singleTransStringRepresentation = self.getStringRepresentationFromParams(params) {
components.append(singleTransStringRepresentation)
}
else {
return nil
}
}
return components.joined(separator: "/")
}
// MARK: - Private
internal func getStringRepresentationFromParams(_ params: [String : String]) -> String? {
let emptyParams = params.filter { $0.0.isEmpty || $0.1.isEmpty }
if !emptyParams.isEmpty {
printLog(.error, text: "An empty string key or value are not allowed.")
return nil
}
let components: [String] = params.sorted { $0.0 < $1.0 }
.filter { $0.0 != TransformationParam.RAW_TRANSFORMATION.rawValue &&
$0.0 != TransformationParam.VARIABLES.rawValue &&
$0.0 != TransformationParam.IF_PARAM.rawValue &&
!$0.1.isEmpty }
.compactMap {
return "\($0)_\($1)"
}
var finalComponents: [String] = [String]()
if let ifConditions = params[TransformationParam.IF_PARAM.rawValue], !ifConditions.isEmpty {
finalComponents.append(TransformationParam.IF_PARAM.rawValue + CLDTransformation.elementsSeparator + ifConditions)
}
if let complexVariables = params[TransformationParam.VARIABLES.rawValue], !complexVariables.isEmpty {
finalComponents.append(complexVariables)
}
finalComponents.append(contentsOf: components)
if let rawTrans = params[TransformationParam.RAW_TRANSFORMATION.rawValue], !rawTrans.isEmpty {
finalComponents.append(rawTrans)
}
return finalComponents.joined(separator: CLDTransformation.transformationContentSeparator)
}
// MARK: - Params
internal enum TransformationParam: String {
case WIDTH = "w"
case HEIGHT = "h"
case NAMED = "t"
case CROP = "c"
case BACKGROUND = "b"
case COLOR = "co"
case EFFECT = "e"
case ANGLE = "a"
case OPACITY = "o"
case BORDER = "bo"
case X = "x"
case Y = "y"
case RADIUS = "r"
case QUALITY = "q"
case DEFAULT_IMAGE = "d"
case GRAVITY = "g"
case COLOR_SPACE = "cs"
case PREFIX = "p"
case OVERLAY = "l"
case UNDERLAY = "u"
case FETCH_FORMAT = "f"
case DENSITY = "dn"
case PAGE = "pg"
case DELAY = "dl"
case FLAGS = "fl"
case DPR = "dpr"
case ZOOM = "z"
case ASPECT_RATIO = "ar"
case CUSTOM_FUNCTION = "fn"
case AUDIO_CODEC = "ac"
case AUDIO_FREQUENCY = "af"
case BIT_RATE = "br"
case VIDEO_SAMPLING = "vs"
case DURATION = "du"
case START_OFFSET = "so"
case END_OFFSET = "eo"
case VIDEO_CODEC = "vc"
case RAW_TRANSFORMATION = "raw_transformation"
case VARIABLES = "variables"
case IF_PARAM = "if"
case KEYFRAME_INTERVAL = "ki"
case FPS = "fps"
case STREAMING_PROFILE = "sp"
}
// MARK: CLDBaseParam
@objc public class CLDBaseParam: NSObject {
fileprivate let param: String
fileprivate init(_ components: [String]) {
self.param = components.joined(separator: ":")
}
fileprivate convenience init(_ components: String...) {
self.init(components)
}
override public var description: String {
get {
return param
}
}
}
// MARK: CLDQuality
/**
Image quality configuration object
*/
@objc public class CLDQuality: CLDBaseParam {
/**
Build an instance of CLDQuality configured for fixed quality.
- parameter level: Quality level to set. Valid range is 1 through 100.
*/
public static func fixed(_ level: Int) -> CLDQuality {
return CLDQuality(level.description)
}
/**
Build an instance of CLDQuality configured for automatic quality. See CLDAutoQuality enum for details.
- parameter level: Auto quality level.
*/
public static func auto(_ level: CLDQualityAuto? = nil) -> CLDQuality {
if let level = level {
return CLDQuality("auto", level.description)
} else {
return CLDQuality("auto")
}
}
/**
Build an instance of CLDQuality configured to use jpegmini addon for automatic quality.
*/
public static func jpegMini() -> CLDQuality {
return CLDQuality("jpegmini")
}
}
/**
Automatic optimal quality settings: the smallest file size without affecting their perceptual quality.
* best: Automatically calculate the optimal quality for images using a less aggressive algorithm
* good: Automatically calculate the optimal quality for an image
* eco: Automatically calculate the optimal quality for images using a more aggressive algorithm
* low: Automatically calculate the optimal quality for images using the most aggressive algorithm
*/
@objc public enum CLDQualityAuto: Int, CustomStringConvertible {
case best, good, eco, low
public var description: String {
get {
switch self {
case .best: return "best"
case .good: return "good"
case .eco: return "eco"
case .low: return "low"
}
}
}
}
// MARK: CLDCustomFunction
/**
Custom function configuration object
*/
@objc public class CLDCustomFunction: CLDBaseParam {
/**
Build an instance of CLDCustomFunction configured for web-assembly custom function.
- parameter publicId: Public id of the web assembly file.
*/
@objc public static func wasm(_ publicId: String) -> CLDCustomFunction {
return CLDCustomFunction("wasm", publicId)
}
/**
Build an instance of CLDCustomFunction configured for remote lambda custom function.
- parameter url: public url of the aws lambda function
*/
@objc public static func remote(_ url: String) -> CLDCustomFunction {
return CLDCustomFunction("remote", url.cldBase64UrlEncode())
}
}
// MARK: FPS
/**
FPS parameters configuration object. For simple cases you can pass a float/string
directly to CLDTransformation.setFps(). This class is used for more complex values (e.g. ranges).
*/
@objc public class CLDFps: CLDBaseParam {
/**
Build an instance of CLDFps based on a string.
- parameter fps: The fps value as string
*/
public static func fromString(_ fps: String) -> CLDFps {
return CLDFps(fps);
}
/**
Build an instance of CLDFps based on a float.
- parameter fps: The fps value as float
*/
public static func fromFloat(_ fps: Float) -> CLDFps {
return CLDFps(fps.cldCleanFormat());
}
/**
Build an instance of CLDFps based on a range of string values. Note: At least one parameter must not be nil.
- parameter start: The fps start value as string
- parameter start: The fps end value as string
*/
public static func range(start: String? = nil, end: String? = nil) -> CLDFps {
return CLDFps("\(start ?? "")-\(end ?? "")");
}
/**
Build an instance of CLDFps based on a range of float values. Note: At least one parameter must not be nil.
- parameter start: The fps start value as float
- parameter start: The fps end value as float
*/
public static func range(start: Float? = nil, end: Float? = nil) -> CLDFps {
return CLDFps("\(start?.cldCleanFormat() ?? "")-\(end?.cldCleanFormat() ?? "")");
}
}
// MARK: Crop
@objc public enum CLDCrop: Int, CustomStringConvertible {
case fill, crop, scale, fit, limit, mFit, lFill, pad, lPad, mPad, thumb, imaggaCrop, imaggaScale
public var description: String {
get {
switch self {
case .fill: return "fill"
case .crop: return "crop"
case .scale: return "scale"
case .fit: return "fit"
case .limit: return "limit"
case .mFit: return "mfit"
case .lFill: return "lfill"
case .pad: return "pad"
case .lPad: return "lpad"
case .mPad: return "mpad"
case .thumb: return "thumb"
case .imaggaCrop: return "imagga_crop"
case .imaggaScale: return "imagga_scale"
}
}
}
}
// MARK: Effect
@objc public enum CLDEffect: Int, CustomStringConvertible {
case hue, red, green, blue, negate, brightness, sepia, grayscale, blackwhite, saturation, colorize, contrast, autoContrast, vibrance, autoColor, improve, autoBrightness, fillLight, viesusCorrect, gamma, screen, multiply, overlay, makeTransparent, trim, shadow, distort, shear, displace, oilPaint, redeye, advRedeye, vignette, gradientFade, pixelate, pixelateRegion, pixelateFaces, blur, blurRegion, blurFaces, sharpen, unsharpMask, orderedDither, art, assistColorblind, preview
public var description: String {
get {
switch self {
case .hue: return "hue"
case .red: return "red"
case .green: return "green"
case .blue: return "blue"
case .negate: return "negate"
case .brightness: return "brightness"
case .sepia: return "sepia"
case .grayscale: return "grayscale"
case .blackwhite: return "blackwhite"
case .saturation: return "saturation"
case .colorize: return "colorize"
case .contrast: return "contrast"
case .autoContrast: return "auto_contrast"
case .vibrance: return "vibrance"
case .autoColor: return "auto_color"
case .improve: return "improve"
case .autoBrightness: return "auto_brightness"
case .fillLight: return "fill_light"
case .viesusCorrect: return "viesus_correct"
case .gamma: return "gamma"
case .screen: return "screen"
case .multiply: return "multiply"
case .overlay: return "overlay"
case .makeTransparent: return "make_transparent"
case .trim: return "trim"
case .shadow: return "shadow"
case .distort: return "distort"
case .shear: return "shear"
case .displace: return "displace"
case .oilPaint: return "oil_paint"
case .redeye: return "redeye"
case .advRedeye: return "adv_redeye"
case .vignette: return "vignette"
case .gradientFade: return "gradient_fade"
case .pixelate: return "pixelate"
case .pixelateRegion: return "pixelate_region"
case .pixelateFaces: return "pixelate_faces"
case .blur: return "blur"
case .blurRegion: return "blur_region"
case .blurFaces: return "blur_faces"
case .sharpen: return "sharpen"
case .unsharpMask: return "unsharp_mask"
case .orderedDither: return "ordered_dither"
case .art: return "art"
case .assistColorblind: return "assist_colorblind"
case .preview: return "preview"
}
}
}
}
// MARK: Artistic Effects
@objc public enum CLDArtFilters: Int, CustomStringConvertible {
case alDente, athena, audrey, aurora, daguerre, eucalyptus, fes, frost, hairspray, hokusai, incognito, linen, peacock, primavera, quartz, redRock, refresh, sizzle, sonnet, ukulele, zorro
public var description: String {
get {
switch self {
case .alDente: return "al_dente"
case .athena: return "athena"
case .audrey: return "audrey"
case .aurora: return "aurora"
case .daguerre: return "daguerre"
case .eucalyptus: return "eucalyptus"
case .fes: return "fes"
case .frost: return "frost"
case .hairspray: return "hairspray"
case .hokusai: return "hokusai"
case .incognito: return "incognito"
case .linen: return "linen"
case .peacock: return "peacock"
case .primavera: return "primavera"
case .quartz: return "quartz"
case .redRock: return "red_rock"
case .refresh: return "refresh"
case .sizzle: return "sizzle"
case .sonnet: return "sonnet"
case .ukulele: return "ukulele"
case .zorro: return "zorro"
}
}
}
}
// MARK: Gravity
@objc public enum CLDGravity: Int, CustomStringConvertible {
case center, auto, face, faceCenter, faces, facesCenter, advFace, advFaces, advEyes, north, northWest, northEast, south, southWest, southEast, east, west, xyCenter, custom, customFace, customFaces, customAdvFace, customAdvFaces, autoOcrText, ocrText, ocrTextAdvOcr
public var description: String {
get {
switch self {
case .center: return "center"
case .auto: return "auto"
case .face: return "face"
case .faceCenter: return "face:center"
case .faces: return "faces"
case .facesCenter: return "faces:center"
case .advFace: return "adv_face"
case .advFaces: return "adv_faces"
case .advEyes: return "adv_eyes"
case .north: return "north"
case .northWest: return "north_west"
case .northEast: return "north_east"
case .south: return "south"
case .southWest: return "south_west"
case .southEast: return "south_east"
case .west: return "west"
case .east: return "east"
case .xyCenter: return "xy_center"
case .custom: return "custom"
case .customFace: return "custom:face"
case .customFaces: return "custom:faces"
case .customAdvFace: return "custom:adv_face"
case .customAdvFaces: return "custom:adv_faces"
case .autoOcrText: return "auto:ocr_text"
case .ocrText: return "ocr_text"
case .ocrTextAdvOcr: return "ocr_text:adv_ocr"
}
}
}
}
}
// MARK: - Expressions
extension CLDTransformation
{
@objc(setWidthWithExpression:)
@discardableResult
public func setWidth(_ input: CLDExpression) -> Self {
return setParam(TransformationParam.WIDTH, value: input.asString())
}
@objc(setHeightWithExpression:)
@discardableResult
public func setHeight(_ input: CLDExpression) -> Self {
return setParam(TransformationParam.HEIGHT, value: input.asString())
}
@objc(setXFromExpression:)
@discardableResult
public func setX(_ input: CLDExpression) -> Self {
return setParam(TransformationParam.X, value: input.asString())
}
@objc(setYFromExpression:)
@discardableResult
public func setY(_ input: CLDExpression) -> Self {
return setParam(TransformationParam.Y, value: input.asString())
}
@objc(setRadiusFromExpression:)
@discardableResult
public func setRadius(_ input: CLDExpression) -> Self {
return setParam(TransformationParam.RADIUS, value: input.asString())
}
@objc(setStartOffsetFromExpression:)
@discardableResult
public func setStartOffset(_ input: CLDExpression) -> Self {
return setParam(TransformationParam.START_OFFSET, value: input.asString())
}
@objc(setEndOffsetFromExpression:)
@discardableResult
public func setEndOffset(_ input: CLDExpression) -> Self {
return setParam(TransformationParam.END_OFFSET, value: input.asString())
}
}
// MARK: - Condition Expression
extension CLDTransformation
{
// MARK: - ifCondition with content
@objc(ifConditionFromString:)
@discardableResult
public func ifCondition(_ condition: String) -> Self {
let conditionObject = CLDConditionExpression(value: condition)
guard !conditionObject.currentValue.isEmpty else {
return setParam(TransformationParam.IF_PARAM, value: condition)
}
return ifCondition(conditionObject)
}
@discardableResult
public func ifCondition(_ condition: CLDConditionExpression) -> Self {
return setParam(TransformationParam.IF_PARAM, value: condition.asString())
}
@discardableResult
public func ifCondition(_ condition: CLDConditionExpression, then transformation: CLDExpression) -> Self {
return ifCondition(condition).setParam(transformation.currentKey, value: transformation.currentValue)
}
// MARK: - ifCondition state
@objc(ifCondition)
@discardableResult
public func ifCondition() -> CLDConditionExpression {
let condition = CLDConditionExpression()
condition.relatedTransformation = self
currentCondition = condition
return condition
}
// MARK: - ifElse
@objc(ifElse)
@discardableResult
public func ifElse() -> Self {
chain()
return setParam(TransformationParam.IF_PARAM, value: "else")
}
// MARK: - endIf
@objc(endIf)
@discardableResult
public func endIf() -> Self {
chain()
guard !transformations.isEmpty else { return self }
let transformSize = transformations.count
let loopSize = transformSize - 1
for index in (0...loopSize).reversed() {
var segment = transformations[index]; // [..., {if: "w_gt_1000",c: "fill", w: 500}, ...]
if let value = segment[TransformationParam.IF_PARAM.rawValue] { // if: "w_gt_1000"
if value == "end" {
break
}
if segment.keys.count > 1 {
segment.removeValue(forKey: TransformationParam.IF_PARAM.rawValue) // {c: fill, w: 500}
transformations[index] = segment // [..., {c: fill, w: 500}, ...]
transformations.insert([TransformationParam.IF_PARAM.rawValue:value], at: index) // [..., "if_w_gt_1000", {c: fill, w: 500}, ...]
}
// otherwise keep looking for if_condition
if value != "else" {
break
}
}
}
setParam(TransformationParam.IF_PARAM.rawValue, value: "end")
return chain()
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/CLDVariable.swift
================================================
//
// CLDVariable.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
internal func CLDThrowFatalError(with message: String) {
// fatalError(message)
}
@objcMembers open class CLDVariable: NSObject {
public var value: String
public var name : String {
didSet { self.addNamePrefixIfNeeded() }
}
internal var isValid : Bool {
return checkValidName(name) && !value.isEmpty
}
static public let variableNamePrefix : String = "$"
static private let collectionPrefix : String = "!"
static private let collectionSuffix : String = "!"
static private let collectionSeparator: String = ":"
static internal let elementsSeparator: String = "_"
static private let separator: String = ","
static private let nameRegex: String = "^\\$[a-zA-Z][a-zA-Z0-9]*$"
// MARK: - Init
public override init() {
self.name = String()
self.value = String()
super.init()
self.addNamePrefixIfNeeded()
}
@objc(initWithName:stringValue:)
public init(name variableName: String, value variableValue: String) {
self.name = variableName
if variableValue.hasPrefix(CLDVariable.variableNamePrefix) {
self.value = CLDExpression(value: variableValue).asString()
} else {
self.value = variableValue
}
super.init()
self.addNamePrefixIfNeeded()
}
@objc(initWithName:doubleValue:)
public init(name variableName: String, value variableValue: Double) {
self.name = variableName
self.value = String(variableValue)
super.init()
self.addNamePrefixIfNeeded()
}
@objc(initWithName:intValue:)
public init(name variableName: String, value variableValue: Int) {
self.name = variableName
self.value = String(variableValue)
super.init()
self.addNamePrefixIfNeeded()
}
public init(name variableName: String, values: [String]) {
self.name = variableName
if values.isEmpty {
self.value = String()
} else {
self.value = CLDVariable.collectionPrefix + values.joined(separator: CLDVariable.collectionSeparator) + CLDVariable.collectionSuffix
}
super.init()
self.addNamePrefixIfNeeded()
}
// MARK: - Public methods
public func asString() -> String {
guard checkValidName(name) else { return String() }
return name + CLDVariable.elementsSeparator + value
}
public func asParams() -> [String : String] {
guard checkValidName(name) else { return [String:String]() }
return [name:value]
}
// MARK: - Private methods
private func checkValidName(_ name: String) -> Bool {
let regex = try! NSRegularExpression(pattern: CLDVariable.nameRegex, options: .caseInsensitive)
let range = NSRange(location: 0, length: name.count)
let isValid = regex.firstMatch(in: name, options: [], range: range) != nil
if !isValid {
CLDThrowFatalError(with: "\(#function) failed!")
}
return isValid
}
private func addNamePrefixIfNeeded() {
guard !checkValidName(name) else { return }
guard !name.hasPrefix(CLDVariable.variableNamePrefix) else { return }
name = addPrefix(to: name)
}
private func addPrefix(to name: String) -> String {
return CLDVariable.variableNamePrefix + name
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/Layers/CLDFetchLayer.swift
================================================
//
// CLDFetchLayer.swift
// Cloudinary
//
// Created by Nitzan Jaitman on 19/02/2019.
// Copyright © 2019 Cloudinary. All rights reserved.
//
import Foundation
@objcMembers open class CLDFetchLayer: CLDLayer {
// MARK: - Init
/**
Initialize a CLDFetchLayer instance.
- parameter url: The url of the remote resource to fetch
- returns: The new CLDFetchLayer instance.
*/
public init(url: String) {
super.init()
setPublicId(publicId: url.cldBase64UrlEncode())
resourceType = String(describing: LayerResourceType.fetch)
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/Layers/CLDLayer.swift
================================================
//
// CLDLayer.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
The CLDLayer is used to help adding an overlay or underlay layer to a transformation.
*/
@objcMembers open class CLDLayer: NSObject {
internal var publicId: String?
internal var format: String?
internal var resourceType: String?
internal var type: String?
// MARK: - Init
/**
Initialize a CLDLayer instance.
-returns: The new CLDLayer instance.
*/
@discardableResult
public override init() {
super.init()
}
// MARK: - Set Values
/**
The identifier of the image to use as a layer.
- parameter publicId: The identifier of the image to use as a layer.
- returns: The same instance of CLDLayer.
*/
@discardableResult
open func setPublicId(publicId: String) -> CLDLayer {
self.publicId = publicId
return self
}
/**
The format of the image to use as a layer.
- parameter format: The format of the image to use as a layer.
- returns: The same instance of CLDLayer.
*/
@discardableResult
open func setFormat(format: String) -> CLDLayer {
self.format = format
return self
}
/**
Set the layer resource type.
- parameter resourceType: The layer resource type.
- returns: The same instance of CLDLayer.
*/
@objc(setResourceTypeFromLayerResourceType:)
@discardableResult
open func setResourceType(_ resourceType: LayerResourceType) -> CLDLayer {
return setResourceType(String(describing: resourceType))
}
/**
Set the layer resource type.
- parameter resourceType: The layer resource type.
- returns: The same instance of CLDLayer.
*/
@objc(setResourceTypeFromString:)
@discardableResult
open func setResourceType(_ resourceType: String) -> CLDLayer {
self.resourceType = resourceType
return self
}
/**
Set the layer type.
- parameter type: The layer type.
- returns: The same instance of CLDLayer.
*/
@objc(setTypeFromType:)
@discardableResult
open func setType(_ type: CLDType) -> CLDLayer {
return setType(String(describing: type))
}
/**
Set the layer type.
- parameter rawType: The layer type.
- returns: The same instance of CLDLayer.
*/
@objc(setTypeFromString:)
@discardableResult
open func setType(_ rawType: String) -> CLDLayer {
type = rawType
return self
}
// MARK: - Helpers
fileprivate func isResourceTypeTextual(_ resourceType: String?) -> Bool {
guard let resourceType = resourceType else {
return false
}
return resourceType == String(describing: LayerResourceType.text) || resourceType == String(describing: LayerResourceType.subtitles)
}
internal func getFinalPublicId() -> String? {
var finalPublicId: String?
if let pubId = publicId , !pubId.isEmpty, let format = format , !format.isEmpty {
finalPublicId = "\(pubId).\(format)"
}
return finalPublicId ?? publicId
}
internal func getStringComponents() -> [String]? {
var components: [String] = []
if publicId == nil, let resourceType = resourceType , resourceType != String(describing: LayerResourceType.text) {
printLog(.error, text: "Must supply publicId for non-text layer")
return nil
}
if let resourceType = resourceType , resourceType != String(describing: LayerResourceType.image) {
components.append(resourceType)
}
if let type = type , type != String(describing: CLDType.upload) {
components.append(type)
}
if !isResourceTypeTextual(resourceType) {
if let pubId = getFinalPublicId() , !pubId.isEmpty {
components.append(pubId.replacingOccurrences(of: "/", with: ":"))
}
}
return components
}
// MARK: - Actions
internal func asString() -> String? {
guard let components = self.getStringComponents() else {
return nil
}
return components.joined(separator: ":")
}
// MARK: - Params
@objc public enum LayerResourceType: Int, CustomStringConvertible {
case image, raw, auto, text, subtitles, video, fetch
public var description: String {
get {
switch self {
case .image: return "image"
case .raw: return "raw"
case .auto: return "auto"
case .text: return "text"
case .subtitles: return "subtitles"
case .video: return "video"
case .fetch: return "fetch"
}
}
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/Layers/CLDSubtitlesLayer.swift
================================================
//
// CLDSubtitlesLayer.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDSubtitlesLayer: CLDTextLayer {
// MARK: - Init
/**
Initialize a CLDLayer instance.
-returns: The new CLDLayer instance.
*/
public override init() {
super.init()
resourceType = String(describing: LayerResourceType.subtitles)
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/Layers/CLDTextLayer.swift
================================================
//
// CLDTextLayer.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDTextLayer: CLDLayer {
internal var text: String?
internal var fontFamily: String?
internal var fontSize: String?
internal var fontStyle: String?
internal var fontWeight: String?
internal var textDecoration: String?
internal var textAlign: String?
internal var stroke: String?
internal var letterSpacing: String?
internal var lineSpacing: String?
internal var fontAntialiasing: String?
internal var fontHinting: String?
// MARK: - Init
/**
Initialize a CLDTextLayer instance.
-returns: The new CLDTextLayer instance.
*/
public override init() {
super.init()
resourceType = String(describing: LayerResourceType.text)
}
// MARK: - Set Values
/**
Add a text caption layer.
- parameter text: The text to use as a caption layer.
- returns: The same instance of CLDTextLayer.
*/
open func setText(text: String) -> CLDTextLayer {
self.text = text
return self
}
/**
Set the name of a font family. e.g. `arial`.
- parameter fontFamily: The layer font family.
- returns: The same instance of CLDTextLayer.
*/
open func setFontFamily(fontFamily: String) -> CLDTextLayer {
self.fontFamily = fontFamily
return self
}
/**
Set the font size in pixels. e.g. 12.
- parameter fontSize: The layer font size.
- returns: The same instance of CLDTextLayer.
*/
@objc(setFontSizeFromInt:)
open func setFontSize(_ fontSize: Int) -> CLDTextLayer {
return setFontSize(String(fontSize))
}
/**
Set the font size in pixels. e.g. 12.
- parameter fontSize: The layer font size.
- returns: The same instance of CLDTextLayer.
*/
@objc(setFontSizeFromString:)
open func setFontSize(_ fontSize: String) -> CLDTextLayer {
self.fontSize = fontSize
return self
}
/**
Set the font style.
- parameter fontStyle: The layer font style.
- returns: The same instance of CLDTextLayer.
*/
@objc(setFontStyleFromLayerFontStyle:)
open func setFontStyle(_ fontStyle: CLDFontStyle) -> CLDTextLayer {
return setFontStyle(String(describing: fontStyle))
}
/**
Set the font style. Possible values: normal (default value) or italic. e.g., italic
- parameter fontStyle: The layer font style.
- returns: The same instance of CLDTextLayer.
*/
@objc(setFontStyleFromString:)
open func setFontStyle(_ fontStyle: String) -> CLDTextLayer {
self.fontStyle = fontStyle
return self
}
/**
Set the text weight.
- parameter fontWeight: The layer font weight.
- returns: The same instance of CLDTextLayer.
*/
@objc(setFontWeightFromLayerFontWeight:)
open func setFontWeight(_ fontWeight: CLDFontWeight) -> CLDTextLayer {
return setFontWeight(String(describing: fontWeight))
}
/**
Set the text weight. Possible values: normal (default value) or bold.
- parameter fontWeight: The layer font weight.
- returns: The same instance of CLDTextLayer.
*/
@objc(setFontWeightFromString:)
open func setFontWeight(_ fontWeight: String) -> CLDTextLayer {
self.fontWeight = fontWeight
return self
}
/**
Set the text decoration. Possible values: none (default value), underline or strikethrough.
- parameter textDecoration: The layer text Decoration.
- returns: The same instance of CLDTextLayer.
*/
@objc(setTextDecorationString:)
open func setTextDecoration(_ textDecoration: String) -> CLDTextLayer {
self.textDecoration = textDecoration
return self
}
/**
Set the text alignment. Possible values: left (default value), center, right, end, start or justify.
- parameter textAlign: The layer text alignment.
- returns: The same instance of CLDTextLayer.
*/
@objc(setTextAlignString:)
open func setTextAlign(_ textAlign: String) -> CLDTextLayer {
self.textAlign = textAlign
return self
}
/**
Set the font stroke (border).
Possible values: none (default value) or stroke.
Set the color and weight of the stroke with the border parameter.
- parameter stroke: The layer text stroke.
- returns: The same instance of CLDTextLayer.
*/
@objc(setStrokeString:)
open func setStroke(_ stroke: String) -> CLDTextLayer {
self.stroke = stroke
return self
}
/**
Set the spacing between the letters in pixels. Can be a positive or negative.
- parameter letterSpacing: The layer letter Spacing.
- returns: The same instance of CLDTextLayer.
*/
@objc(setLetterSpacingFromInt:)
open func setLetterSpacing(_ letterSpacing: Int) -> CLDTextLayer {
return setLetterSpacing(String(letterSpacing))
}
/**
Set the spacing between the letters in pixels. Can be a positive or negative.
- parameter letterSpacing: The layer letter Spacing.
- returns: The same instance of CLDTextLayer.
*/
@objc(setLetterSpacingFromFloat:)
open func setLetterSpacing(_ letterSpacing: Float) -> CLDTextLayer {
return setLetterSpacing(letterSpacing.cldFloatFormat())
}
/**
Set the spacing between the letters in pixels. Can be a positive or negative.
- parameter letterSpacing: The layer letter Spacing.
- returns: The same instance of CLDTextLayer.
*/
@objc(setLetterSpacingString:)
open func setLetterSpacing(_ letterSpacing: String) -> CLDTextLayer {
self.letterSpacing = letterSpacing
return self
}
/**
Set the spacing between the lines in pixels (only relevant for multi-line text). Can be a positive or negative.
- parameter lineSpacing: The layer line Spacing.
- returns: The same instance of CLDTextLayer.
*/
@objc(setLineSpacingFromInt:)
open func setLineSpacing(_ lineSpacing: Int) -> CLDTextLayer {
return setLineSpacing(String(describing: letterSpacing))
}
/**
Set the spacing between the lines in pixels (only relevant for multi-line text). Can be a positive or negative.
- parameter lineSpacing: The layer line Spacing.
- returns: The same instance of CLDTextLayer.
*/
@objc(setLineSpacingFromFloat:)
open func setLineSpacing(_ lineSpacing: Float) -> CLDTextLayer {
return setLineSpacing(lineSpacing.cldFloatFormat())
}
/**
Set the spacing between the lines in pixels (only relevant for multi-line text). Can be a positive or negative.
- parameter lineSpacing: The layer line Spacing.
- returns: The same instance of CLDTextLayer.
*/
@objc(setLineSpacingString:)
open func setLineSpacing(_ lineSpacing: String) -> CLDTextLayer {
self.lineSpacing = lineSpacing
return self
}
/**
Set the antialiasing of the text layer rendering
- parameter antialiasing: The antialiasing method
- returns: The same instance of CLDTextLayer.
*/
@objc(setFontAntialiasingString:)
open func setFontAntialiasing(_ antialiasing: String) -> CLDTextLayer {
self.fontAntialiasing = antialiasing
return self
}
/**
Set the antialiasing of the text layer rendering
- parameter antialiasing: The antialiasing method
- returns: The same instance of CLDTextLayer.
*/
open func setFontAntialiasing(_ antialiasing: CLDFontAntialiasing) -> CLDTextLayer {
self.fontAntialiasing = antialiasing.rawValue
return self
}
/**
Set the text rendering hinting
- parameter hinting: The hinting method
- returns: The same instance of CLDTextLayer.
*/
@objc (setFontHintingString:)
open func setFontHinting(_ hinting: String) -> CLDTextLayer{
self.fontHinting = hinting
return self
}
/**
Set the text rendering hinting
- parameter hinting: The hinting method
- returns: The same instance of CLDTextLayer.
*/
open func setFontHinting(_ hinting: CLDFontHinting) -> CLDTextLayer{
self.fontHinting = hinting.rawValue
return self
}
// MARK: - Actions
internal override func getStringComponents() -> [String]? {
if (text == nil || text!.isEmpty) && (publicId == nil || publicId!.isEmpty) {
printLog(.error, text: "Must supply either text or publicId")
return nil
}
guard var components = super.getStringComponents() else {
return nil
}
let optionalTextParams = getOptionalTextPropertiesArray()
let mandatoryTextParams = getMandatoryTextPropertiesArray()
if optionalTextParams.isEmpty {
if !mandatoryTextParams.isEmpty {
components.append(mandatoryTextParams.joined(separator: "_"))
}
}
else if !mandatoryTextParams.isEmpty {
let textProperties = mandatoryTextParams + optionalTextParams
components.append(textProperties.joined(separator: "_"))
}
else {
printLog(.error, text: "Must supply fontSize and fontFamily for text layer")
return nil
}
if let publicId = publicId , !publicId.isEmpty {
components.append(publicId.replacingOccurrences(of: "/", with: ":"))
}
if let text = text , !text.isEmpty {
if let text = text.cldSmartEncodeUrl() {
var textToAdd = text.replacingOccurrences(of: "%2C", with: "%252C")
textToAdd = textToAdd.replacingOccurrences(of: "/", with: "%252F")
components.append(textToAdd)
}
}
return components
}
//MARK: - Private
fileprivate func getOptionalTextPropertiesArray() -> [String] {
var properties: [String] = []
if let fontWeight = fontWeight , fontWeight != "normal" {
properties.append(fontWeight)
}
if let fontStyle = fontStyle , fontStyle != "normal" {
properties.append(fontStyle)
}
if let antialiasing = fontAntialiasing, !antialiasing.isEmpty {
properties.append("antialias_\(antialiasing)")
}
if let hinting = fontHinting, !hinting.isEmpty {
properties.append("hinting_\(hinting)")
}
if let textDecoration = textDecoration , textDecoration != "none" {
properties.append(textDecoration)
}
if let stroke = stroke , stroke != "none" {
properties.append(stroke)
}
if let textAlign = textAlign , !textAlign.isEmpty {
properties.append(textAlign)
}
if let letterSpacing = letterSpacing , !letterSpacing.isEmpty {
properties.append("letter_spacing_\(letterSpacing)")
}
if let lineSpacing = lineSpacing , !lineSpacing.isEmpty {
properties.append("line_spacing_\(lineSpacing)")
}
return properties
}
fileprivate func getMandatoryTextPropertiesArray() -> [String] {
var properties: [String] = []
if let fontSize = fontSize {
properties.insert(fontSize, at: 0)
}
if let fontFamily = fontFamily {
properties.insert(fontFamily, at: 0)
}
return properties
}
/// Text layer font antialiasing methods
public enum CLDFontAntialiasing: String {
/// Use a bi-level alpha mask.
case NONE = "none"
/// Perform single-color antialiasing. For example, using shades of gray for black text on a white background.
case GRAY = "gray"
/// Perform antialiasing by taking advantage of the order of subpixel elements on devices such as LCD panels.
case SUBPIXEL = "subpixel"
/// Some antialiasing is performed, but speed is prioritized over quality.
case FAST = "fast"
/// Antialiasing that balances quality and performance.
case GOOD = "good"
/// Renders at the highest quality, sacrificing speed if necessary.
case BEST = "best"
}
/// Text layer font hinting methods
public enum CLDFontHinting: String {
/// Do not hint outlines.
case NONE = "none"
/// Hint outlines slightly to improve contrast while retaining good fidelity to the original shapes.
case SLIGHT = "slight"
/// Hint outlines with medium strength, providing a compromise between fidelity to the original shapes and contrast.
case MEDIUM = "medium"
/// Hint outlines to the maximize contrast.
case FULL = "full"
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/RequestParams/CLDRequestParams.swift
================================================
//
// CLDRequestParams.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
The CLDRequestParams class is a base class for all different request params object.
It holds a dictionary of the actual params, the request's resource type, the request signature for signing a request using an externaly generated signature, and the subclass' representing action.
*/
@objcMembers open class CLDRequestParams: NSObject {
/**
A dictionary of the params to be sent as part of the request.
*/
internal var params: [String : Any]
/**
The request's resource type, if set it will be part of the request URL. On most cases defaults to "image".
*/
internal var resourceType: String?
/**
The request signature for signing a request using an externally generated signature. if no signature is assigned, the SDK will sign the request using the configured API secret.
*/
internal var signature: CLDSignature?
/**
Override config apiKey with this value.
*/
internal var apiKey: String?
internal override init() {
apiKey = nil
signature = nil
resourceType = nil
params = [:]
super.init()
}
//MARK: - Set Param
/**
A generic setter to manualy set a param.
- parameter key: The key of parameter to set.
- parameter value: The parameter value.
- returns: The same instance of CLDRequestParams.
*/
@discardableResult
open func setParam(_ key: String, value: Any?) -> Self {
params[key] = value
return self
}
@discardableResult
@objc(setResourceTypeFromUrlResourceType:)
open func setResourceType(_ resourceType: CLDUrlResourceType) -> Self {
return setResourceType(String(describing: resourceType))
}
@discardableResult
@objc(setResourceTypeFromString:)
open func setResourceType(_ resourceType: String) -> Self {
self.resourceType = resourceType
return self
}
@discardableResult
@objc(setSignatureWithSignature:)
open func setSignature(_ signature: CLDSignature) -> Self {
self.signature = signature
return self
}
@discardableResult
@objc(setApiKeyWithKey:)
open func setApiKey(_ apiKey: String) -> Self {
self.apiKey = apiKey
return self
}
// MARK: - Get Param
/**
A generic getter to retrieve a param for a given key.
- parameter key: The key of the parameter to retrieve.
- returns: The same instance of CLDRequestParams.
*/
open func getParam(_ key: String) -> Any? {
return params[key]
}
internal func merge(_ other: CLDRequestParams?) {
if let other = other {
self.signature = other.signature
self.resourceType = other.resourceType
self.params.cldMerge(other.params)
self.apiKey = other.apiKey
}
}
internal func setTimeout(from config: CLDConfiguration) {
guard let timeout = config.timeout else { return }
setParam(CLDConfiguration.ConfigParam.Timeout.description, value: timeout)
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/RequestParams/Helpers/CLDRequestParamsHelpers.swift
================================================
//
// CLDRequestParamsHelpers.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreGraphics
// MARK: - CLDSignature
/**
The CLDSignature class represents a signature used to sign a URL request.
*/
@objcMembers open class CLDSignature: NSObject {
public let signature: String
public let timestamp: NSNumber
public init(signature: String, timestamp: NSNumber) {
self.signature = signature
self.timestamp = timestamp
super.init()
}
internal enum SignatureParam: String, CustomStringConvertible {
case Signature = "signature"
case Timestamp = "timestamp"
var description: String {
get {
switch self {
case .Signature: return "signature"
case .Timestamp: return "timestamp"
}
}
}
}
}
// MARK: - CLDAccessControlRule
/**
The CLDAccessControlRule class represents a single access control rule for upload/update API
*/
@objcMembers open class CLDAccessControlRule: NSObject, Codable {
// MARK: Properties
internal var start: Date?
internal var end: Date?
internal var accessType: CLDAccessType
// MARK: Init
/**
Initializes the CLDAccessControlRule
- parameter accessType: The access type for this rule
- parameter start: The start time for the rule
- parameter end: The end time for the rule
- returns: A new CLDaccessControlRule instance.
*/
internal init(accessType: CLDAccessType, start: Date? = nil, end: Date? = nil) {
self.accessType = accessType
self.start = start
self.end = end
}
// MARK: Static builders
/**
Get a new instance of CLDAccessControlRule with token strategy
*/
public static func token()-> CLDAccessControlRule {
return CLDAccessControlRule(accessType: CLDAccessType.token)
}
/**
Get a new instance of CLDAccessControlRule with anonymous strategy
- parameter start: The start date for the rule
*/
public static func anonymous(start: Date)-> CLDAccessControlRule {
return CLDAccessControlRule(accessType: CLDAccessType.anonymous, start: start)
}
/**
Get a new instance of CLDAccessControlRule with anonymous strategy
- parameter end: The end date for the rule
*/
public static func anonymous(end: Date)-> CLDAccessControlRule {
return CLDAccessControlRule(accessType: CLDAccessType.anonymous, end: end)
}
/**
Get a new instance of CLDAccessControlRule with anonymous strategy
- parameter start: The start date for the rule
- parameter end: The end date for the rule
*/
public static func anonymous(start: Date, end: Date)-> CLDAccessControlRule {
return CLDAccessControlRule(accessType: CLDAccessType.anonymous, start: start, end: end)
}
// MARK: Encodable
enum CodingKeys: String, CodingKey {
case start
case end
case accessType = "access_type"
}
// MARK: Equatable
static func == (lhs: CLDAccessControlRule, rhs: CLDAccessControlRule) -> Bool {
return lhs.accessType == rhs.accessType && lhs.start == rhs.start && lhs.end == rhs.end
}
}
public enum CLDAccessType : String, Codable {
case anonymous, token
}
// MARK: - CLDCoordinate
/**
The CLDCoordinate class represents a rectangle area on an asset.
*/
@objcMembers open class CLDCoordinate: NSObject {
let rect: CGRect
var x: Float {
return Float(rect.origin.x)
}
var y: Float {
return Float(rect.origin.y)
}
var width: Float {
return Float(rect.width)
}
var height: Float {
return Float(rect.height)
}
// MARK: Init
/**
Initializes the CLDCoordinate using a CGRect.
- parameter rect: The rectangle representing an area on the asset.
- returns: A new CLDCoordinate instance.
*/
public init(rect: CGRect) {
self.rect = rect
}
open override var description: String {
get {
var components: [String] = []
components.append(x.cldFormat(f: ".0"))
components.append(y.cldFormat(f: ".0"))
components.append(width.cldFormat(f: ".0"))
components.append(height.cldFormat(f: ".0"))
return components.joined(separator: ",")
}
}
}
// MARK: - CLDResponsiveBreakpoints
/**
The CLDResponsiveBreakpoints class describe the settings available for configuring responsive breakpoints.
Responsive breakpoints is used to request Cloudinary to automatically find the best breakpoints.
*/
@objcMembers open class CLDResponsiveBreakpoints: NSObject {
internal var params: [String: AnyObject] = [:]
// MARK - Init
/**
Initializes a CLDResponsiveBreakpoints instance.
- parameter createDerived: If true, create and keep the derived assets of the selected breakpoints during the API call.
If false, assets generated during the analysis process are thrown away.
- returns: A new CLDResponsiveBreakpoints instance.
*/
public init(createDerived: Bool) {
super.init()
setParam(ResponsiveBreakpointsParams.CreateDerived.rawValue, value: createDerived as AnyObject?)
}
// MARK - Set Param
/**
Set the base transformation to first apply to the image before finding the best breakpoints.
- parameter transformation: The transformation to apply.
- returns: The same CLDResponsiveBreakpoints instance.
*/
open func setTransformations(_ transformation: CLDTransformation) -> Self {
return setParam(ResponsiveBreakpointsParams.Transformation.rawValue, value: transformation.asString() as AnyObject)
}
/**
Set the maximum width needed for this asset.
If specifying a width bigger than the original asset, the width of the original asset is used instead. default is 1000.
- parameter maxWidth: The max width to set.
- returns: The same CLDResponsiveBreakpoints instance.
*/
open func setMaxWidth(_ maxWidth: Int) -> Self {
return setParam(ResponsiveBreakpointsParams.MaxWidth.rawValue, value: maxWidth as AnyObject?)
}
/**
Set the minimum width needed for this asset. default is 50.
- parameter minWidth: The min width to set.
- returns: The same CLDResponsiveBreakpoints instance.
*/
open func setMinWidth(_ minWidth: Int) -> Self {
return setParam(ResponsiveBreakpointsParams.MinWidth.rawValue, value: minWidth as AnyObject?)
}
/**
Set the minimum number of bytes between two consecutive breakpoints (assets). default is 20000.
- parameter bytesStep: The bytes step to set.
- returns: The same CLDResponsiveBreakpoints instance.
*/
open func setBytesStep(_ bytesStep: Int) -> Self {
return setParam(ResponsiveBreakpointsParams.BytesStep.rawValue, value: bytesStep as AnyObject?)
}
/**
Set the maximum number of breakpoints to find, between 3 and 200.
This means that there might be size differences bigger than the given bytes_step value between consecutive assets. default is 20.
- parameter maxImages: The max images to set.
- returns: The same CLDResponsiveBreakpoints instance.
*/
open func setMaxImages(_ maxImages: Int) -> Self {
return setParam(ResponsiveBreakpointsParams.MaxImages.rawValue, value: maxImages as AnyObject?)
}
/**
Set the format of the resulting images.
- parameter format: The format to set.
- returns: The same CLDResponsiveBreakpoints instance.
*/
open func setFormat(_ format: String) -> Self {
return setParam(ResponsiveBreakpointsParams.Format.rawValue, value: format as AnyObject)
}
@discardableResult
open func setParam(_ key: String, value: AnyObject?) -> Self {
params[key] = value
return self
}
open override var description: String {
get {
var components: [String] = []
for param in params {
if param.value is Int || param.value is Bool {
components.append("\"\(param.key)\":\(param.value)")
} else {
components.append("\"\(param.key)\":\"\(param.value)\"")
}
}
return "{\(components.joined(separator: ","))}"
}
}
fileprivate enum ResponsiveBreakpointsParams: String, CustomStringConvertible {
case CreateDerived = "create_derived"
case Transformation = "transformation"
case MaxWidth = "max_width"
case MinWidth = "min_width"
case BytesStep = "bytes_step"
case MaxImages = "max_images"
case Format = "format"
var description: String {
get {
switch self {
case .CreateDerived: return "create_derived"
case .Transformation: return "transformation"
case .MaxWidth: return "max_width"
case .MinWidth: return "min_width"
case .BytesStep: return "bytes_step"
case .MaxImages: return "max_images"
case .Format: return "format"
}
}
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/Requests/CLDRequest.swift
================================================
//
// CLDRequest.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
The `CLDRequest` object is returned when creating a network request using one of Cloudinary's API calls.
It allows the options to add a response closure to be called once the request has finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@objcMembers open class CLDRequest: NSObject {
internal var networkRequest: CLDNetworkDataRequest
internal init(networkRequest: CLDNetworkDataRequest) {
self.networkRequest = networkRequest
}
// MARK: - Public
/**
Resume the request.
*/
open func resume() {
networkRequest.resume()
}
/**
Suspend the request.
*/
open func suspend() {
networkRequest.suspend()
}
/**
Cancel the request.
*/
open func cancel() {
networkRequest.cancel()
}
//MARK: Handlers
/**
Set a response closure to be called once the request has finished.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: The same instance of CLDRequest.
*/
@discardableResult
open func responseRaw(_ completionHandler: @escaping (_ response: Any?, _ error: NSError?) -> ()) -> CLDRequest {
networkRequest.response(completionHandler)
return self
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/Results/CLDBaseResult.swift
================================================
//
// CLDBaseResult.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDBaseResult: NSObject {
open fileprivate(set) var resultJson: [String : AnyObject]
// MARK: - Init
internal init(json: [String : AnyObject]) {
resultJson = json
}
// MARK: - Private Helpers
internal func getParam(_ param: CommonResultKeys) -> AnyObject? {
return resultJson[String(describing: param)]
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/Results/Helpers/AccessibilityAnalysis/CLDAccessibilityAnalysisResult.swift
================================================
//
// CLDAccessibilityAnalysisResult.swift
//
// Copyright (c) 2020 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDAccessibilityAnalysisResult: CLDBaseResult {
open var colorblindAccessibilityScore: Double? {
guard let accessibilityScore = getParam(.colorblindAccessibilityScore) as? Double else { return nil }
return accessibilityScore
}
open var colorblindAccessibilityAnalysis: CLDColorblindAccessibilityAnalysisResult? {
guard let accessibilityAnalysis = getParam(.colorblindAccessibilityAnalysis) as? [String: AnyObject] else { return nil }
return CLDColorblindAccessibilityAnalysisResult(json: accessibilityAnalysis)
}
// MARK: - Private Helpers
fileprivate func getParam(_ param: CLDAccessibilityAnalysisResultKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum CLDAccessibilityAnalysisResultKey: CustomStringConvertible {
case colorblindAccessibilityScore, colorblindAccessibilityAnalysis
var description: String {
switch self {
case .colorblindAccessibilityScore : return "colorblind_accessibility_score"
case .colorblindAccessibilityAnalysis: return "colorblind_accessibility_analysis"
}
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/Results/Helpers/AccessibilityAnalysis/CLDColorblindAccessibilityAnalysisResult.swift
================================================
//
// CLDColorblindAccessibilityAnalysisResult.swift
//
// Copyright (c) 2020 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDColorblindAccessibilityAnalysisResult: CLDBaseResult {
open var distinctColors: Double? {
guard let distinctColors = getParam(.distinctColors) as? Double else { return nil }
return distinctColors
}
open var distinctEdges: Double? {
guard let distinctEdges = getParam(.distinctEdges) as? Double else { return nil }
return distinctEdges
}
open var mostIndistinctPair: [String]? {
guard let mostIndistinctPair = getParam(.mostIndistinctPair) as? [String] else { return nil }
return mostIndistinctPair
}
// MARK: - Private Helpers
fileprivate func getParam(_ param: CLDColorblindAccessibilityAnalysisResultKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum CLDColorblindAccessibilityAnalysisResultKey: CustomStringConvertible {
case distinctColors, distinctEdges, mostIndistinctPair
var description: String {
switch self {
case .distinctColors : return "distinct_colors"
case .distinctEdges : return "distinct_edges"
case .mostIndistinctPair: return "most_indistinct_pair"
}
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/Results/Helpers/CLDBoundingBox.swift
================================================
//
// CLDBoundingBox.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreGraphics
@objcMembers open class CLDBoundingBox: CLDBaseResult {
open var topLeft: CGPoint? {
return CLDBoundingBox.parsePoint(resultJson, key: String(describing: CLDBoundingBoxJsonKey.topLeft))
}
open var size: CGSize? {
return CLDBoundingBox.parseSize(resultJson, key: String(describing: CLDBoundingBoxJsonKey.size))
}
internal static func parsePoint(_ json: [String : AnyObject], key: String) -> CGPoint? {
guard let
point = json[key] as? [String : Double],
let x = point[CommonResultKeys.x.description],
let y = point[CommonResultKeys.y.description] else {
return nil
}
return CGPoint(x: CGFloat(x), y: CGFloat(y))
}
internal static func parseSize(_ json: [String : AnyObject], key: String) -> CGSize? {
guard let
point = json[key] as? [String : Double],
let width = point[CommonResultKeys.width.description],
let height = point[CommonResultKeys.height.description] else {
return nil
}
return CGSize(width: CGFloat(width), height: CGFloat(height))
}
// MARK: - Private Helpers
fileprivate func getParam(_ param: CLDBoundingBoxJsonKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum CLDBoundingBoxJsonKey: CustomStringConvertible {
case topLeft, size
var description: String {
switch self {
case .topLeft: return "tl"
case .size: return "size"
}
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/Results/Helpers/CLDDetection.swift
================================================
//
// CLDDetection.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDDetection: CLDBaseResult {
open var rekognitionFace: CLDRekognitionFace? {
guard let rekognitionFace = getParam(.rekognitionFace) as? [String : AnyObject] else {
return nil
}
return CLDRekognitionFace(json: rekognitionFace)
}
// MARK: - Private Helpers
fileprivate func getParam(_ param: CLDDetectionKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum CLDDetectionKey: CustomStringConvertible {
case rekognitionFace
var description: String {
switch self {
case .rekognitionFace: return "rekognition_face"
}
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/Results/Helpers/CLDFace.swift
================================================
//
// CLDFace.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreGraphics
@objcMembers open class CLDFace: CLDBaseResult {
open var boundingBox: CLDBoundingBox? {
guard let bb = getParam(.boundingBox) as? [String : AnyObject] else {
return nil
}
return CLDBoundingBox(json: bb)
}
open var confidence: Double? {
return getParam(.confidence) as? Double
}
open var age: Double? {
return getParam(.age) as? Double
}
open var smile: Double? {
return getParam(.smile) as? Double
}
open var glasses: Double? {
return getParam(.glasses) as? Double
}
open var sunglasses: Double? {
return getParam(.sunglasses) as? Double
}
open var beard: Double? {
return getParam(.beard) as? Double
}
open var mustache: Double? {
return getParam(.mustache) as? Double
}
open var eyeClosed: Double? {
return getParam(.eyeClosed) as? Double
}
open var mouthOpenWide: Double? {
return getParam(.mouthOpenWide) as? Double
}
open var beauty: Double? {
return getParam(.beauty) as? Double
}
open var gender: Double? {
return getParam(.gender) as? Double
}
open var race: [String : Double]? {
return getParam(.race) as? [String : Double]
}
open var emotion: [String : Double]? {
return getParam(.emotion) as? [String : Double]
}
open var quality: [String : Double]? {
return getParam(.quality) as? [String : Double]
}
open var pose: [String : Double]? {
return getParam(.pose) as? [String : Double]
}
open var eyeLeftPosition: CGPoint? {
return CLDBoundingBox.parsePoint(resultJson, key: String(describing: CLDFaceKey.eyeLeftPosition))
}
open var eyeRightPosition: CGPoint? {
return CLDBoundingBox.parsePoint(resultJson, key: String(describing: CLDFaceKey.eyeRightPosition))
}
open var eyeLeft_Left: CGPoint? {
return CLDBoundingBox.parsePoint(resultJson, key: String(describing: CLDFaceKey.eyeLeft_Left))
}
open var eyeLeft_Right: CGPoint? {
return CLDBoundingBox.parsePoint(resultJson, key: String(describing: CLDFaceKey.eyeLeft_Right))
}
open var eyeLeft_Up: CGPoint? {
return CLDBoundingBox.parsePoint(resultJson, key: String(describing: CLDFaceKey.eyeLeft_Up))
}
open var eyeLeft_Down: CGPoint? {
return CLDBoundingBox.parsePoint(resultJson, key: String(describing: CLDFaceKey.eyeLeft_Down))
}
open var eyeRight_Left: CGPoint? {
return CLDBoundingBox.parsePoint(resultJson, key: String(describing: CLDFaceKey.eyeRight_Left))
}
open var eyeRight_Right: CGPoint? {
return CLDBoundingBox.parsePoint(resultJson, key: String(describing: CLDFaceKey.eyeRight_Right))
}
open var eyeRight_Up: CGPoint? {
return CLDBoundingBox.parsePoint(resultJson, key: String(describing: CLDFaceKey.eyeRight_Up))
}
open var eyeRight_Down: CGPoint? {
return CLDBoundingBox.parsePoint(resultJson, key: String(describing: CLDFaceKey.eyeRight_Down))
}
open var nosePosition: CGPoint? {
return CLDBoundingBox.parsePoint(resultJson, key: String(describing: CLDFaceKey.nosePosition))
}
open var noseLeft: CGPoint? {
return CLDBoundingBox.parsePoint(resultJson, key: String(describing: CLDFaceKey.noseLeft))
}
open var noseRight: CGPoint? {
return CLDBoundingBox.parsePoint(resultJson, key: String(describing: CLDFaceKey.noseRight))
}
open var mouthLeft: CGPoint? {
return CLDBoundingBox.parsePoint(resultJson, key: String(describing: CLDFaceKey.mouthLeft))
}
open var mouthRight: CGPoint? {
return CLDBoundingBox.parsePoint(resultJson, key: String(describing: CLDFaceKey.mouthRight))
}
open var mouthUp: CGPoint? {
return CLDBoundingBox.parsePoint(resultJson, key: String(describing: CLDFaceKey.mouthUp))
}
open var mouthDown: CGPoint? {
return CLDBoundingBox.parsePoint(resultJson, key: String(describing: CLDFaceKey.mouthDown))
}
// MARK: - Private Helpers
fileprivate func getParam(_ param: CLDFaceKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum CLDFaceKey: CustomStringConvertible {
case boundingBox, confidence, age, smile, glasses, sunglasses, beard, mustache, eyeClosed, mouthOpenWide, beauty, gender, race, emotion, quality, pose, eyeLeftPosition, eyeRightPosition, eyeLeft_Left, eyeLeft_Right, eyeLeft_Up, eyeLeft_Down, eyeRight_Left, eyeRight_Right, eyeRight_Up, eyeRight_Down, nosePosition, noseLeft, noseRight, mouthLeft, mouthRight, mouthUp, mouthDown
var description: String {
switch self {
case .boundingBox: return "boundingbox"
case .confidence: return "confidence"
case .age: return "age"
case .smile: return "smile"
case .glasses: return "glasses"
case .sunglasses: return "sunglasses"
case .beard: return "beard"
case .mustache: return "mustache"
case .eyeClosed: return "eye_closed"
case .mouthOpenWide: return "mouth_open_wide"
case .beauty: return "beauty"
case .gender: return "sex"
case .race: return "race"
case .emotion: return "emotion"
case .quality: return "quality"
case .pose: return "pose"
case .eyeLeftPosition: return "eye_left"
case .eyeRightPosition: return "eye_right"
case .eyeLeft_Left: return "e_ll"
case .eyeLeft_Right: return "e_lr"
case .eyeLeft_Up: return "e_lu"
case .eyeLeft_Down: return "e_ld"
case .eyeRight_Left: return "e_rl"
case .eyeRight_Right: return "e_rr"
case .eyeRight_Up: return "e_ru"
case .eyeRight_Down: return "e_rd"
case .nosePosition: return "nose"
case .noseLeft: return "n_l"
case .noseRight: return "n_r"
case .mouthLeft: return "mouth_l"
case .mouthRight: return "mouth_r"
case .mouthUp: return "m_u"
case .mouthDown: return "m_d"
}
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/Results/Helpers/CLDInfo.swift
================================================
//
// CLDInfo.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDInfo: CLDBaseResult {
open var detection: CLDDetection? {
guard let detection = getParam(.detection) as? [String : AnyObject] else {
return nil
}
return CLDDetection(json: detection)
}
open var ocr: CLDOcrResult? {
guard let ocr = getParam(.ocr) as? [String : AnyObject] else {
return nil
}
return CLDOcrResult(json: ocr)
}
// MARK: - Private Helpers
fileprivate func getParam(_ param: CLDInfoKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum CLDInfoKey: CustomStringConvertible {
case detection, ocr
var description: String {
switch self {
case .detection: return "detection"
case .ocr : return "ocr"
}
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/Results/Helpers/CLDOcr/CLDAdvOcrResult.swift
================================================
//
// CLDAdvOcrResult.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDAdvOcrResult: CLDBaseResult {
open var status: String? {
guard let status = getParam(.status) as? String else { return nil }
return status
}
open var data: [CLDOcrDataResult]? {
guard let data = getParam(.data) as? [[String : AnyObject]] else { return nil }
return data.compactMap{ CLDOcrDataResult(json: $0) }
}
// MARK: - Private Helpers
fileprivate func getParam(_ param: CLDAdvOcrResultKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum CLDAdvOcrResultKey: CustomStringConvertible {
case status, data
var description: String {
switch self {
case .status: return "status"
case .data : return "data"
}
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/Results/Helpers/CLDOcr/CLDOcrBlockResult.swift
================================================
//
// CLDOcrBlockResult.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDOcrBlockResult: CLDBaseResult {
open var boundingBox: CLDOcrBoundindBlockResult? {
guard let boundingBox = getParam(.boundingBox) as? [String : AnyObject] else { return nil }
return CLDOcrBoundindBlockResult(json: boundingBox)
}
open var property: CLDOcrPropertyResult? {
guard let property = getParam(.property) as? [String : AnyObject] else { return nil }
return CLDOcrPropertyResult(json: property)
}
open var paragraphs: [CLDOcrParagraphResult]? {
guard let paragraphs = getParam(.paragraphs) as? [[String : AnyObject]] else { return nil }
return paragraphs.compactMap({ CLDOcrParagraphResult(json: $0) })
}
open var blockType: String? {
guard let blockType = getParam(.blockType) as? String else { return nil }
return blockType
}
// MARK: - Private Helpers
fileprivate func getParam(_ param: CLDOcrBlockResultKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum CLDOcrBlockResultKey: CustomStringConvertible {
case boundingBox, property, paragraphs, blockType
var description: String {
switch self {
case .boundingBox: return "boundingBox"
case .property : return "property"
case .paragraphs : return "paragraphs"
case .blockType : return "blockType"
}
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/Results/Helpers/CLDOcr/CLDOcrBoundindBlockResult.swift
================================================
//
// CLDOcrBoundindBlockResult.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreGraphics
@objcMembers open class CLDOcrBoundindBlockResult: CLDBaseResult {
open var vertices: [CGPoint]? {
guard let vertices = getParam(.vertices) as? [[String : AnyObject]] else { return nil}
return vertices.compactMap{
if let x = $0["x"] as? NSNumber, let y = $0["y"] as? NSNumber {
return CGPoint(x: x.intValue, y: y.intValue)
}
else {
return nil
}
}
}
// MARK: - Private Helpers
fileprivate func getParam(_ param: CLDOcrBoundindBlockResultKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum CLDOcrBoundindBlockResultKey: CustomStringConvertible {
case vertices
var description: String {
switch self {
case .vertices : return "vertices"
}
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/Results/Helpers/CLDOcr/CLDOcrDataResult.swift
================================================
//
// CLDOcrDataResult.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDOcrDataResult: CLDBaseResult {
open var textAnnotations: [CLDOcrTextAnnotationResult]? {
guard let textAnnotations = getParam(.textAnnotations) as? [[String : AnyObject]] else { return nil }
return textAnnotations.compactMap{ CLDOcrTextAnnotationResult(json: $0) }
}
open var fullTextAnnotation: CLDOcrFullTextAnnotationResult? {
guard let fullTextAnnotation = getParam(.fullTextAnnotation) as? [String : AnyObject] else { return nil }
return CLDOcrFullTextAnnotationResult(json: fullTextAnnotation)
}
// MARK: - Private Helpers
fileprivate func getParam(_ param: CLDOcrDataResultKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum CLDOcrDataResultKey: CustomStringConvertible {
case textAnnotations, fullTextAnnotation
var description: String {
switch self {
case .textAnnotations : return "textAnnotations"
case .fullTextAnnotation : return "fullTextAnnotation"
}
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/Results/Helpers/CLDOcr/CLDOcrDetectedLanguagesResult.swift
================================================
//
// CLDOcrDetectedLanguagesResult.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDOcrDetectedLanguagesResult: CLDBaseResult {
open var languageCode: String? {
guard let languageCode = getParam(.languageCode) as? String else { return nil }
return languageCode
}
open var confidence: Int? {
guard let confidence = getParam(.confidence) as? Int else { return nil }
return confidence
}
// MARK: - Private Helpers
fileprivate func getParam(_ param: CLDOcrDetectedLanguagesResultKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum CLDOcrDetectedLanguagesResultKey: CustomStringConvertible {
case languageCode, confidence
var description: String {
switch self {
case .languageCode: return "languageCode"
case .confidence : return "confidence"
}
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/Results/Helpers/CLDOcr/CLDOcrFullTextAnnotationResult.swift
================================================
//
// CLDOcrFullTextAnnotationResult.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDOcrFullTextAnnotationResult: CLDBaseResult {
open var pages: [CLDOcrPageResult]? {
guard let pages = getParam(.pages) as? [[String : AnyObject]] else { return nil }
return pages.compactMap({ CLDOcrPageResult(json: $0) })
}
open var text: String? {
guard let text = getParam(.text) as? String else { return nil }
return text
}
// MARK: - Private Helpers
fileprivate func getParam(_ param: CLDOcrFullTextAnnotationResultKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum CLDOcrFullTextAnnotationResultKey: CustomStringConvertible {
case pages, text
var description: String {
switch self {
case .pages: return "pages"
case .text : return "text"
}
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/Results/Helpers/CLDOcr/CLDOcrPageResult.swift
================================================
//
// CLDOcrPageResult.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDOcrPageResult: CLDBaseResult {
open var blocks: [CLDOcrBlockResult]? {
guard let blocks = getParam(.blocks) as? [[String : AnyObject]] else { return nil }
return blocks.compactMap({ CLDOcrBlockResult(json: $0) })
}
open var property: CLDOcrPropertyResult? {
guard let property = getParam(.property) as? [String : AnyObject] else { return nil }
return CLDOcrPropertyResult(json: property)
}
open var width: Int? {
guard let width = getParam(.pageWidth) as? Int else { return nil }
return width
}
open var height: Int? {
guard let height = getParam(.pageHeight) as? Int else { return nil }
return height
}
// MARK: - Private Helpers
fileprivate func getParam(_ param: CLDOcrPageResultKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum CLDOcrPageResultKey: CustomStringConvertible {
case blocks, property, pageWidth, pageHeight
var description: String {
switch self {
case .blocks : return "blocks"
case .property : return "property"
case .pageWidth : return "width"
case .pageHeight: return "height"
}
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/Results/Helpers/CLDOcr/CLDOcrParagraphResult.swift
================================================
//
// CLDOcrParagraphResult.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDOcrParagraphResult: CLDBaseResult {
open var boundingBox: CLDOcrBoundindBlockResult? {
guard let boundingBox = getParam(.boundingBox) as? [String : AnyObject] else { return nil }
return CLDOcrBoundindBlockResult(json: boundingBox)
}
open var property: CLDOcrPropertyResult? {
guard let property = getParam(.property) as? [String : AnyObject] else { return nil }
return CLDOcrPropertyResult(json: property)
}
open var words: [CLDOcrWordResult]? {
guard let words = getParam(.words) as? [[String : AnyObject]] else { return nil }
return words.compactMap({ CLDOcrWordResult(json: $0) })
}
// MARK: - Private Helpers
fileprivate func getParam(_ param: CLDOcrParagraphResultKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum CLDOcrParagraphResultKey: CustomStringConvertible {
case boundingBox, property, words
var description: String {
switch self {
case .boundingBox: return "boundingBox"
case .property : return "property"
case .words : return "words"
}
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/Results/Helpers/CLDOcr/CLDOcrPropertyResult.swift
================================================
//
// CLDOcrPropertyResult.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDOcrPropertyResult: CLDBaseResult {
open var detectedLanguages: [CLDOcrDetectedLanguagesResult]? {
guard let detectedLanguages = getParam(.detectedLanguages) as? [[String : AnyObject]] else { return nil }
return detectedLanguages.compactMap({ CLDOcrDetectedLanguagesResult(json: $0) })
}
// MARK: - Private Helpers
fileprivate func getParam(_ param: CLDOcrPropertyResultKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum CLDOcrPropertyResultKey: CustomStringConvertible {
case detectedLanguages
var description: String {
switch self {
case .detectedLanguages: return "detectedLanguages"
}
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/Results/Helpers/CLDOcr/CLDOcrResult.swift
================================================
//
// CLDOcrResult.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDOcrResult: CLDBaseResult {
open var advOcr: CLDAdvOcrResult? {
guard let advOcr = getParam(.advOcr) as? [String : AnyObject] else { return nil }
return CLDAdvOcrResult(json: advOcr)
}
// MARK: - Private Helpers
fileprivate func getParam(_ param: CLDOcrResultKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum CLDOcrResultKey: CustomStringConvertible {
case advOcr
var description: String {
switch self {
case .advOcr: return "adv_ocr"
}
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/Results/Helpers/CLDOcr/CLDOcrSymbolResult.swift
================================================
//
// CLDOcrSymbolResult.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDOcrSymbolResult: CLDBaseResult {
open var boundingBox: CLDOcrBoundindBlockResult? {
guard let boundingBox = getParam(.boundingBox) as? [String : AnyObject] else { return nil }
return CLDOcrBoundindBlockResult(json: boundingBox)
}
open var property: CLDOcrPropertyResult? {
guard let property = getParam(.property) as? [String : AnyObject] else { return nil }
return CLDOcrPropertyResult(json: property)
}
open var text: String? {
guard let text = getParam(.text) as? String else { return nil }
return text
}
// MARK: - Private Helpers
fileprivate func getParam(_ param: CLDOcrSymbolResultKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum CLDOcrSymbolResultKey: CustomStringConvertible {
case boundingBox, property, text
var description: String {
switch self {
case .boundingBox: return "boundingBox"
case .property : return "property"
case .text : return "text"
}
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/Results/Helpers/CLDOcr/CLDOcrTextAnnotationResult.swift
================================================
//
// CLDOcrTextAnnotationResult.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDOcrTextAnnotationResult: CLDBaseResult {
open var locale: String? {
guard let locale = getParam(.locale) as? String else { return nil }
return locale
}
open var textDescription: String? {
guard let textDescription = getParam(.description) as? String else { return nil }
return textDescription
}
open var boundingBlock: CLDOcrBoundindBlockResult? {
guard let boundingBlock = getParam(.boundingPoly) as? [String : AnyObject] else { return nil }
return CLDOcrBoundindBlockResult(json: boundingBlock)
}
// MARK: - Private Helpers
fileprivate func getParam(_ param: CLDOcrTextAnnotationResultKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum CLDOcrTextAnnotationResultKey: CustomStringConvertible {
case locale, description, boundingPoly
var description: String {
switch self {
case .locale : return "locale"
case .description : return "description"
case .boundingPoly : return "boundingPoly"
}
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/Results/Helpers/CLDOcr/CLDOcrWordResult.swift
================================================
//
// CLDOcrWordResult.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDOcrWordResult: CLDBaseResult {
open var boundingBox: CLDOcrBoundindBlockResult? {
guard let boundingBox = getParam(.boundingBox) as? [String : AnyObject] else { return nil }
return CLDOcrBoundindBlockResult(json: boundingBox)
}
open var property: CLDOcrPropertyResult? {
guard let property = getParam(.property) as? [String : AnyObject] else { return nil }
return CLDOcrPropertyResult(json: property)
}
open var symbols: [CLDOcrSymbolResult]? {
guard let symbols = getParam(.symbols) as? [[String : AnyObject]] else { return nil }
return symbols.compactMap({ CLDOcrSymbolResult(json: $0) })
}
// MARK: - Private Helpers
fileprivate func getParam(_ param: CLDOcrWordResultKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum CLDOcrWordResultKey: CustomStringConvertible {
case boundingBox, property, symbols
var description: String {
switch self {
case .boundingBox: return "boundingBox"
case .property : return "property"
case .symbols : return "symbols"
}
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/Results/Helpers/CLDQualityAnalysis/CLDQualityAnalysis.swift
================================================
//
// CLDQualityAnalysis.swift
//
// Copyright (c) 2020 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDQualityAnalysis: CLDBaseResult {
open var blockiness: NSNumber? {
guard let blockiness = getParam(.blockiness) as? NSNumber else { return nil }
return blockiness
}
open var chromaSubsampling: NSNumber? {
guard let chromaSubsampling = getParam(.chromaSubsampling) as? NSNumber else { return nil }
return chromaSubsampling
}
open var resolution: NSNumber? {
guard let resolution = getParam(.resolution) as? NSNumber else { return nil }
return resolution
}
open var noise: NSNumber? {
guard let noise = getParam(.noise) as? NSNumber else { return nil }
return noise
}
open var colorScore: NSNumber? {
guard let colorScore = getParam(.colorScore) as? NSNumber else { return nil }
return colorScore
}
open var jpegChroma: NSNumber? {
guard let jpegChroma = getParam(.jpegChroma) as? NSNumber else { return nil }
return jpegChroma
}
open var dct: NSNumber? {
guard let dct = getParam(.dct) as? NSNumber else { return nil }
return dct
}
open var jpegQuality: NSNumber? {
guard let jpegQuality = getParam(.jpegQuality) as? NSNumber else { return nil }
return jpegQuality
}
open var focus: NSNumber? {
guard let focus = getParam(.focus) as? NSNumber else { return nil }
return focus
}
open var saturation: NSNumber? {
guard let saturation = getParam(.saturation) as? NSNumber else { return nil }
return saturation
}
open var contrast: NSNumber? {
guard let contrast = getParam(.contrast) as? NSNumber else { return nil }
return contrast
}
open var exposure: NSNumber? {
guard let exposure = getParam(.exposure) as? NSNumber else { return nil }
return exposure
}
open var lighting: NSNumber? {
guard let lighting = getParam(.lighting) as? NSNumber else { return nil }
return lighting
}
open var pixelScore: NSNumber? {
guard let pixelScore = getParam(.pixelScore) as? NSNumber else { return nil }
return pixelScore
}
// MARK: - Private Helpers
fileprivate func getParam(_ param: CLDQualityAnalysisKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum CLDQualityAnalysisKey: CustomStringConvertible {
case blockiness, chromaSubsampling, resolution, noise, colorScore, jpegChroma, dct, jpegQuality, focus, saturation, contrast, exposure, lighting, pixelScore
var description: String {
switch self {
case .blockiness: return "blockiness"
case .chromaSubsampling: return "chroma_subsampling"
case .resolution: return "resolution"
case .noise: return "noise"
case .colorScore: return "color_score"
case .jpegChroma: return "jpeg_chroma"
case .dct: return "dct"
case .jpegQuality: return "jpeg_quality"
case .focus: return "focus"
case .saturation: return "saturation"
case .contrast: return "contrast"
case .exposure: return "exposure"
case .lighting: return "lighting"
case .pixelScore: return "pixel_score"
}
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/Results/Helpers/CLDRekognitionFace.swift
================================================
//
// CLDRekognitionFace.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDRekognitionFace: CLDBaseResult {
open var status: String? {
return getParam(.status) as? String
}
open var faces: [CLDFace]? {
guard let facesArr = getParam(.faces) as? [[String : AnyObject]] else {
return nil
}
var faces: [CLDFace] = []
for face in facesArr {
faces.append(CLDFace(json: face))
}
return faces
}
// MARK: - Private Helpers
fileprivate func getParam(_ param: CLDRekognitionFaceKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum CLDRekognitionFaceKey: CustomStringConvertible {
case status, facesData
var description: String {
switch self {
case .status: return "status"
case .facesData: return "data"
}
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Helpers/Results/Helpers/CommonResultKeys.swift
================================================
//
// CommonResultKeys.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
internal enum CommonResultKeys: CustomStringConvertible {
case publicId, format, version, resourceType, urlType, createdAt, length, width, height, x, y, url, secureUrl, exif, metadata, faces, colors, tags, moderation, context, phash, info, accessControl, eager, qualityAnalysis, coordinates, accessibilityAnalysis, originalFilename, assetFolder, displayName, playbackUrl, metadata_object
var description: String {
switch self {
case .publicId: return "public_id"
case .format: return "format"
case .version: return "version"
case .resourceType: return "resource_type"
case .urlType: return "type"
case .createdAt: return "created_at"
case .length: return "bytes"
case .width: return "width"
case .height: return "height"
case .x: return "x"
case .y: return "y"
case .url: return "url"
case .secureUrl: return "secure_url"
case .exif: return "exif"
case .metadata: return "image_metadata"
case .faces: return "faces"
case .colors: return "colors"
case .tags: return "tags"
case .moderation: return "moderation"
case .context: return "context"
case .phash: return "phash"
case .info: return "info"
case .eager: return "eager"
case .accessControl: return "access_control"
case .qualityAnalysis: return "quality_analysis"
case .accessibilityAnalysis: return "accessibility_analysis"
case .coordinates: return "coordinates"
case .originalFilename: return "original_filename"
case .assetFolder: return "asset_folder"
case .displayName: return "display_name"
case .playbackUrl: return "playback_url"
case .metadata_object: return "metadata"
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/ManagementApi/CLDManagementApi.swift
================================================
//
// CLDManagementApi.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
The CLDManagementApi class is used to perform the available methods for managing your cloud assets.
*/
@objcMembers open class CLDManagementApi: CLDBaseNetworkObject {
// MARK: - Init
fileprivate override init() {
super.init()
}
override internal init(networkCoordinator:CLDNetworkCoordinator) {
super.init(networkCoordinator: networkCoordinator)
}
// MARK: - Features
/**
Rename an asset on your cloud.
- parameter publicId: The current identifier of the uploaded asset.
- parameter to: The new identifier to assign to the uploaded asset.
- parameter overwrite: A boolean parameter indicating whether or not to overwrite an existing image with the target Public ID. Default: false.
- parameter invalidate: A boolean parameter indicating whether to invalidate CDN cached copies of the image (and all its transformed versions). Default: false.
- parameter params: An object holding the available parameters for the request.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: An instance of `CLDRenameRequest`,
allowing the options to add response closure to be called once the request has finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@discardableResult
open func rename(_ publicId: String, to: String, overwrite: Bool? = nil, invalidate: Bool? = nil, params: CLDRenameRequestParams? = nil, completionHandler: ((_ result: CLDRenameResult?, _ error: Error?) -> ())? = nil) -> CLDRenameRequest {
let renameParams = CLDRenameRequestParams(fromPublicId: publicId, toPublicId: to, overwrite: overwrite, invalidate: invalidate)
renameParams.merge(params)
let request = networkCoordinator.callAction(.Rename, params:renameParams)
let renameRequest = CLDRenameRequest(networkRequest: request)
renameRequest.response(completionHandler)
return renameRequest
}
/**
Immediately and permanently delete assets from your Cloudinary account
- parameter publicId: The identifier of the asset to remove.
- parameter params: An object holding the available parameters for the request.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: An instance of `CLDDeleteRequest`,
allowing the options to add response closure to be called once the request has finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@discardableResult
open func destroy(_ publicId: String, params: CLDDestroyRequestParams? = nil, completionHandler: ((_ result: CLDDeleteResult?, _ error: Error?) -> ())? = nil) -> CLDDeleteRequest {
let destroyParams = CLDDestroyRequestParams(publicId: publicId)
destroyParams.merge(params)
let request = networkCoordinator.callAction(.Destroy, params: destroyParams)
let destroyRequest = CLDDeleteRequest(networkRequest: request)
destroyRequest.response(completionHandler)
return destroyRequest
}
/**
Add a tag to one or more assets in your cloud.
Tags are used to categorize and organize your images, and can also be used to apply group actions to images,
for example to delete images, create sprites, ZIP files, JSON lists, or animated GIFs.
Each image can be assigned one or more tags, which is a short name that you can dynamically use (no need to predefine tags).
- parameter tag: The tag to assign.
- parameter publicIds: An array of Public IDs of images uploaded to Cloudinary.
- parameter params: An object holding the available parameters for the request.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: An instance of `CLDTagRequest`,
allowing the options to add response closure to be called once the request has finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@discardableResult
open func addTag(_ tag: String, publicIds: [String], params: CLDTagsRequestParams? = nil, completionHandler: ((_ result: CLDTagResult?, _ error: Error?) -> ())? = nil) -> CLDTagRequest {
let addTagParams = CLDTagsRequestParams(tags: [tag], publicIds: publicIds)
addTagParams.merge(params)
let request = networkCoordinator.callAction(.Tags, params: addTagParams.setCommand(.add))
let tagRequest = CLDTagRequest(networkRequest: request)
tagRequest.response(completionHandler)
return tagRequest
}
/**
Add the tags to one or more assets in your cloud.
Tags are used to categorize and organize your images, and can also be used to apply group actions to images,
for example to delete images, create sprites, ZIP files, JSON lists, or animated GIFs.
Each image can be assigned one or more tags, which is a short name that you can dynamically use (no need to predefine tags).
- parameter tag: An array of tags to assign
- parameter publicIds: An array of Public IDs of images uploaded to Cloudinary.
- parameter params: An object holding the available parameters for the request.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: An instance of `CLDTagRequest`,
allowing the options to add response closure to be called once the request has finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@discardableResult
@objc(addTags:publicIds:params:completionHandler:)
open func addTag(_ tag: [String], publicIds: [String], params: CLDTagsRequestParams? = nil, completionHandler: ((_ result: CLDTagResult?, _ error: Error?) -> ())? = nil) -> CLDTagRequest {
let addTagParams = CLDTagsRequestParams(tags: tag, publicIds: publicIds)
addTagParams.merge(params)
let request = networkCoordinator.callAction(.Tags, params: addTagParams.setCommand(.add))
let tagRequest = CLDTagRequest(networkRequest: request)
tagRequest.response(completionHandler)
return tagRequest
}
/**
Remove a tag to one or more assets in your cloud.
Tags are used to categorize and organize your images, and can also be used to apply group actions to images,
for example to delete images, create sprites, ZIP files, JSON lists, or animated GIFs.
Each image can be assigned one or more tags, which is a short name that you can dynamically use (no need to predefine tags).
- parameter tag: The tag to remove.
- parameter publicIds: An array of Public IDs of images uploaded to Cloudinary.
- parameter params: An object holding the available parameters for the request.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: An instance of `CLDTagRequest`,
allowing the options to add response closure to be called once the request has finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@discardableResult
open func removeTag(_ tag: String, publicIds: [String], params: CLDTagsRequestParams? = nil, completionHandler: ((_ result: CLDTagResult?, _ error: Error?) -> ())? = nil) -> CLDTagRequest {
let removeTagParams = CLDTagsRequestParams(tags: [tag], publicIds: publicIds)
removeTagParams.merge(params)
let request = networkCoordinator.callAction(.Tags, params: removeTagParams.setCommand(.remove))
let tagRequest = CLDTagRequest(networkRequest: request)
tagRequest.response(completionHandler)
return tagRequest
}
/**
Remove tags to one or more assets in your cloud.
Tags are used to categorize and organize your images, and can also be used to apply group actions to images,
for example to delete images, create sprites, ZIP files, JSON lists, or animated GIFs.
Each image can be assigned one or more tags, which is a short name that you can dynamically use (no need to predefine tags).
- parameter tag: An array of tags to remove
- parameter publicIds: An array of Public IDs of images uploaded to Cloudinary.
- parameter params: An object holding the available parameters for the request.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: An instance of `CLDTagRequest`,
allowing the options to add response closure to be called once the request has finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@discardableResult
@objc(removeTags:publicIds:params:completionHandler:)
open func removeTag(_ tag: [String], publicIds: [String], params: CLDTagsRequestParams? = nil, completionHandler: ((_ result: CLDTagResult?, _ error: Error?) -> ())? = nil) -> CLDTagRequest {
let removeTagParams = CLDTagsRequestParams(tags: tag, publicIds: publicIds)
removeTagParams.merge(params)
let request = networkCoordinator.callAction(.Tags, params: removeTagParams.setCommand(.remove))
let tagRequest = CLDTagRequest(networkRequest: request)
tagRequest.response(completionHandler)
return tagRequest
}
/**
Replaces a tag to one or more assets in your cloud.
Tags are used to categorize and organize your images, and can also be used to apply group actions to images,
for example to delete images, create sprites, ZIP files, JSON lists, or animated GIFs.
Each image can be assigned one or more tags, which is a short name that you can dynamically use (no need to predefine tags).
- parameter tag: The tag to replace.
- parameter publicIds: An array of Public IDs of images uploaded to Cloudinary.
- parameter params: An object holding the available parameters for the request.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: An instance of `CLDTagRequest`,
allowing the options to add response closure to be called once the request has finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@discardableResult
open func replaceTag(_ tag: String, publicIds: [String], params: CLDTagsRequestParams? = nil, completionHandler: ((_ result: CLDTagResult?, _ error: Error?) -> ())? = nil) -> CLDTagRequest {
let replaceTagParams = CLDTagsRequestParams(tags: [tag], publicIds: publicIds)
replaceTagParams.merge(params)
let request = networkCoordinator.callAction(.Tags, params: replaceTagParams.setCommand(.replace))
let tagRequest = CLDTagRequest(networkRequest: request)
tagRequest.response(completionHandler)
return tagRequest
}
/**
Replaces tags to one or more assets in your cloud.
Tags are used to categorize and organize your images, and can also be used to apply group actions to images,
for example to delete images, create sprites, ZIP files, JSON lists, or animated GIFs.
Each image can be assigned one or more tags, which is a short name that you can dynamically use (no need to predefine tags).
- parameter tag: The array of tags to replace.
- parameter publicIds: An array of Public IDs of images uploaded to Cloudinary.
- parameter params: An object holding the available parameters for the request.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: An instance of `CLDTagRequest`,
allowing the options to add response closure to be called once the request has finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@discardableResult
@objc(replaceTags:publicIds:params:completionHandler:)
open func replaceTag(_ tag: [String], publicIds: [String], params: CLDTagsRequestParams? = nil, completionHandler: ((_ result: CLDTagResult?, _ error: Error?) -> ())? = nil) -> CLDTagRequest {
let replaceTagParams = CLDTagsRequestParams(tags: tag, publicIds: publicIds)
replaceTagParams.merge(params)
let request = networkCoordinator.callAction(.Tags, params: replaceTagParams.setCommand(.replace))
let tagRequest = CLDTagRequest(networkRequest: request)
tagRequest.response(completionHandler)
return tagRequest
}
/**
The explicit method is used to apply actions to already uploaded images, i.e., to update images that have already been uploaded.
The most common usage of this method is to generate transformations for images that have already been uploaded,
either so that they do not need to be generated on the fly when first accessed by users,
or because Strict Transformations are enabled for your account and you cannot create transformed images on the fly (for more information, see [Access control to images](http://cloudinary.com/documentation/upload_images#control_access_to_images)).
- parameter publicId: The identifier of the uploaded asset.
- parameter type: The specific type of the resource.
- parameter params: An object holding the available parameters for the request.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: An instance of `CLDExplicitRequest`,
allowing the options to add response closure to be called once the request has finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@discardableResult
@objc(explicitPublicId:stringType:params:completionHandler:)
open func explicit(_ publicId: String, type: String, params: CLDExplicitRequestParams? = nil, completionHandler: ((_ result: CLDExplicitResult?, _ error: Error?) -> ())? = nil) -> CLDExplicitRequest {
let explicitParams = CLDExplicitRequestParams(publicId: publicId, type: type)
explicitParams.merge(params)
let request = networkCoordinator.callAction(.Explicit, params: explicitParams)
let explicitRequest = CLDExplicitRequest(networkRequest: request)
explicitRequest.response(completionHandler)
return explicitRequest
}
/**
The explicit method is used to apply actions to already uploaded images, i.e., to update images that have already been uploaded.
The most common usage of this method is to generate transformations for images that have already been uploaded,
either so that they do not need to be generated on the fly when first accessed by users,
or because Strict Transformations are enabled for your account and you cannot create transformed images on the fly (for more information, see [Access control to images](http://cloudinary.com/documentation/upload_images#control_access_to_images)).
- parameter publicId: The identifier of the uploaded asset.
- parameter type: The specific type of the resource.
- parameter params: An object holding the available parameters for the request.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: An instance of `CLDExplicitRequest`,
allowing the options to add response closure to be called once the request has finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@discardableResult
open func explicit(_ publicId: String, type: CLDType, params: CLDExplicitRequestParams? = nil, completionHandler: ((_ result: CLDExplicitResult?, _ error: Error?) -> ())? = nil) -> CLDExplicitRequest {
return explicit(publicId, type: String(describing: type), params: params, completionHandler: completionHandler)
}
/**
The explode method creates derived images for all the individual pages in a PDF file.
Each derived image created is stored with the same Public ID as the PDF file,
and can be accessed using the page parameter for delivering an image of a specific PDF page.
This method is useful for pregenerating all the pages of the PDF as individual images so that they do not need to be generated on the fly when first accessed by your users.
- parameter publicId: The identifier of the uploaded asset.
- parameter transformation: A transformation to run on all the pages before storing them as derived images. This parameter is given as an array (using the SDKs) or comma-separated list (for direct API calls) of transformations, and separated with a slash for chained transformations.
At minimum, you must pass the page transformation with the value all. If you supply additional transformations, you must deliver the image using the same relative order of the page and the other transformations. If you use a different order when you deliver, then it is considered a different transformation, and will be generated on-the-fly as a new derived image.
- parameter params: An object holding the available parameters for the request.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: An instance of `CLDExplodeRequest`,
allowing the options to add response closure to be called once the request has finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@discardableResult
open func explode(_ publicId: String, transformation: CLDTransformation, params: CLDExplodeRequestParams? = nil, completionHandler: ((_ result: CLDExplodeResult?, _ error: Error?) -> ())? = nil) -> CLDExplodeRequest {
let explodeParams = CLDExplodeRequestParams(publicId: publicId, transformation: transformation)
explodeParams.merge(params)
let request = networkCoordinator.callAction(.Explode, params: explodeParams)
let explodeRequest = CLDExplodeRequest(networkRequest: request)
explodeRequest.response(completionHandler)
return explodeRequest
}
/**
Generate sprites by merging multiple images into a single large image for reducing network overhead and bypassing download limitations.
This method creates a sprite from all images that have been assigned a specified tag.
- parameter tag: The sprite is created from all images with this tag.
- parameter params: An object holding the available parameters for the request.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: An instance of `CLDSpriteRequest`,
allowing the options to add response closure to be called once the request has finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@discardableResult
open func generateSprite(_ tag: String, params: CLDSpriteRequestParams? = nil, completionHandler: ((_ result: CLDSpriteResult?, _ error: Error?) -> ())? = nil) -> CLDSpriteRequest {
let generateSpriteParams = CLDSpriteRequestParams(tag: tag)
generateSpriteParams.merge(params)
let request = networkCoordinator.callAction(.GenerateSprite, params: generateSpriteParams)
let spriteRequest = CLDSpriteRequest(networkRequest: request)
spriteRequest.response(completionHandler)
return spriteRequest
}
/**
Create a single animated GIF file from all images that have been assigned a specified tag,
where each image is included as a single frame of the resulting animating GIF (sorted alphabetically by their Public ID).
For a detailed explanation on generating animated GIFs, see the [documentation on creating animated GIFs.](http://cloudinary.com/documentation/image_transformations#creating_animated_gifs)
- parameter tag: The sprite is created from all images with this tag.
- parameter params: An object holding the available parameters for the request.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: An instance of `CLDMultiRequest`,
allowing the options to add response closure to be called once the request has finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@discardableResult
open func multi(_ tag: String, params: CLDMultiRequestParams? = nil, completionHandler: ((_ result: CLDMultiResult?, _ error: Error?) -> ())? = nil) -> CLDMultiRequest {
let multiParams = CLDMultiRequestParams(tag: tag)
multiParams.merge(params)
let request = networkCoordinator.callAction(.Multi, params: multiParams)
let multiRequest = CLDMultiRequest(networkRequest: request)
multiRequest.response(completionHandler)
return multiRequest
}
/**
Dynamically generate an image from a given textual string.
You can then use this textual image as any other image,
for example, as an overlay for other images. Various font,
color and style parameters can be specified to customize the look & feel of the text before converting it to an image.
- parameter text: The text string to generate an image for.
- parameter params: An object holding the available parameters for the request.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: An instance of `CLDTextRequest`,
allowing the options to add response closure to be called once the request has finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@discardableResult
open func text(_ text: String, params: CLDTextRequestParams? = nil, completionHandler: ((_ result: CLDTextResult?, _ error: Error?) -> ())? = nil) -> CLDTextRequest {
let textParams = CLDTextRequestParams(text: text)
textParams.merge(params)
let request = networkCoordinator.callAction(.GenerateText, params: textParams)
let textRequest = CLDTextRequest(networkRequest: request)
textRequest.response(completionHandler)
return textRequest
}
/**
The Cloudinary library supports using a delete token to delete images on the client-side for a limited time of 10 minutes after being uploaded.
After 10 minutes has passed, the image cannot be deleted from the client side, only via the Destroy method.
In order to also receive a deletion token in the upload response, add the return_delete_token parameter to the upload method and set it to true.
- parameter token: The delete token received in the upload response, after uploading the asset using `return_delete_token` set to true.
- parameter params: An object holding the available parameters for the request.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: An instance of `CLDDeleteRequest`,
allowing the options to add response closure to be called once the request has finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@discardableResult
open func deleteByToken(_ token: String, params: CLDDeleteByTokenRequestParams? = nil, completionHandler: ((_ result: CLDDeleteResult?, _ error: Error?) -> ())? = nil) -> CLDDeleteRequest {
let deleteByTokenParams = CLDDeleteByTokenRequestParams(token: token)
deleteByTokenParams.merge(params)
let request = networkCoordinator.callAction(.DeleteByToken, params: deleteByTokenParams)
let deleteByTokenRequest = CLDDeleteRequest(networkRequest: request)
deleteByTokenRequest.response(completionHandler)
return deleteByTokenRequest
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/ManagementApi/Requests/CLDDeleteRequest.swift
================================================
//
// CLDDeleteRequest.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
The `CLDExplodeRequest` object is returned when creating an explode request.
It allows the options to add a response closure to be called once the request has finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@objcMembers open class CLDDeleteRequest: CLDRequest {
/**
Set a response closure to be called once the request has finished.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: The same instance of CLDDeleteRequest.
*/
@discardableResult
open func response(_ completionHandler: ((_ result: CLDDeleteResult?, _ error: NSError?) -> ())?) -> CLDDeleteRequest {
responseRaw { (response, error) in
if let res = response as? [String : AnyObject] {
completionHandler?(CLDDeleteResult(json: res), nil)
}
else if let err = error {
completionHandler?(nil, err)
}
else {
completionHandler?(nil, CLDError.generalError())
}
}
return self
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/ManagementApi/Requests/CLDExplicitRequest.swift
================================================
//
// CLDExplicitRequest.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
The `CLDExplodeRequest` object is returned when creating an explode request.
It allows the options to add a response closure to be called once the request has finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@objcMembers open class CLDExplicitRequest: CLDRequest {
/**
Set a response closure to be called once the request has finished.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: The same instance of CLDExplicitRequest.
*/
@discardableResult
open func response(_ completionHandler: ((_ result: CLDExplicitResult?, _ error: NSError?) -> ())?) -> CLDExplicitRequest {
responseRaw { (response, error) in
if let res = response as? [String : AnyObject] {
completionHandler?(CLDExplicitResult(json: res), nil)
}
else if let err = error {
completionHandler?(nil, err)
}
else {
completionHandler?(nil, CLDError.generalError())
}
}
return self
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/ManagementApi/Requests/CLDExplodeRequest.swift
================================================
//
// CLDExplodeRequest.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
The `CLDExplodeRequest` object is returned when creating an explode request.
It allows the options to add a response closure to be called once the request has finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@objcMembers open class CLDExplodeRequest: CLDRequest {
/**
Set a response closure to be called once the request has finished.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: The same instance of CLDExplodeRequest.
*/
@discardableResult
open func response(_ completionHandler: ((_ result: CLDExplodeResult?, _ error: NSError?) -> ())?) -> CLDExplodeRequest {
responseRaw { (response, error) in
if let res = response as? [String : AnyObject] {
completionHandler?(CLDExplodeResult(json: res), nil)
}
else if let err = error {
completionHandler?(nil, err)
}
else {
completionHandler?(nil, CLDError.generalError())
}
}
return self
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/ManagementApi/Requests/CLDMultiRequest.swift
================================================
//
// CLDMultiRequest.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
The `CLDMultiRequest` object is returned when creating a multi request.
It allows the options to add a response closure to be called once the request has finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@objcMembers open class CLDMultiRequest: CLDRequest {
/**
Set a response closure to be called once the request has finished.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: The same instance of CLDMultiRequest.
*/
@discardableResult
open func response(_ completionHandler: ((_ result: CLDMultiResult?, _ error: NSError?) -> ())?) -> CLDMultiRequest {
responseRaw { (response, error) in
if let res = response as? [String : AnyObject] {
completionHandler?(CLDMultiResult(json: res), nil)
}
else if let err = error {
completionHandler?(nil, err)
}
else {
completionHandler?(nil, CLDError.generalError())
}
}
return self
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/ManagementApi/Requests/CLDRenameRequest.swift
================================================
//
// CLDRenameRequest.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
The `CLDRenameRequest` object is returned when creating a rename request.
It allows the options to add a response closure to be called once the request has finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@objcMembers open class CLDRenameRequest: CLDRequest {
/**
Set a response closure to be called once the request has finished.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: The same instance of CLDRenameRequest.
*/
@discardableResult
open func response(_ completionHandler: ((_ result: CLDRenameResult?, _ error: NSError?) -> ())?) -> CLDRenameRequest {
responseRaw { (response, error) in
if let res = response as? [String : AnyObject] {
completionHandler?(CLDRenameResult(json: res), nil)
}
else if let err = error {
completionHandler?(nil, err)
}
else {
completionHandler?(nil, CLDError.generalError())
}
}
return self
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/ManagementApi/Requests/CLDSpriteRequest.swift
================================================
//
// CLDSpriteRequest.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
The `CLDSpriteRequest` object is returned when creating a sprite request.
It allows the options to add a response closure to be called once the request has finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@objcMembers open class CLDSpriteRequest: CLDRequest {
/**
Set a response closure to be called once the request has finished.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: The same instance of CLDSpriteRequest.
*/
@discardableResult
open func response(_ completionHandler: ((_ result: CLDSpriteResult?, _ error: NSError?) -> ())?) -> CLDSpriteRequest {
responseRaw { (response, error) in
if let res = response as? [String : AnyObject] {
completionHandler?(CLDSpriteResult(json: res), nil)
}
else if let err = error {
completionHandler?(nil, err)
}
else {
completionHandler?(nil, CLDError.generalError())
}
}
return self
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/ManagementApi/Requests/CLDTagRequest.swift
================================================
//
// CLDTagRequest.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
The `CLDTagRequest` object is returned when creating a tag request.
It allows the options to add a response closure to be called once the request has finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@objcMembers open class CLDTagRequest: CLDRequest {
/**
Set a response closure to be called once the request has finished.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: The same instance of CLDTagRequest.
*/
@discardableResult
open func response(_ completionHandler: ((_ result: CLDTagResult?, _ error: NSError?) -> ())?) -> CLDTagRequest {
responseRaw { (response, error) in
if let res = response as? [String : AnyObject] {
completionHandler?(CLDTagResult(json: res), nil)
}
else if let err = error {
completionHandler?(nil, err)
}
else {
completionHandler?(nil, CLDError.generalError())
}
}
return self
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/ManagementApi/Requests/CLDTextRequest.swift
================================================
//
// CLDTextRequest.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
The `CLDTagRequest` object is returned when creating a tag request.
It allows the options to add a response closure to be called once the request has finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@objcMembers open class CLDTextRequest: CLDRequest {
/**
Set a response closure to be called once the request has finished.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: The same instance of CLDTextRequest.
*/
@discardableResult
open func response(_ completionHandler: ((_ result: CLDTextResult?, _ error: NSError?) -> ())?) -> CLDTextRequest {
responseRaw { (response, error) in
if let res = response as? [String : AnyObject] {
completionHandler?(CLDTextResult(json: res), nil)
}
else if let err = error {
completionHandler?(nil, err)
}
else {
completionHandler?(nil, CLDError.generalError())
}
}
return self
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/ManagementApi/RequestsParams/CLDDeleteByTokenRequestParams.swift
================================================
//
// CLDDeleteByTokenRequestParams.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
This class represents the different parameters that can be passed when performing a `delete by token` request.
*/
open class CLDDeleteByTokenRequestParams: CLDRequestParams {
// MARK: Init
fileprivate override init() {
super.init()
}
/**
Initializes a CLDDeleteByTokenRequestParams instance.
- parameter token: The delete token received in the upload response, after uploading the asset using `return_delete_token` set to true.
- returns: A new instance of CLDDeleteByTokenRequestParams.
*/
internal init(token: String) {
super.init()
setParam(DeleteByTokenParams.Token.rawValue, value: token)
}
/**
Initializes a CLDDeleteByTokenRequestParams instance.
- parameter params: A dictionary of the request parameters.
- returns: A new instance of CLDDeleteByTokenRequestParams.
*/
public init(params: [String : Any]) {
super.init()
self.params = params
}
// MARK: Params
fileprivate enum DeleteByTokenParams: String {
case Token = "token"
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/ManagementApi/RequestsParams/CLDDestroyRequestParams.swift
================================================
//
// CLDDestroyRequestParams.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
This class represents the different parameters that can be passed when performing a destroy request.
*/
@objcMembers open class CLDDestroyRequestParams: CLDRequestParams {
//MARK: Init
public override init() {
super.init()
}
/**
Initializes a CLDDestroyRequestParams instance.
- parameter publicId: The identifier of the asset to remove.
- returns: A new instance of CLDDestroyRequestParams.
*/
internal init(publicId: String) {
super.init()
setParam(DestroyParams.PublicId.rawValue, value: publicId)
}
/**
Initializes a CLDDestroyRequestParams instance.
- parameter params: A dictionary of the request parameters.
- returns: A new instance of CLDDestroyRequestParams.
*/
public init(params: [String : AnyObject]) {
super.init()
self.params = params
}
//MARK: Set Params
/**
Set the specific file type of the resource using one of the available options from CLDType.
- parameter type: The file type to set.
- returns: The same instance of CLDDestroyRequestParams.
*/
@discardableResult
@objc(setTypeWithType:)
open func setType(_ type: CLDType) -> Self {
return setType(String(describing: type))
}
/**
Set the specific file type of the resource.
- parameter type: The specific file type of the resource.
- returns: The same instance of CLDExplodeRequestParams.
*/
@discardableResult
open func setType(_ type: String) -> Self {
super.setParam(DestroyParams.CloudType.rawValue, value: type)
return self
}
/**
Set boolean parameter indicating whether or not the asset should be invalidated through the CDN. default is false.
- parameter invalidate: The boolean parameter.
- returns: The same instance of CLDDestroyRequestParams.
*/
@discardableResult
open func setInvalidate(_ invalidate: Bool) -> Self {
super.setParam(DestroyParams.Invalidate.rawValue, value: invalidate)
return self
}
fileprivate enum DestroyParams: String {
case PublicId = "public_id"
case CloudType = "type"
case Invalidate = "invalidate"
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/ManagementApi/RequestsParams/CLDExplicitRequestParams.swift
================================================
//
// CLDExplicitRequestParams.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
This class represents the different parameters that can be passed when performing an `explicit` request.
*/
@objcMembers open class CLDExplicitRequestParams: CLDUploadRequestParams {
//MARK: Init
public override init() {
super.init()
}
/**
Initializes a CLDExplicitRequestParams instance.
- parameter publicId: The identifier of the uploaded asset.
- parameter type: The specific file type of the resource.
- returns: A new instance of CLDExplicitRequestParams.
*/
internal convenience init(publicId: String, type: CLDType) {
self.init(publicId: publicId, type: String(describing: type))
}
/**
Initializes a CLDExplicitRequestParams instance.
- parameter publicId: The identifier of the uploaded asset.
- parameter type: The specific file type of the resource.
- returns: A new instance of CLDExplicitRequestParams.
*/
@objc(initWithPublicId:andType:)
internal init(publicId: String, type: String) {
super.init()
setPublicId(publicId)
setType(type)
}
/**
Initializes a CLDExplicitRequestParams instance.
- parameter params: A dictionary of the request parameters.
- returns: A new instance of CLDExplicitRequestParams.
*/
public override init(params: [String : AnyObject]) {
super.init(params: params)
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/ManagementApi/RequestsParams/CLDExplodeRequestParams.swift
================================================
//
// CLDExplodeRequestParams.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
This class represents the different parameters that can be passed when performing an `explode` request.
*/
@objcMembers open class CLDExplodeRequestParams: CLDRequestParams {
// MARK: Init
public override init() {
super.init()
}
/**
Initializes a CLDExplodeRequestParams instance.
- parameter publicId: The identifier of the uploaded asset.
- parameter transformation: A transformation to run on all the pages before storing them as derived images. This parameter is given as an array (using the SDKs) or comma-separated list (for direct API calls) of transformations, and separated with a slash for chained transformations.
At minimum, you must pass the page transformation with the value all. If you supply additional transformations, you must deliver the image using the same relative order of the page and the other transformations. If you use a different order when you deliver, then it is considered a different transformation, and will be generated on-the-fly as a new derived image.
- returns: A new instance of CLDExplodeRequestParams.
*/
internal init(publicId: String, transformation: CLDTransformation) {
super.init()
setParam(ExplodeParams.PublicId.rawValue, value: publicId)
setParam(ExplodeParams.Transformation.rawValue, value: transformation.asString())
}
/**
Initializes a CLDExplodeRequestParams instance.
- parameter params: A dictionary of the request parameters.
- returns: A new instance of CLDExplodeRequestParams.
*/
public init(params: [String : AnyObject]) {
super.init()
self.params = params
}
// MARK: Set Params
/**
Set the specific file type of the resource.
- parameter type: The specific file type of the resource.
- returns: The same instance of CLDExplodeRequestParams.
*/
@discardableResult
@objc(setTypeWithType:)
open func setType(_ type: CLDType) -> Self {
return setType(String(describing: type))
}
/**
Set the specific file type of the resource.
- parameter type: The specific file type of the resource.
- returns: The same instance of CLDExplodeRequestParams.
*/
@discardableResult
open func setType(_ type: String) -> Self {
setParam(ExplodeParams.CloudType.rawValue, value: type)
return self
}
/**
Set a format to convert the images before storing them in your Cloudinary account. default is jpg.
- parameter format: The format to convert to.
- returns: The same instance of CLDExplodeRequestParams.
*/
@discardableResult
open func setFormat(_ format: String) -> Self {
setParam(ExplodeParams.Format.rawValue, value: format)
return self
}
/**
Set a boolean parameter indicating whether to perform the image generation in the background (asynchronously). default is false.
- parameter async: The boolean parameter.
- returns: The same instance of CLDExplodeRequestParams.
*/
@discardableResult
open func setAsync(_ async: Bool) -> Self {
setParam(ExplodeParams.Async.rawValue, value: async)
return self
}
/**
Set an HTTP or HTTPS URL to notify your application (a webhook) when the process has completed.
- parameter notificationUrl: The URL.
- returns: The same instance of CLDExplodeRequestParams.
*/
@discardableResult
open func setNotificationUrl(_ notificationUrl: String) -> Self {
setParam(ExplodeParams.NotificationUrl.rawValue, value: notificationUrl)
return self
}
fileprivate enum ExplodeParams: String {
case PublicId = "public_id"
case CloudType = "type"
case Transformation = "transformation"
case Format = "format"
case Async = "async"
case NotificationUrl = "notification_url"
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/ManagementApi/RequestsParams/CLDMultiRequestParams.swift
================================================
//
// CLDMultiRequestParams.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
This class represents the different parameters that can be passed when performing a request to generate a multi-image.
*/
open class CLDMultiRequestParams: CLDRequestParams {
// MARK: Init
public override init() {
super.init()
}
/**
Initializes a CLDMultiRequestParams instance.
- parameter tag: The sprite is created from all images with this tag.
- returns: A new instance of CLDMultiRequestParams.
*/
internal init(tag: String) {
super.init()
setParam(MultiParams.Tag.rawValue, value: tag)
}
/**
Initializes a CLDMultiRequestParams instance.
- parameter params: A dictionary of the request parameters.
- returns: A new instance of CLDMultiRequestParams.
*/
public init(params: [String : AnyObject]) {
super.init()
self.params = params
}
// MARK: Set Params
/**
Set a transformation to run on all the individual images before creating the sprite.
- parameter transformation: The transformation to run.
- returns: A new instance of CLDMultiRequestParams.
*/
@discardableResult
open func setTransformation(_ transformation: CLDTransformation) -> Self {
if let trans = transformation.asString() {
super.setParam(MultiParams.Transformation.rawValue, value: trans)
}
return self
}
/**
Set a format to convert the images before storing them in your Cloudinary account. default is jpg.
- parameter format: The format to convert to.
- returns: A new instance of CLDMultiRequestParams.
*/
@discardableResult
open func setFormat(_ format: String) -> Self {
super.setParam(MultiParams.Format.rawValue, value: format)
return self
}
/**
Set a boolean parameter indicating whether to perform the image generation in the background (asynchronously). default is false.
- parameter async: The boolean parameter.
- returns: A new instance of CLDMultiRequestParams.
*/
@discardableResult
open func setAsync(_ async: Bool) -> Self {
super.setParam(MultiParams.Async.rawValue, value: async)
return self
}
/**
Set an HTTP or HTTPS URL to notify your application (a webhook) when the process has completed.
- parameter notificationUrl: The URL.
- returns: A new instance of CLDMultiRequestParams.
*/
@discardableResult
open func setNotificationUrl(_ notificationUrl: String) -> Self {
super.setParam(MultiParams.NotificationUrl.rawValue, value: notificationUrl)
return self
}
fileprivate enum MultiParams: String {
case Tag = "tag"
case Transformation = "transformation"
case Format = "format"
case Async = "async"
case NotificationUrl = "notification_url"
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/ManagementApi/RequestsParams/CLDRenameRequestParams.swift
================================================
//
// CLDRenameRequestParams.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
This class represents the different parameters that can be passed when performing a rename request.
*/
@objcMembers open class CLDRenameRequestParams: CLDRequestParams {
// MARK: Init
public override init() {
super.init()
}
/**
Initializes a CLDRenameRequestParams instance.
- parameter fromPublicId: The current identifier of the uploaded asset.
- parameter toPublicId: The new identifier to assign to the uploaded asset.
- parameter overwrite: A boolean parameter indicating whether or not to overwrite an existing image with the target Public ID. Default: false.
- parameter invalidate: A boolean parameter whether to invalidate CDN cached copies of the image (and all its transformed versions). Default: false.
- parameter notification_url An HTTP or HTTPS URL to notify your application (a webhook) when the process has completed. If not specified, the response is sent to the Notification URL (if defined) in the Webhook Notifications settings of your Cloudinary Console.
- parameter context: A boolean parameter indicating whether to include contextual metadata for the asset in the response. Default: false.
- parameter metadata: A boolean parameter indicating whether to include structured metadata for the asset in the response. Default: false.
- returns: A new instance of CLDRenameRequestParams.
*/
internal init(fromPublicId: String, toPublicId: String, overwrite: Bool? = nil, invalidate: Bool? = nil, notificationUrl: String? = nil, context: Bool? = nil, metadata: Bool? = nil) {
super.init()
setParam(RenameParams.FromPublicId.rawValue, value: fromPublicId)
setParam(RenameParams.ToPublicId.rawValue, value: toPublicId)
setParam(RenameParams.Overwrite.rawValue, value: overwrite)
setParam(RenameParams.Invalidate.rawValue, value: invalidate)
setParam(RenameParams.NotificationUrl.rawValue, value: notificationUrl)
setParam(RenameParams.Context.rawValue, value: context)
setParam(RenameParams.Metadata.rawValue, value: metadata)
}
/**
Initializes a CLDRenameRequestParams instance.
- parameter params: A dictionary of the request parameters.
- returns: A new instance of CLDRenameRequestParams.
*/
public init(params: [String : AnyObject]) {
super.init()
self.params = params
}
// MARK: - Rename Params
fileprivate enum RenameParams: String {
case FromPublicId = "from_public_id"
case ToPublicId = "to_public_id"
case Overwrite = "overwrite"
case Invalidate = "invalidate"
case NotificationUrl = "notification_url"
case Context = "context"
case Metadata = "metadata"
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/ManagementApi/RequestsParams/CLDSpriteRequestParams.swift
================================================
//
// CLDSpriteRequestParams.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
This class represents the different parameters that can be passed when performing a request to generate a sprite.
*/
@objcMembers open class CLDSpriteRequestParams: CLDRequestParams {
// MARK: Init
public override init() {
super.init()
}
/**
Initializes a CLDSpriteRequestParams instance.
- parameter tag: The sprite is created from all images with this tag.
- returns: A new instance of CLDSpriteRequestParams.
*/
internal init(tag: String) {
super.init()
setParam(SpriteParams.Tag.rawValue, value: tag)
}
/**
Initializes a CLDSpriteRequestParams instance.
- parameter params: A dictionary of the request parameters.
- returns: A new instance of CLDSpriteRequestParams.
*/
public init(params: [String : AnyObject]) {
super.init()
self.params = params
}
// MARK: Set Params
/**
Set a transformation to run on all the individual images before creating the sprite.
- parameter transformation: The transformation to run.
- returns: A new instance of CLDSpriteRequestParams.
*/
@discardableResult
open func setTransformation(_ transformation: CLDTransformation) -> Self {
if let stringRep = transformation.asString() {
setParam(SpriteParams.Transformation.rawValue, value: stringRep)
}
return self
}
/**
Set a format to convert the images before storing them in your Cloudinary account. default is jpg.
- parameter format: The format to convert to.
- returns: A new instance of CLDSpriteRequestParams.
*/
@discardableResult
open func setFormat(_ format: String) -> Self {
super.setParam(SpriteParams.Format.rawValue, value: format)
return self
}
/**
Set a boolean parameter indicating whether to perform the image generation in the background (asynchronously). default is false.
- parameter async: The boolean parameter.
- returns: A new instance of CLDSpriteRequestParams.
*/
@discardableResult
open func setAsync(_ async: Bool) -> Self {
super.setParam(SpriteParams.Async.rawValue, value: async)
return self
}
/**
Set an HTTP or HTTPS URL to notify your application (a webhook) when the process has completed.
- parameter notificationUrl: The URL.
- returns: A new instance of CLDSpriteRequestParams.
*/
@discardableResult
open func setNotificationUrl(_ notificationUrl: String) -> Self {
super.setParam(SpriteParams.NotificationUrl.rawValue, value: notificationUrl)
return self
}
fileprivate enum SpriteParams: String {
case Tag = "tag"
case Transformation = "transformation"
case Format = "format"
case Async = "async"
case NotificationUrl = "notification_url"
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/ManagementApi/RequestsParams/CLDTagsRequestParams.swift
================================================
//
// CLDTagsRequestParams.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
This class represents the different parameters that can be passed when performing a tag request.
*/
@objcMembers open class CLDTagsRequestParams: CLDRequestParams {
//MARK: Init
public override init() {
super.init()
}
/**
Initializes a CLDTagsRequestParams instance.
- parameter tag: The tag to assign, remove, or replace.
- parameter publicIds: An array of Public IDs of images uploaded to Cloudinary.
- returns: A new instance of CLDTagsRequestParams.
*/
internal init(tags: [String], publicIds: [String]) {
super.init()
setParam(TagsParams.Tag.rawValue, value: tags.joined(separator: ","))
setParam(TagsParams.PublicIds.rawValue, value: publicIds)
}
/**
Initializes a CLDTagsRequestParams instance.
- parameter params: A dictionary of the request parameters.
- returns: A new instance of CLDTagsRequestParams.
*/
public init(params: [String : AnyObject]) {
super.init()
self.params = params
}
// MARK: - Set Params
/**
Sets the desired action to be done.
- parameter command: The action to perform on asset resources using the given tag.
Either add the given tag, remove the given tag, or replace the given tag,
which adds the given tag while removing all other tags assigned.
- returns: The same instance of CLDTagsRequestParams.
*/
internal func setCommand(_ command: TagsCommand) -> Self {
setParam(TagsParams.Command.rawValue, value: command.description)
return self
}
// MARK: Private
fileprivate enum TagsParams: String {
case PublicIds = "public_ids"
case Tag = "tag"
case Command = "command"
}
internal enum TagsCommand: CustomStringConvertible {
case add, remove, replace
var description: String {
switch self {
case .add: return "add"
case .remove: return "remove"
case .replace: return "replace"
}
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/ManagementApi/RequestsParams/CLDTextRequestParams.swift
================================================
//
// CLDTextRequestParams.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
This class represents the different parameters that can be passed when performing a request to generate a text-image.
*/
open class CLDTextRequestParams: CLDRequestParams {
// MARK: Init
public override init() {
super.init()
}
/**
Initializes a CLDTextRequestParams instance.
- parameter text: The text string to generate an image for.
- returns: A new instance of CLDTextRequestParams.
*/
internal init(text: String) {
super.init()
setParam(TextParams.Text.rawValue, value: text)
}
/**
Initializes a CLDTextRequestParams instance.
- parameter params: A dictionary of the request parameters.
- returns: A new instance of CLDTextRequestParams.
*/
public init(params: [String : AnyObject]) {
super.init()
self.params = params
}
// MARK: Set Params
/**
Set an identifier that is used for accessing the generated image.
If not specified, a unique identifier is generated by Cloudinary.
- parameter publicId: The identifier.
- returns: A new instance of CLDTextRequestParams.
*/
@discardableResult
open func setPublicId(_ publicId: String) -> CLDTextRequestParams {
setParam(TextParams.PublicId.rawValue, value: publicId)
return self
}
/**
Set a font family.
- parameter fontFamily: The name of the font family.
- returns: A new instance of CLDTextRequestParams.
*/
@discardableResult
open func setFontFamily(_ fontFamily: String) -> CLDTextRequestParams {
setParam(TextParams.FontFamily.rawValue, value: fontFamily)
return self
}
/**
Set the font size in points.
- parameter fontSize: The font size in points. default is 12.
- returns: A new instance of CLDTextRequestParams.
*/
@discardableResult
open func setFontSizeFromInt(_ fontSize: Int) -> CLDTextRequestParams {
return setFontSize(String(fontSize))
}
/**
Set the font size in points.
- parameter fontSize: The font size in points. default is 12.
- returns: A new instance of CLDTextRequestParams.
*/
@discardableResult
open func setFontSize(_ fontSize: String) -> CLDTextRequestParams {
setParam(TextParams.FontSize.rawValue, value: fontSize)
return self
}
/**
Set the font size in points.
- parameter fontColor: A name or RGB representation of the font's color. For example: `red` or #ff0000. default is black.
- returns: A new instance of CLDTextRequestParams.
*/
@discardableResult
open func setFontColor(_ fontColor: String) -> CLDTextRequestParams {
setParam(TextParams.FontColor.rawValue, value: fontColor)
return self
}
/**
Set the font weight.
- parameter fontWeight: The font weight to set. default is normal.
- returns: A new instance of CLDTextRequestParams.
*/
@discardableResult
@objc(setFontWeightFromFontWeight:)
open func setFontWeight(_ fontWeight: CLDFontWeight) -> CLDTextRequestParams {
return setFontWeight(String(describing: fontWeight))
}
/**
Set the font weight.
- parameter fontWeight: The font weight to set. default is normal.
- returns: A new instance of CLDTextRequestParams.
*/
@discardableResult
open func setFontWeight(_ fontWeight: String) -> CLDTextRequestParams {
setParam(TextParams.FontWeight.rawValue, value: fontWeight)
return self
}
/**
Set the font style.
- parameter fontStyle: The font style to set. default is normal.
- returns: A new instance of CLDTextRequestParams.
*/
@discardableResult
@objc(setFontStyleFromFontStyle:)
open func setFontStyle(_ fontStyle: CLDFontStyle) -> CLDTextRequestParams {
return setFontStyle(String(describing: fontStyle))
}
/**
Set the font style.
- parameter fontStyle: The font style to set. default is normal.
- returns: A new instance of CLDTextRequestParams.
*/
@discardableResult
open func setFontStyle(_ fontStyle: String) -> CLDTextRequestParams {
setParam(TextParams.FontStyle.rawValue, value: fontStyle)
return self
}
/**
Set the background color.
- parameter background: A name or RGB representation of the background color. For example: `red` or #ff0000. default is transparent.
- returns: A new instance of CLDTextRequestParams.
*/
@discardableResult
open func setBackground(_ background: String) -> CLDTextRequestParams {
setParam(TextParams.Background.rawValue, value: background)
return self
}
/**
Set the text opacity level from 0 to 100.
- parameter opacity: The text opacity value between 0 (invisible) and 100. default is 100.
- returns: A new instance of CLDTextRequestParams.
*/
@discardableResult
open func setOpacity(_ opacity: Int) -> CLDTextRequestParams {
setParam(TextParams.Opacity.rawValue, value: opacity)
return self
}
/**
Set a text decoration to add the the generated text, for example: underline.
- parameter textDecoration: The text decoration to set. default is none.
- returns: A new instance of CLDTextRequestParams.
*/
@discardableResult
@objc(setTextDecorationFromTextDecoration:)
open func setTextDecoration(_ textDecoration: CLDTextDecoration) -> CLDTextRequestParams {
return setTextDecoration(String(describing: textDecoration))
}
/**
Set a text decoration to add the the generated text, for example: underline.
- parameter textDecoration: The text decoration to set. default is none.
- returns: A new instance of CLDTextRequestParams.
*/
@discardableResult
open func setTextDecoration(_ textDecoration: String) -> CLDTextRequestParams {
setParam(TextParams.TextDecoration.rawValue, value: textDecoration)
return self
}
fileprivate enum TextParams: String {
case Text = "text"
case PublicId = "public_id"
case FontFamily = "font_family"
case FontSize = "font_size"
case FontColor = "font_color"
case FontWeight = "font_weight"
case FontStyle = "font_style"
case Background = "background"
case Opacity = "opacity"
case TextDecoration = "text_decoration"
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/ManagementApi/Results/CLDDeleteResult.swift
================================================
//
// CLDDeleteResult.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDDeleteResult: CLDBaseResult {
// MARK: - Getters
open var result: String? {
return getParam(.result) as? String
}
// MARK: - Private Helpers
fileprivate func getParam(_ param: DeleteResultKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum DeleteResultKey: CustomStringConvertible {
case result
var description: String {
switch self {
case .result: return "result"
}
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/ManagementApi/Results/CLDExplicitResult.swift
================================================
//
// CLDExplicitResult.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objc open class CLDExplicitResult: CLDUploadResult {
}
================================================
FILE: Cloudinary/Classes/Core/Features/ManagementApi/Results/CLDExplodeResult.swift
================================================
//
// CLDExplodeResult.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDExplodeResult: CLDBaseResult {
// MARK: - Getters
open var status: String? {
return getParam(.status) as? String
}
open var batchId: String? {
return getParam(.batchId) as? String
}
// MARK: - Private Helpers
fileprivate func getParam(_ param: ExplodeResultKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum ExplodeResultKey: CustomStringConvertible {
case status, batchId
var description: String {
switch self {
case .status: return "status"
case .batchId: return "batch_id"
}
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/ManagementApi/Results/CLDMultiResult.swift
================================================
//
// CLDMultiResult.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDMultiResult: CLDBaseResult {
// MARK: - Getters
open var url: String? {
return getParam(.url) as? String
}
open var secureUrl: String? {
return getParam(.secureUrl) as? String
}
open var publicId: String? {
return getParam(.publicId) as? String
}
open var version: String? {
guard let version = getParam(.version) else {
return nil
}
return String(describing: version)
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/ManagementApi/Results/CLDRenameResult.swift
================================================
//
// CLDRenameResult.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDRenameResult: CLDBaseResult {
// MARK: - Getters
open var publicId: String? {
return getParam(.publicId) as? String
}
open var format: String? {
return getParam(.format) as? String
}
open var version: String? {
guard let version = getParam(.version) else {
return nil
}
return String(describing: version)
}
open var resourceType: String? {
return getParam(.resourceType) as? String
}
open var type: String? {
return getParam(.urlType) as? String
}
open var createdAt: String? {
return getParam(.createdAt) as? String
}
open var length: Double? {
return getParam(.length) as? Double
}
open var width: Int? {
return getParam(.width) as? Int
}
open var height: Int? {
return getParam(.height) as? Int
}
open var url: String? {
return getParam(.url) as? String
}
open var secureUrl: String? {
return getParam(.secureUrl) as? String
}
open var nextCursor: String? {
return getParam(.nextCursor) as? String
}
open var exif: [String : String]? {
return getParam(.exif) as? [String : String]
}
open var metadata: [String : String]? {
return getParam(.metadata) as? [String : String]
}
open var metadataObject: [String: String]? {
return getParam(.metadata_object) as? [String: String]
}
open var faces: AnyObject? {
return getParam(.faces)
}
open var colors: AnyObject? {
return getParam(.colors)
}
open var derived: CLDDerived? {
guard let derived = getParam(.derived) as? [String : AnyObject] else {
return nil
}
return CLDDerived(json: derived)
}
open var tags: [String]? {
return getParam(.tags) as? [String]
}
open var moderation: AnyObject? {
return getParam(.moderation)
}
open var context: AnyObject? {
return getParam(.context)
}
open var phash: String? {
return getParam(.phash) as? String
}
open var predominant: CLDPredominant? {
guard let predominant = getParam(.predominant) as? [String : AnyObject] else {
return nil
}
return CLDPredominant(json: predominant)
}
open var coordinates: CLDCoordinates? {
guard let coordinates = getParam(RenameResultKey.coordinates) as? [String : AnyObject] else {
return nil
}
return CLDCoordinates(json: coordinates)
}
open var info: CLDInfo? {
guard let info = getParam(.info) as? [String : AnyObject] else {
return nil
}
return CLDInfo(json: info)
}
// MARK: - Private Helpers
fileprivate func getParam(_ param: RenameResultKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum RenameResultKey: CustomStringConvertible {
case nextCursor, derived, predominant, coordinates
var description: String {
switch self {
case .nextCursor: return "next_cursor"
case .derived: return "derived"
case .predominant: return "predominant"
case .coordinates: return "coordinates"
}
}
}
}
// MARK: - CLDCoordinates
@objcMembers open class CLDCoordinates: CLDBaseResult {
open var custom: AnyObject? {
return getParam(.custom)
}
open var faces: AnyObject? {
return getParam(.faces)
}
// MARK: Private Helpers
fileprivate func getParam(_ param: CLDCoordinatesKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum CLDCoordinatesKey: CustomStringConvertible {
case custom
var description: String {
switch self {
case .custom: return "custom"
}
}
}
}
// MARK: - CLDPredominant
@objcMembers open class CLDPredominant: CLDBaseResult {
open var google: AnyObject? {
return getParam(.google)
}
// MARK: - Private Helpers
fileprivate func getParam(_ param: CLDPredominantKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum CLDPredominantKey: CustomStringConvertible {
case google
var description: String {
switch self {
case .google: return "google"
}
}
}
}
// MARK: - CLDDerived
@objcMembers open class CLDDerived: CLDBaseResult {
open var transformation: String? {
return getParam(.transformation) as? String
}
open var format: String? {
return getParam(.format) as? String
}
open var length: Double? {
return getParam(.length) as? Double
}
open var identifier: String? {
return getParam(.id) as? String
}
open var url: String? {
return getParam(.url) as? String
}
open var secureUrl: String? {
return getParam(.secureUrl) as? String
}
// MARK: - Private Helpers
fileprivate func getParam(_ param: CLDDerivedKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum CLDDerivedKey: CustomStringConvertible {
case transformation, id
var description: String {
switch self {
case .transformation: return "transformation"
case .id: return "id"
}
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/ManagementApi/Results/CLDSpriteResult.swift
================================================
//
// CLDSpriteResult.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDSpriteResult: CLDBaseResult {
// MARK: - Getters
open var cssUrl: String? {
return getParam(.cssUrl) as? String
}
open var secureCssUrl: String? {
return getParam(.secureCssUrl) as? String
}
open var imageUrl: String? {
return getParam(.imageUrl) as? String
}
open var jsonUrl: String? {
return getParam(.jsonUrl) as? String
}
open var publicId: String? {
return getParam(.publicId) as? String
}
open var version: String? {
guard let version = getParam(.version) else {
return nil
}
return String(describing: version)
}
open var imageInfos: [String : CLDImageInfo]? {
guard let imageInfosDic = getParam(.imageInfos) as? [String : AnyObject] else {
return nil
}
var imageInfos: [String : CLDImageInfo] = [:]
for key in imageInfosDic.keys {
if let imgInfo = imageInfosDic[key] as? [String : AnyObject] {
imageInfos[key] = CLDImageInfo(json: imgInfo)
}
}
return imageInfos
}
// MARK: - Private Helpers
fileprivate func getParam(_ param: SpriteResultKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum SpriteResultKey: CustomStringConvertible {
case cssUrl, secureCssUrl, imageUrl, jsonUrl, imageInfos
var description: String {
switch self {
case .cssUrl: return "css_url"
case .secureCssUrl: return "secure_css_url"
case .imageUrl: return "image_url"
case .jsonUrl: return "json_url"
case .imageInfos: return "image_infos"
}
}
}
}
@objcMembers open class CLDImageInfo: CLDBaseResult {
open var x: Int? {
return getParam(.x) as? Int
}
open var y: Int? {
return getParam(.y) as? Int
}
open var width: Int? {
return getParam(.width) as? Int
}
open var height: Int? {
return getParam(.height) as? Int
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/ManagementApi/Results/CLDTagResult.swift
================================================
//
// CLDTagResult.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDTagResult: CLDBaseResult {
// MARK: - Getters
open var publicIds: [String]? {
return getParam(.publicIds) as? [String]
}
// MARK: - Private Helpers
fileprivate func getParam(_ param: TagResultKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum TagResultKey: CustomStringConvertible {
case publicIds
var description: String {
switch self {
case .publicIds: return "public_ids"
}
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/ManagementApi/Results/CLDTextResult.swift
================================================
//
// CLDTextResult.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDTextResult: CLDBaseResult {
// MARK: - Getters
open var width: Int? {
return getParam(.width) as? Int
}
open var height: Int? {
return getParam(.height) as? Int
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/UploadWidget/CLDUploaderWidget.swift
================================================
//
// CLDUploaderWidget.swift
//
// Copyright (c) 2020 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import UIKit
import AVKit
import MobileCoreServices
@objc public protocol CLDUploaderWidgetDelegate: AnyObject {
/**
Informs the delegate that the upload process will begin for the added requests.
- parameter widget: The widget object.
- parameter uploadRequests: The `CLDUploadRequest`s to be uploaded.
*/
func uploadWidget(_ widget: CLDUploaderWidget, willCall uploadRequests: [CLDUploadRequest])
/**
Informs the delegate that the widget is canceled.
- parameter widget: The widget object.
*/
func widgetDidCancel(_ widget: CLDUploaderWidget)
/**
Informs the delegate that the widget view is dismissed.
- parameter widget: The widget object.
*/
func uploadWidgetDidDismiss()
}
@objcMembers public class CLDUploaderWidget: NSObject {
public private(set) weak var rootViewController : UIViewController!
public private(set) var cloudinaryObject : CLDCloudinary
public private(set) var configuration : CLDWidgetConfiguration?
public private(set) var images : [UIImage]
public private(set) var videos : [AVPlayerItem]
private var widgetViewController : CLDWidgetViewController!
private var widgetPresented : Bool
private var imagePicker : UIImagePickerController!
public weak var delegate : CLDUploaderWidgetDelegate?
private var selectAssetFromLibrary: Bool {
self.images.count == 0 && self.videos.count == 0
}
// MARK - public methods
/**
Initializes the `CLDUploaderWidget` instance with the specified cloudinary, configuration, images and delegate.
- parameter cloudinary: The CLDCloudinary object to be used for uploading the selected assets.
- parameter configuration: The configuration used by this CLDUploaderWidget instance.
- parameter images: The images to be presented, edited and uploaded.
- parameter delegate: The delegate object conforming to `CLDUploaderWidgetDelegate`.
- returns: The new `CLDUploaderWidget` instance.
*/
convenience public init(
cloudinary: CLDCloudinary,
configuration: CLDWidgetConfiguration?,
images: [UIImage]?,
delegate: CLDUploaderWidgetDelegate?
) {
self.init(cloudinary: cloudinary, configuration: configuration, images: images, videos: nil, delegate: delegate)
}
/**
Initializes the `CLDUploaderWidget` instance with the specified cloudinary, configuration, images, videos and delegate.
- parameter cloudinary: The CLDCloudinary object to be used for uploading the selected assets.
- parameter configuration: The configuration used by this CLDUploaderWidget instance.
- parameter images: The images to be presented, edited and uploaded.
- parameter videos: The videos to be presented and uploaded.
- parameter delegate: The delegate object conforming to `CLDUploaderWidgetDelegate`.
- returns: The new `CLDUploaderWidget` instance.
*/
public init(
cloudinary: CLDCloudinary,
configuration: CLDWidgetConfiguration?,
images: [UIImage]?,
videos: [AVPlayerItem]?,
delegate: CLDUploaderWidgetDelegate?
) {
self.cloudinaryObject = cloudinary
self.configuration = configuration
self.images = images != nil ? images! : [UIImage]()
self.videos = videos != nil ? videos! : [AVPlayerItem]()
self.delegate = delegate
self.widgetPresented = false
super.init()
}
/**
Sets the CLDCloudinary object to be used for uploading the selected assets
- parameter cloudinary: The cloudinary object.
- returns: The same instance of CLDUploaderWidget.
*/
@objc(setCloudinaryFromCloudinary:)
@discardableResult
public func setCloudinary(_ cloudinary: CLDCloudinary) -> Self {
guard widgetPresented == false else {
print("cloudinary can not be set while widget is presented")
return self
}
self.cloudinaryObject = cloudinary
return self
}
/**
Holds the configuration parameters to be used by the `CLDUpladerWidget` instance.
- parameter configuration: The configuration object.
- returns: The same instance of CLDUploaderWidget.
*/
@objc(setConfigurationFromConfiguration:)
@discardableResult
public func setConfiguration(_ configuration: CLDWidgetConfiguration) -> Self {
guard widgetPresented == false else {
print("configuration can not be set while widget is presented")
return self
}
self.configuration = configuration
return self
}
/**
Sets images to be presented, edited and uploaded.
- parameter images: The images array object.
- returns: The same instance of CLDUploaderWidget.
*/
@objc(setImagesFromImages:)
@discardableResult
public func setImages(_ images: [UIImage]) -> Self {
guard widgetPresented == false else {
print("images can not be set while widget is presented")
return self
}
self.images = images
return self
}
/**
Sets videos to be presented and uploaded.
- parameter videos: The videos array object.
- returns: The same instance of CLDUploaderWidget.
*/
@objc(setVideosFromVideoItems:)
@discardableResult
public func setVideos(_ videoItems: [AVPlayerItem]) -> Self {
guard widgetPresented == false else {
print("videos can not be set while widget is presented")
return self
}
self.videos = videoItems
return self
}
/**
Sets videos to be presented and uploaded.
- parameter videos: The videos array object.
- returns: The same instance of CLDUploaderWidget.
*/
@objc(setVideosFromVideoUrls:)
@discardableResult
public func setVideos(_ videoUrls: [URL]) -> Self {
guard widgetPresented == false else {
print("videos can not be set while widget is presented")
return self
}
let videoItems = videoUrls.compactMap {
AVPlayerItem(url: $0)
}
self.videos = videoItems
return self
}
/**
Sets a delegate object conforming to `CLDUploaderWidgetDelegate` protocol to recieve information via delegate methods.
- parameter delegate: The delegate object.
- returns: The same instance of CLDUploaderWidget.
*/
@objc(setDelegateFromDelegate:)
@discardableResult
public func setDelegate(_ delegate: CLDUploaderWidgetDelegate) -> Self {
self.delegate = delegate
return self
}
/**
Modally presenting the widget or an `UIImagePickerController` from the given view controller.
- parameter viewController: The presenting `UIViewController` object.
*/
public func presentWidget(from viewController: UIViewController) {
let viewControllerToPresent: UIViewController
if selectAssetFromLibrary {
imagePicker = createImagePicker()
viewControllerToPresent = imagePicker
}
else {
widgetViewController = createWidgetViewController()
viewControllerToPresent = widgetViewController
}
if #available(iOS 9.0, *) {
viewControllerToPresent.loadViewIfNeeded()
}
else {
_ = viewController.view
}
viewControllerToPresent.modalPresentationStyle = .fullScreen
if #available(iOS 13.0, *) {
viewControllerToPresent.isModalInPresentation = true
}
rootViewController = viewController
rootViewController.present(viewControllerToPresent, animated: true, completion: nil)
}
/**
Dismisses the widget.
*/
public func dismissWidget() {
if selectAssetFromLibrary {
imagePicker.dismiss(animated: true) {
self.delegate?.uploadWidgetDidDismiss()
}
}
else {
widgetViewController.dismiss(animated: true) {
self.delegate?.uploadWidgetDidDismiss()
}
}
}
// MARK - private methods
private func createWidgetViewController() -> CLDWidgetViewController {
let imageContainers = images.compactMap {
CLDWidgetAssetContainer(originalImage: $0, editedImage: $0)
}
let videoContainers = videos.compactMap {
CLDWidgetAssetContainer(videoItem: $0)
}
let assetContainers = videoContainers + imageContainers
return CLDWidgetViewController(assets: assetContainers, configuration: configuration, delegate: self)
}
private func createImagePicker() -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = self
picker.sourceType = .photoLibrary
picker.mediaTypes = [kUTTypeImage as String, kUTTypeMovie as String]
return picker
}
private func upload(assets: [CLDWidgetAssetContainer]) -> [CLDUploadRequest] {
let images = assets.filter {
$0.assetType == .image
}
let videos = assets.filter {
$0.assetType == .video
}
let imagesUploadRequests = upload(images: images)
let videosUploadRequests = upload(videos: videos)
return videosUploadRequests + imagesUploadRequests
}
private func upload(images: [CLDWidgetAssetContainer]) -> [CLDUploadRequest] {
guard images.count > 0 else { return [] }
let datas = images.compactMap {
$0.editedImage?.pngData()
}
let uploader = cloudinaryObject.createUploader()
let requests = datas.compactMap { (data) -> CLDUploadRequest? in
// unsigned upload must have a preset
if let configuration = configuration,
configuration.uploadType.signed == false,
let preset = configuration.uploadType.preset {
return uploader.upload(data: data, uploadPreset: preset).response {
(result, error) in
print("image unsigned result \(String(describing: result))")
print("image unsigned error \(String(describing: error))")
}
}
else {
// default upload type
return uploader.signedUpload(data: data).response { (result, error) in
print("image signed result \(String(describing: result))")
print("image signed error \(String(describing: error))")
}
}
}
return requests
}
private func upload(videos: [CLDWidgetAssetContainer]) -> [CLDUploadRequest] {
guard videos.count > 0 else { return [] }
let videosUrls = videos.compactMap {
($0.originalVideo?.asset as? AVURLAsset)?.url
}
let params = CLDUploadRequestParams()
params.setResourceType(.video)
let uploader = cloudinaryObject.createUploader()
let requests = videosUrls.compactMap { (url) -> CLDUploadRequest? in
// unsigned upload must have a preset
if let configuration = configuration,
configuration.uploadType.signed == false,
let preset = configuration.uploadType.preset {
return uploader.upload(url: url, uploadPreset: preset, params: params).response {
(result, error) in
print("video unsigned result \(String(describing: result))")
print("video unsigned error \(String(describing: error))")
}
}
else {
// default upload type
return uploader.signedUpload(url: url, params: params).response { (result, error) in
print("video signed result \(String(describing: result))")
print("video signed error \(String(describing: error))")
}
}
}
return requests
}
}
// MARK: - UIImagePickerControllerDelegate
extension CLDUploaderWidget: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let image = info[.originalImage] as? UIImage {
images = [image]
}
else if let videoUrl = info[.mediaURL] as? URL {
let playerItem = AVPlayerItem(url: videoUrl)
videos = [playerItem]
}
else {
dismissWidget()
return
}
widgetViewController = createWidgetViewController()
picker.pushViewController(widgetViewController, animated: true)
}
public func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
delegate?.widgetDidCancel(self)
dismissWidget()
}
}
// MARK: - CLDWidgetViewControllerDelegate
extension CLDUploaderWidget: CLDWidgetViewControllerDelegate {
func widgetViewController(_ controller: CLDWidgetViewController, didFinishEditing editedAssets: [CLDWidgetAssetContainer]) {
let uploadRequests = upload(assets: editedAssets)
delegate?.uploadWidget(self, willCall: uploadRequests)
dismissWidget()
}
func widgetViewControllerDidCancel(_ controller: CLDWidgetViewController) {
delegate?.widgetDidCancel(self)
dismissWidget()
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/UploadWidget/CropView/CLDCropOverlayView.swift
================================================
//
// CLDCropOverlayView.swift
// ProjectCLDWidgetEditViewController
//
// Created by Arkadi Yoskovitz on 8/17/20.
// Copyright © 2020 Gini-Apps LTD. All rights reserved.
//
import UIKit
private let kCLDCropOverLayerPlaceholder = CGFloat(00.0);
private let kCLDCropOverLayerCornerWidth = CGFloat(20.0);
@objc
@objcMembers
public class CLDCropOverlayView : UIView
{
// MARK: -
public override var frame: CGRect { didSet { layoutLinesIfNeeded() } }
public var gridColor : UIColor { didSet { layoutLinesIfNeeded() } }
public var knobColor : UIColor { didSet { layoutLinesIfNeeded() } }
// MARK: - Public Properties
/**
Hides the interior grid lines, sans animation.
*/
public var isGridHidden : Bool {
set { setGridlines(hidden: newValue, animted: false) }
get { areGridLinesHidden }
}
/**
Add/Remove the interior horizontal grid lines.
*/
public var shouldDisplayHorizontalGridLines : Bool {
set { setDisplayHorizontalGridLines(newValue) }
get { displayGridLinesHorizontal }
}
/**
Add/Remove the interior vertical grid lines.
*/
public var shouldDisplayVerticalGridLines : Bool {
set { setDisplayVerticalGridLines(newValue) }
get { displayGridLinesVertical }
}
// MARK: - Private Properties
private var areGridLinesHidden : Bool
private var displayGridLinesHorizontal: Bool
private var displayGridLinesVertical : Bool
private var gridLinesHorizontal : [UIView]
private var gridLinesVertical : [UIView]
private var outerLineViews : [UIView] // top, right, bottom, left
private var topLeftLineViews : [UIView] // vertical, horizontal
private var bottomLeftLineViews : [UIView]
private var bottomRightLineViews : [UIView]
private var topRightLineViews : [UIView]
// MARK: - init
public override init(frame: CGRect) {
self.areGridLinesHidden = false
self.displayGridLinesVertical = true
self.displayGridLinesHorizontal = true
self.gridColor = UIColor.white
self.knobColor = UIColor.white
self.gridLinesHorizontal = [UIView]()
self.gridLinesVertical = [UIView]()
self.outerLineViews = [UIView]() //top, right, bottom, left
self.topLeftLineViews = [UIView]()
self.topRightLineViews = [UIView]()
self.bottomLeftLineViews = [UIView]()
self.bottomRightLineViews = [UIView]()
super.init(frame: frame)
self.clipsToBounds = false
self.outerLineViews.append(contentsOf: [
createLineView(background: gridColor),
createLineView(background: gridColor),
createLineView(background: gridColor),
createLineView(background: gridColor)
])
self.topLeftLineViews.append(contentsOf: [
createLineView(background: gridColor),
createLineView(background: gridColor)
])
self.topRightLineViews.append(contentsOf: [
createLineView(background: gridColor),
createLineView(background: gridColor)
])
self.bottomLeftLineViews.append(contentsOf: [
createLineView(background: gridColor),
createLineView(background: gridColor)
])
self.bottomRightLineViews.append(contentsOf: [
createLineView(background: gridColor),
createLineView(background: gridColor)
])
self.setDisplayHorizontalGridLines(true)
self.setDisplayVerticalGridLines(true)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: -
public override func didMoveToSuperview() {
super.didMoveToSuperview()
layoutLinesIfNeeded()
}
// MARK: -
/**
Shows and hides the interior grid lines with an optional crossfade animation.
*/
public func setGridlines(hidden: Bool, animted: Bool) {
areGridLinesHidden = hidden;
let block : () -> Void = {
self.gridLinesHorizontal.forEach { $0.alpha = hidden ? 0.0 : 1.0 }
self.gridLinesVertical.forEach { $0.alpha = hidden ? 0.0 : 1.0 }
}
switch animted {
case true : UIView.animate(withDuration: hidden ? 0.35 : 0.2) { block() }
case false: block()
}
}
// MARK: - Private setter actions
private func setDisplayHorizontalGridLines(_ display: Bool) {
displayGridLinesHorizontal = display
gridLinesHorizontal.forEach { $0.removeFromSuperview() }
gridLinesHorizontal.removeAll()
if displayGridLinesHorizontal {
gridLinesHorizontal.append(contentsOf: [
createLineView(background: gridColor),
createLineView(background: gridColor)
])
}
setNeedsDisplay()
}
private func setDisplayVerticalGridLines(_ display: Bool) {
displayGridLinesVertical = display
gridLinesVertical.forEach { $0.removeFromSuperview() }
gridLinesVertical.removeAll()
if displayGridLinesVertical {
gridLinesVertical.append(contentsOf: [
createLineView(background: gridColor),
createLineView(background: gridColor)
])
}
setNeedsDisplay()
}
// MARK: - Private methods
private func layoutLinesIfNeeded() {
guard !outerLineViews.isEmpty else { return }
applyLayoutLines()
}
private func applyLayoutLines() {
let boundsSize = bounds.size
// Border lines
outerLineViews.enumerated().forEach { (item) in
let aRect : CGRect
switch item.offset {
case 0: aRect = CGRect(x: 0.0 , y: -1.0, width: boundsSize.width + 2.0, height: 1.0) // Top
case 1: aRect = CGRect(x: boundsSize.width, y: 0.0, width: 1.0, height: boundsSize.height + 0.0) // Right
case 2: aRect = CGRect(x: -1.0 , y: boundsSize.height, width: boundsSize.width + 2.0, height: 1.0) // Bottom
case 3: aRect = CGRect(x: -1.0 , y: 0.0, width: 1.0, height: boundsSize.height + 1.0) // Left
default: aRect = CGRect.zero
}
item.element.frame = aRect
}
// Corner liness
[topLeftLineViews, topRightLineViews, bottomRightLineViews, bottomLeftLineViews].enumerated().forEach { item in
var VFrame = CGRect.zero
var HFrame = CGRect.zero
switch item.offset {
case 0: // Top left
VFrame = CGRect(x: kCLDCropOverLayerPlaceholder - 3.0,
y: kCLDCropOverLayerPlaceholder - 3.0,
width: 3.0, height: kCLDCropOverLayerCornerWidth + 3.0)
HFrame = CGRect(x: kCLDCropOverLayerPlaceholder + 0.0,
y: kCLDCropOverLayerPlaceholder - 3.0,
width: kCLDCropOverLayerCornerWidth + 0.0, height: 3.0)
break
case 1: // Top right
VFrame = CGRect(x: boundsSize.width + kCLDCropOverLayerPlaceholder,
y: kCLDCropOverLayerPlaceholder - 3.0,
width: 3.0, height: kCLDCropOverLayerCornerWidth + 3.0)
HFrame = CGRect(x: boundsSize.width - kCLDCropOverLayerCornerWidth,
y: kCLDCropOverLayerPlaceholder - 3.0,
width: kCLDCropOverLayerCornerWidth + 0.0, height: 3.0)
break
case 2: // Bottom right
VFrame = CGRect(x: boundsSize.width + kCLDCropOverLayerPlaceholder,
y: boundsSize.height - kCLDCropOverLayerCornerWidth,
width: 3.0, height: kCLDCropOverLayerCornerWidth + 3.0)
HFrame = CGRect(x: boundsSize.width - kCLDCropOverLayerCornerWidth,
y: boundsSize.height + kCLDCropOverLayerPlaceholder,
width: kCLDCropOverLayerCornerWidth + 0.0, height: 3.0)
break
case 3: // Bottom left
VFrame = CGRect(x: kCLDCropOverLayerPlaceholder - 3.0,
y: boundsSize.height - kCLDCropOverLayerCornerWidth,
width: 3.0, height: kCLDCropOverLayerCornerWidth)
HFrame = CGRect(x: kCLDCropOverLayerPlaceholder - 3.0,
y: boundsSize.height + kCLDCropOverLayerPlaceholder,
width: kCLDCropOverLayerCornerWidth + 3.0, height: 3.0)
break
default:
break
}
item.element[0].frame = VFrame
item.element[1].frame = HFrame
}
var padding : CGFloat
var linesCount : Int
let thickness = 1.0 / UIScreen.main.scale
// Grid lines - horizontal
linesCount = gridLinesHorizontal.count
padding = (bounds.height - (thickness * CGFloat(linesCount))) / (CGFloat(linesCount) + 1.0)
gridLinesHorizontal.enumerated().forEach { item in
var aRect = CGRect.zero
aRect.size.width = bounds.width
aRect.size.height = thickness
aRect.origin.y = (padding * CGFloat(item.offset + 1)) + (thickness * CGFloat(item.offset))
item.element.frame = aRect
}
// Grid lines - vertical
linesCount = gridLinesVertical.count
padding = (bounds.width - (thickness * CGFloat(linesCount))) / (CGFloat(linesCount) + 1.0)
gridLinesVertical.enumerated().forEach { item in
var aRect = CGRect.zero
aRect.size.width = thickness
aRect.size.height = bounds.height
aRect.origin.x = (padding * CGFloat(item.offset + 1)) + (thickness * CGFloat(item.offset))
item.element.frame = aRect
}
}
private func createLineView(background color: UIColor) -> UIView {
let newLine = UIView(frame: .zero)
newLine.backgroundColor = color
addSubview(newLine)
return newLine
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/UploadWidget/CropView/CLDCropScrollView.swift
================================================
//
// CLDCropScrollView.swift
// ProjectCLDWidgetEditViewController
//
// Created by Arkadi Yoskovitz on 8/17/20.
// Copyright © 2020 Gini-Apps LTD. All rights reserved.
//
import UIKit
public typealias CLDTouchHandler = () -> Void
/*
Subclassing UIScrollView was necessary in order to directly capture
touch events that weren't otherwise accessible via UIGestureRecognizer objects.
*/
@objc
@objcMembers
public class CLDCropScrollView : UIScrollView {
public var touchesBegan :CLDTouchHandler?
public var touchesCancelled:CLDTouchHandler?
public var touchesEnded :CLDTouchHandler?
override init(frame: CGRect) {
super.init(frame: frame)
configureInitialState()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configureInitialState()
}
private func configureInitialState() {
self.autoresizingMask = [.flexibleHeight , .flexibleWidth]
self.alwaysBounceHorizontal = true
self.alwaysBounceVertical = true
self.showsHorizontalScrollIndicator = false
self.showsVerticalScrollIndicator = false
}
public override func touchesBegan(_ touches: Set, with event: UIEvent?) {
if let handler = touchesBegan {
handler()
}
super.touchesBegan(touches, with: event)
}
public override func touchesEnded(_ touches: Set, with event: UIEvent?) {
if let handler = touchesEnded {
handler()
}
super.touchesEnded(touches, with: event)
}
public override func touchesCancelled(_ touches: Set, with event: UIEvent?) {
if let handler = touchesEnded {
handler()
}
super.touchesCancelled(touches, with: event)
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/UploadWidget/CropView/CLDCropScrollViewController.swift
================================================
//
// CLDCropViewScrollViewDelegate.swift
//
// Copyright (c) 2020 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import UIKit
class CLDCropScrollViewController: NSObject, UIScrollViewDelegate {
weak var cropView: CLDCropView!
init(cropView: CLDCropView) {
self.cropView = cropView
super.init()
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return cropView.backgroundContainerView
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
cropView.matchForegroundToBackground()
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
cropView.startEditing()
cropView.isResettable = true
}
func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) {
cropView.startEditing()
cropView.isResettable = true
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
cropView.startResetTimer()
cropView.checkForCanReset()
}
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
cropView.startResetTimer()
cropView.checkForCanReset()
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
if scrollView.isTracking {
cropView.cropBoxLastEditedZoomScale = scrollView.zoomScale
cropView.cropBoxLastEditedMinZoomScale = scrollView.minimumZoomScale
}
cropView.matchForegroundToBackground()
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if !decelerate {
cropView.startResetTimer()
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/UploadWidget/CropView/CLDCropView.swift
================================================
//
// CLDCropView.swift
//
// Copyright (c) 2020 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import UIKit
public extension CGFloat {
static var ulpOfOne: CGFloat { CGFloat(Float.ulpOfOne) }
}
/**
Preset values of the most common aspect ratios that can be used to quickly configure
the crop view controller.
*/
@objc
public enum CLDCropViewControllerAspectRatioPreset : Int {
case original
case square
case rect3x2
case rect5x3
case rect4x3
case rect5x4
case rect7x5
case rect16x9
case custom
}
/**
When the user taps down to resize the box, this state is used
to determine where they tapped and how to manipulate the box
*/
@objc
public enum CLDCropViewOverlayEdge : Int {
case none
case topLeft
case top
case topRight
case right
case bottomRight
case bottom
case bottomLeft
case left
}
// =====================================================================================================================
@objc
public protocol CLDCropViewDelegate : NSObjectProtocol {
@objc optional func cropView(_ cropView: CLDCropView, didChangeResettable state: Bool)
}
// =====================================================================================================================
@objc
@objcMembers
public class CLDCropView: UIView {
// MARK: - Constants
internal struct Constants {
internal static let padding : CGFloat = CGFloat(14.0) // kTOCropViewPadding
internal static let timerDuration : TimeInterval = TimeInterval(0.8) // kTOCropTimerDuration
internal static let minimumBoxSize : CGFloat = CGFloat(42.0) // kTOCropViewMinimumBoxSize
internal static let circularPathRadius : CGFloat = CGFloat(300.0) // kTOCropViewCircularPathRadius
internal static let maximumZoomScale : CGFloat = CGFloat(15.0) // kTOMaximumZoomScale
}
// MARK: - Types
internal class Calculator {
internal class CropBox {}
internal class ImageCrop {}
internal class AspectRatio {}
}
// MARK: - Properties
/**
A delegate object that receives notifications from the crop view
*/
public weak var delegate: CLDCropViewDelegate?
/**
If false, the user cannot resize the crop box frame using a pan gesture from a corner.
Default value is true.
*/
public var cropBoxResizeEnabled: Bool {
get { _cropBoxResizeEnabled }
set {
_cropBoxResizeEnabled = newValue
gridPanGestureRecognizer.isEnabled = newValue
}
}
/**
Inset the workable region of the crop view in case in order to make space for accessory views
*/
public var cropRegionInsets: UIEdgeInsets
/**
When performing manual content layout (such as during screen rotation), disable any internal layout
*/
public var internalLayoutDisabled: Bool
/**
A width x height ratio that the crop box will be rescaled to (eg 4:3 is {4.0f, 3.0f})
Setting it to CGSizeZero will reset the aspect ratio to the image's own ratio.
*/
public var aspectRatio: CGSize {
get { _aspectRatio }
set { setAspectRatio(newValue, animated: false) }
}
/**
When the cropping box is locked to its current aspect ratio (But can still be resized)
*/
public var aspectRatioLockEnabled: Bool
/**
If true, a custom aspect ratio is set, and the aspectRatioLockEnabled is set to YES,
the crop box will swap it's dimensions depending on portrait or landscape sized images.
This value also controls whether the dimensions can swap when the image is rotated.
Default is true.
*/
public var aspectRatioLockDimensionSwapEnabled: Bool
/**
When the user taps 'reset', whether the aspect ratio will also be reset as well
Default is true.
*/
public var resetAspectRatioEnabled: Bool
/**
The rotation angle of the crop view (Will always be negative as it rotates in a counter-clockwise direction)
*/
public var angle: Int {
get { _angle }
set {
// The initial layout would not have been performed yet.
// Save the value and it will be applied when it has
var newAngle = newValue
if (newValue % 90 != 0) { newAngle = 0 }
if !initialSetupPerformed {
restoreAngle = newAngle
return
}
// Negative values are allowed, so rotate clockwise or counter clockwise depending on direction
if (newAngle >= 0) {
while ( labs(angle) != labs(newAngle)) { rotateImageNinetyDegreesAnimated(false, clockwise: true ) }
} else {
while (-labs(angle) != -labs(newAngle)) { rotateImageNinetyDegreesAnimated(false, clockwise: false) }
}
}
}
/**
In relation to the coordinate space of the image, the frame that the crop view is focusing on
*/
public var imageCropFrame: CGRect {
get {
return Calculator.ImageCrop.computeFrame(given: imageSize, scrollView: scrollView, cropBox: cropBoxFrame)
}
set {
guard initialSetupPerformed else { restoreImageCropFrame = newValue; return }
let adjutsments = Calculator.ImageCrop.computeAdjutsments(for: self,
scrollView: scrollView,
content: contentBounds,
newValue: newValue)
cropBoxFrame = adjutsments.cropFrame
applyCropFrameAdjutsments(to: scrollView, originPoint: newValue.origin, minimumScale: adjutsments.minimumZoomScale, adjustedZoom: adjutsments.adjustedZoomScale)
}
}
/**
Paddings of the crop rectangle.
Default to 14.0
*/
public var cropViewPadding: CGFloat
/**
Delay before crop frame is adjusted according new crop area.
Default to 0.8
*/
public var cropAdjustingDelay: TimeInterval
/**
The minimum croping aspect ratio. If set, user is prevented from setting cropping
rectangle to lower aspect ratio than defined by the parameter.
*/
public var minimumAspectRatio: CGFloat
/**
The maximum scale that user can apply to image by pinching to zoom. Small values
are only recomended with aspectRatioLockEnabled set to true.
Default to 15.0
*/
public var maximumZoomScale: CGFloat
/**
Always show the cropping grid lines, even when the user isn't interacting.
This also disables the fading animation.
(Default is NO)
*/
public var alwaysShowCroppingGrid: Bool {
get { _alwaysShowCroppingGrid }
set {
if _alwaysShowCroppingGrid == newValue { return }
_alwaysShowCroppingGrid = newValue
gridOverlayView.setGridlines(hidden: !newValue, animted: true)
}
}
/**
Permanently hides the translucency effect covering the outside bounds of the
crop box.
Default is NO
*/
public var translucencyAlwaysHidden: Bool {
get { _translucencyAlwaysHidden }
set {
guard _translucencyAlwaysHidden != newValue else { return }
_translucencyAlwaysHidden = newValue
translucencyView.isHidden = newValue
}
}
/**
The image that the crop view is displaying. This cannot be changed once the crop view is instantiated.
*/
public internal(set) var image : UIImage
/* Views */
/**
The main image view, placed within the scroll view
*/
internal var backgroundImageView: UIImageView!
/**
A view which contains the background image view, to separate its transforms from the scroll view.
*/
internal var backgroundContainerView: UIView!
/**
A container view that clips the a copy of the image so it appears over the dimming view
*/
public private(set) var foregroundContainerView: UIView!
/**
A copy of the background image view, placed over the dimming views
*/
internal var foregroundImageView: UIImageView!
/**
The scroll view in charge of panning/zooming the image.
*/
internal private(set) var scrollView: CLDCropScrollView!
/**
The scroll view in charge of panning/zooming the image.
*/
private var scrollViewDelegate: CLDCropScrollViewController!
/**
A semi-transparent grey view, overlaid on top of the background image
*/
private var overlayView: UIView!
/**
A blur view that is made visible when the user isn't interacting with the crop view
*/
internal var translucencyView: UIVisualEffectView!
/**
The dark blur visual effect applied to the visual effect view.
*/
private var translucencyEffect: UIBlurEffect!
/**
A grid view overlaid on top of the foreground image view's container.
*/
public private(set) var gridOverlayView: CLDCropOverlayView!
/* Gesture Recognizers */
/**
The gesture recognizer in charge of controlling the resizing of the crop view
*/
private var gridPanGestureRecognizer: UIPanGestureRecognizer!
/* Crop box handling */
/**
No by default, when setting initialCroppedImageFrame this will be set to YES, and set back to NO after first application - so it's only done once
*/
private var applyInitialCroppedImageFrame: Bool
/**
The edge region that the user tapped on, to resize the cropping region
*/
internal var tappedEdge: CLDCropViewOverlayEdge
/**
When resizing, this is the original frame of the crop box.
*/
internal var cropOriginFrame : CGRect
/**
The initial touch point of the pan gesture recognizer
*/
internal var panOriginPoint : CGPoint
/**
The frame of the cropping box in the coordinate space of the crop view
*/
public internal(set) var cropBoxFrame: CGRect {
get { _cropBoxFrame }
set {
if _cropBoxFrame.equalTo(newValue) { return }
guard newValue.isValidBoxSize else { return }
// Clamp the cropping region to the inset boundaries of the screen
_cropBoxFrame = Calculator.CropBox.clampFrame(given: contentBounds, cropBox: newValue,
minimumBoxSize: Constants.minimumBoxSize)
applyDidSetCropBoxFrame(to: _cropBoxFrame)
}
}
/**
A manager for the crop view UI
*/
fileprivate var cropUIManager: CLDCropViewUIManager!
/**
The timer used to reset the view after the user stops interacting with it
*/
internal var resetTimer: Timer?
/**
Used to denote the active state of the user manipulating the content
default is NO. setting is not animated.
*/
private var isEditing: Bool {
get { _isEditing }
set {
setEditing(newValue, resetCropBox: false, animated: false)
}
}
internal func startEditing() {
cancelResetTimer()
setEditing(true, resetCropBox: false, animated: true)
}
internal func setEditing(_ editing: Bool, resetCropBox: Bool, animated: Bool) {
if _isEditing == editing { return }
_isEditing = editing
// Toggle the visiblity of the gridlines when not editing
var hidden = !editing
if (alwaysShowCroppingGrid) { hidden = false } // Override this if the user requires
gridOverlayView.setGridlines(hidden: hidden, animted: animated)
if resetCropBox {
moveCroppedContentToCenterAnimated(animated)
captureStateForImageRotation()
cropBoxLastEditedAngle = angle
}
switch animated {
case false:
toggleTranslucencyViewVisible(!editing)
case true:
let duration = editing ? 0.05 : 0.35
let delay = editing ? 0.00 : 0.35
UIView.animateKeyframes(withDuration: duration, delay: delay, options: [], animations: {
self.toggleTranslucencyViewVisible(!editing)
}, completion: nil)
}
}
/**
At times during animation, disable matching the forground image view to the background
*/
internal var disableForgroundMatching: Bool
/* Pre-screen-rotation state information */
private var rotationContentOffset: CGPoint
private var rotationContentSize : CGSize
private var rotationBoundFrame : CGRect
/* View State information */
/**
Give the current screen real-estate, the frame that the scroll view is allowed to use
*/
internal var contentBounds: CGRect {
return Calculator.bounds(for: self.bounds, padding: cropViewPadding, cropRegion: cropRegionInsets)
}
/**
Given the current rotation of the image, the size of the image
*/
internal var imageSize: CGSize {
if (angle == -90 || angle == -270 || angle == 90 || angle == 270 )
{
return CGSize(width: image.size.height, height: image.size.width )
}
else
{
return CGSize(width: image.size.width , height: image.size.height)
}
}
/**
True if an aspect ratio was explicitly applied to this crop view
*/
internal var hasAspectRatio: Bool {
return aspectRatio.width > CGFloat.ulpOfOne && aspectRatio.height > CGFloat.ulpOfOne
}
/* 90-degree rotation state data */
/**
When performing 90-degree rotations, remember what our last manual size was to use that as a base
*/
internal var cropBoxLastEditedSize: CGSize
/**
Remember which angle we were at when we saved the editing size
*/
internal var cropBoxLastEditedAngle: Int
/**
Remember the zoom size when we last edited
*/
internal var cropBoxLastEditedZoomScale: CGFloat
/**
Remember the minimum size when we last edited.
*/
internal var cropBoxLastEditedMinZoomScale: CGFloat
/**
Disallow any input while the rotation animation is playing
*/
internal var rotateAnimationInProgress: Bool
/* Reset state data */
/**
Save the original crop box size so we can tell when the content has been edited
*/
private var originalCropBoxSize: CGSize
/**
Save the original content offset so we can tell if it's been scrolled.
*/
private var originalContentOffset: CGPoint
/**
Whether the user has manipulated the crop view to the point where it can be reset
*/
public internal(set) var isResettable: Bool {
get{ _isResettableFlag }
set{
guard newValue != _isResettableFlag else { return }
_isResettableFlag = newValue
delegate?.cropView?(self, didChangeResettable: _isResettableFlag)
}
}
/**
In iOS 9, a new dynamic blur effect became available.
*/
public internal(set) var dynamicBlurEffect: Bool
// MARK: - ======================================================================================
/**
If restoring to a previous crop setting, these properties hang onto the
values until the view is configured for the first time.
*/
private var restoreAngle : Int
private var restoreImageCropFrame: CGRect
/**
Set to YES once `performInitialLayout` is called.
This lets pending properties get queued until the view has been properly set up in its parent.
*/
private var initialSetupPerformed: Bool
// MARK: - Private properties ======================================================================================
private var _cropBoxFrame : CGRect
private var _cropBoxResizeEnabled : Bool
private var _simpleRenderMode : Bool
private var _imageCropFrame : CGRect
private var _isEditing : Bool
private var _isResettableFlag : Bool
private var _aspectRatio : CGSize
private var _croppingViewsHidden : Bool
private var _gridOverlayHidden : Bool
private var _alwaysShowCroppingGrid : Bool
private var _translucencyAlwaysHidden: Bool
var _angle : Int
// MARK: - Initialized ======================================================================================
/**
Create a new instance of the crop view with the specified image and cropping
*/
public init(image: UIImage) {
self.image = image
self.aspectRatioLockDimensionSwapEnabled = true
self.resetAspectRatioEnabled = true
self.cropViewPadding = Constants.padding
self.cropAdjustingDelay = Constants.timerDuration
self.cropBoxLastEditedSize = CGSize.zero
self.cropBoxLastEditedAngle = 0
self.cropBoxLastEditedZoomScale = 0.0
self.cropBoxLastEditedMinZoomScale = 0.0
self.rotateAnimationInProgress = false
self.cropRegionInsets = .zero
self.minimumAspectRatio = CGFloat(0.0)
self.internalLayoutDisabled = false
self.aspectRatioLockEnabled = false
self.tappedEdge = .none
self.cropOriginFrame = CGRect.zero
self.panOriginPoint = CGPoint.zero
self._angle = 0
self._isEditing = false
self._cropBoxFrame = .zero
self._aspectRatio = .zero
self._imageCropFrame = .zero
self._isResettableFlag = false
self._simpleRenderMode = false
self._cropBoxResizeEnabled = false
self._alwaysShowCroppingGrid = false
self._translucencyAlwaysHidden = false
self._gridOverlayHidden = false
self._croppingViewsHidden = false
self.disableForgroundMatching = false
self.rotationContentOffset = .zero
self.rotationContentSize = .zero
self.rotationBoundFrame = .zero
self.originalCropBoxSize = .zero
self.originalContentOffset = .zero
self.initialSetupPerformed = false
self.maximumZoomScale = Constants.maximumZoomScale
self.applyInitialCroppedImageFrame = false
self.dynamicBlurEffect = false
self.restoreAngle = 0
self.restoreImageCropFrame = .zero
super.init(frame: .zero)
// UI manager
self.cropUIManager = CLDCropViewUIManager(cropView: self)
// The pan controller to recognize gestures meant to resize the grid view
self.gridPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(gridPanGestureRecognized(_:)))
self.cropBoxResizeEnabled = true
self.alwaysShowCroppingGrid = false
self.translucencyAlwaysHidden = false
// View properties
self.autoresizingMask = [.flexibleHeight , .flexibleWidth]
self.backgroundColor = UIColor(white: 0.12, alpha: 1.0)
self.cropBoxFrame = CGRect.zero
self._isEditing = false
self._cropBoxResizeEnabled = true
self._aspectRatio = CGSize.zero
self.resetAspectRatioEnabled = true
self.cropAdjustingDelay = Constants.timerDuration
self.cropViewPadding = Constants.padding
/* Dynamic animation blurring is only possible on iOS 9, however since the API was available on iOS 8,
we'll need to manually check the system version to ensure that it's available. */
self.dynamicBlurEffect = UIDevice.current.systemVersion.compare("9.0", options: .numeric) != .orderedAscending
// Scroll View
self.scrollViewDelegate = CLDCropScrollViewController(cropView: self)
self.scrollView = CLDCropScrollView(frame: bounds)
self.scrollView.delegate = self.scrollViewDelegate
self.addSubview(self.scrollView)
// Disable smart inset behavior in iOS 11
if #available(iOS 11.0, *) {
self.scrollView.contentInsetAdjustmentBehavior = .never
}
self.scrollView.touchesBegan = { [weak self] in self?.startEditing() }
self.scrollView.touchesEnded = { [weak self] in self?.startResetTimer() }
// Background Image View
self.backgroundImageView = UIImageView(image: self.image)
self.backgroundImageView.layer.minificationFilter = CALayerContentsFilter.trilinear
// Background container view
self.backgroundContainerView = UIView(frame: self.backgroundImageView.frame)
self.backgroundContainerView.addSubview(self.backgroundImageView)
self.scrollView.addSubview(self.backgroundContainerView)
// Grey transparent overlay view
self.overlayView = UIView(frame: bounds)
self.overlayView.autoresizingMask = [.flexibleHeight , .flexibleWidth]
self.overlayView.backgroundColor = self.backgroundColor?.withAlphaComponent(0.35)
self.overlayView.isHidden = false
self.overlayView.isUserInteractionEnabled = false
self.addSubview(self.overlayView)
// Translucency View
self.translucencyEffect = UIBlurEffect(style: UIBlurEffect.Style.dark)
self.translucencyView = UIVisualEffectView(effect: self.translucencyEffect)
self.translucencyView.frame = self.bounds
self.translucencyView.isHidden = self.translucencyAlwaysHidden
self.translucencyView.isUserInteractionEnabled = false
self.translucencyView.autoresizingMask = [.flexibleHeight , .flexibleWidth]
self.addSubview(self.translucencyView)
// The forground container that holds the foreground image view
self.foregroundContainerView = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
self.foregroundContainerView.clipsToBounds = true
self.foregroundContainerView.isUserInteractionEnabled = false
self.addSubview(self.foregroundContainerView)
self.foregroundImageView = UIImageView(image: self.image)
self.foregroundImageView.layer.minificationFilter = CALayerContentsFilter.trilinear
self.foregroundContainerView.addSubview(self.foregroundImageView)
// Disable colour inversion for the image views
if #available(iOS 11.0, *) {
self.foregroundImageView.accessibilityIgnoresInvertColors = true
self.backgroundImageView.accessibilityIgnoresInvertColors = true
}
// The white grid overlay view
self.gridOverlayView = CLDCropOverlayView(frame: self.foregroundContainerView.frame)
self.gridOverlayView.isUserInteractionEnabled = false
self.gridOverlayView.isGridHidden = true
self.addSubview(self.gridOverlayView)
self.gridPanGestureRecognizer.delegate = self
self.scrollView.panGestureRecognizer.require(toFail: self.gridPanGestureRecognizer)
self.addGestureRecognizer(self.gridPanGestureRecognizer)
}
public required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - View Layout
/**
Performs the initial set up, including laying out the image and applying any restore properties.
This should be called once the crop view has been added to a parent that is in its final layout frame.
*/
public func performInitialSetup() {
// Calling this more than once is potentially destructive
if initialSetupPerformed {
return
}
// Disable from calling again
initialSetupPerformed = true
// Perform the initial layout of the image
layoutInitialImage()
// -- State Restoration --
// If the angle value was previously set before this point, apply it now
if (restoreAngle != 0) {
angle = restoreAngle
restoreAngle = 0
cropBoxLastEditedAngle = angle
}
// If an image crop frame was also specified before creation, apply it now
if !restoreImageCropFrame.isEmpty {
imageCropFrame = restoreImageCropFrame
restoreImageCropFrame = .zero
}
// Save the current layout state for later
captureStateForImageRotation()
// Check if we performed any resetabble modifications
checkForCanReset()
}
private func layoutInitialImage() {
let scaledImageSize = cropUIManager.manualLayout_initialImageAndGetScaledImageSize()
// Save the current state for use with 90-degree rotations
cropBoxLastEditedAngle = 0
captureStateForImageRotation()
//save the size for checking if we're in a resettable state
originalCropBoxSize = resetAspectRatioEnabled ? scaledImageSize : cropBoxFrame.size
originalContentOffset = scrollView.contentOffset
checkForCanReset()
matchForegroundToBackground()
}
internal func matchForegroundToBackground() {
if disableForgroundMatching { return }
// We can't simply match the frames since if the images are rotated, the frame property becomes unusable
if let view = backgroundContainerView.superview {
foregroundImageView.frame = view.convert(backgroundContainerView.frame, to: foregroundContainerView)
}
}
private func updateCropBoxFrame(withGesture point: CGPoint) {
cropBoxFrame = Calculator.CropBox.calculateFrame(given: point, in: self)
checkForCanReset()
}
private func toggleTranslucencyViewVisible(_ visible: Bool) {
if dynamicBlurEffect == false {
translucencyView.alpha = visible ? 1.0 : 0.0
} else {
translucencyView.effect = visible ? translucencyEffect : nil
}
}
// MARK: - Gesture Recognizer - ====================================================================================
@objc private func gridPanGestureRecognized(_ recognizer: UIPanGestureRecognizer) {
let point = recognizer.location(in: self)
switch recognizer.state {
case .began:
startEditing()
panOriginPoint = point
cropOriginFrame = cropBoxFrame
tappedEdge = Calculator.overlayEdge(for: panOriginPoint, cropBoxFrame: cropBoxFrame)
break
case .ended:
startResetTimer()
break
default:
break
}
updateCropBoxFrame(withGesture: point)
}
// MARK: - Timer ====================================================================================
internal func startResetTimer() {
if let _ = resetTimer {} else {
resetTimer = Timer.scheduledTimer(timeInterval: cropAdjustingDelay, target: self, selector: #selector(timerTriggered), userInfo: nil, repeats: false)
}
}
@objc private func timerTriggered() {
setEditing(false, resetCropBox: true, animated: true)
resetTimer?.invalidate()
resetTimer = nil
}
internal func cancelResetTimer() {
resetTimer?.invalidate()
resetTimer = nil
}
// MARK: - =========================================================================================================
/**
Changes the aspect ratio of the crop box to match the one specified
- parameter aspectRatio The aspect ratio (For example 16:9 is 16.0f/9.0f). 'CGSizeZero' will reset it to the image's own ratio
- parameter animated Whether the locking effect is animated
*/
public func setAspectRatio(_ newValue: CGSize, animated: Bool) {
_aspectRatio = newValue
let adjustments = Calculator.AspectRatio.postChangeAdjustments(given: newValue, animated: animated, in: self)
cropBoxLastEditedSize = adjustments.cropBoxFrame.size
cropBoxLastEditedAngle = angle
let translateBlock : () -> Void = {
self.scrollView.contentOffset = adjustments.scrollOffset
self.cropBoxFrame = adjustments.cropBoxFrame
if (adjustments.ShouldZoomOut) {
self.scrollView.zoomScale = self.scrollView.minimumZoomScale
}
self.moveCroppedContentToCenterAnimated(false)
self.checkForCanReset()
}
switch animated {
case false:
translateBlock()
case true :
UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 1.0,
initialSpringVelocity: 0.7, options: [.beginFromCurrentState],
animations: translateBlock, completion: nil)
}
}
public func lockAspectRatio(to aspectRatio: CGSize) {
if aspectRatioLockEnabled {
_aspectRatio = aspectRatio
}
}
/**
Rotates the entire canvas to a 90-degree angle. The default rotation is counterclockwise.
- parameter animated Whether the transition is animated
*/
public func rotateImageNinetyDegreesAnimated(_ animated: Bool) {
rotateImageNinetyDegreesAnimated(animated, clockwise: false)
}
/**
Rotates the entire canvas to a 90-degree angle
- parameter animated Whether the transition is animated
- parameter clockwise Whether the rotation is clockwise. Passing 'NO' means counterclockwise
*/
public func rotateImageNinetyDegreesAnimated(_ animated: Bool, clockwise: Bool) {
cropUIManager.manualLayout_rotateImageNinetyDegreesAnimated(animated, clockwise: clockwise)
}
/**
When triggered, the crop view will perform a relayout to ensure the crop box
fills the entire crop view region
*/
public func moveCroppedContentToCenterAnimated(_ animated: Bool) {
cropUIManager.manualLayout_moveCroppedContentToCenterAnimated(animated)
}
// MARK: -- Editing Mode
internal func captureStateForImageRotation() {
cropBoxLastEditedSize = cropBoxFrame.size
cropBoxLastEditedZoomScale = scrollView.zoomScale
cropBoxLastEditedMinZoomScale = scrollView.minimumZoomScale
}
// MARK: -- Resettable State
internal func checkForCanReset() {
var resettable = false
if angle != 0 { // Image has been rotated
resettable = true
}
else if scrollView.zoomScale > scrollView.minimumZoomScale + CGFloat.ulpOfOne { // Image has been zoomed in
resettable = true
}
else if
Int(floor(cropBoxFrame.width )) != Int(floor(originalCropBoxSize.width )) ||
Int(floor(cropBoxFrame.height)) != Int(floor(originalCropBoxSize.height)) // Crop box has been changed
{
resettable = true
}
else if
Int(floor(scrollView.contentOffset.x)) != Int(floor(originalContentOffset.x)) ||
Int(floor(scrollView.contentOffset.y)) != Int(floor(originalContentOffset.y))
{
resettable = true
}
isResettable = resettable
}
// MARK: -- fileprivate methods
fileprivate func applyCropFrameAdjutsments(to scrollView: UIScrollView, originPoint: CGPoint, minimumScale: CGFloat, adjustedZoom scale: CGFloat) {
let scaledOffset = CGPoint(x: originPoint.x * minimumScale,
y: originPoint.y * minimumScale)
// Zoom into the scroll view to the appropriate size
scrollView.zoomScale = scrollView.minimumZoomScale * scale
scrollView.contentOffset = CGPoint(x: (scaledOffset.x * scale) - scrollView.contentInset.left,
y: (scaledOffset.y * scale) - scrollView.contentInset.top)
}
fileprivate func applyDidSetCropBoxFrame(to rectangle: CGRect) {
// Set the clipping view to match the new rect
foregroundContainerView.frame = rectangle
// Set the new overlay view to match the same region
gridOverlayView.frame = rectangle
Calculator.CropBox.updateScrollView(scrollView, given: backgroundContainerView.bounds.size,
boundingRect: self.bounds, newCropBox: rectangle)
// Re-align the background content to match
matchForegroundToBackground()
}
}
// MARK: - UIGestureRecognizerDelegate
extension CLDCropView : UIGestureRecognizerDelegate {
public override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer != gridPanGestureRecognizer {
return true
}
let tapPoint = gestureRecognizer.location(in: self)
let rectangle = gridOverlayView.frame
let innerRectangle = rectangle.insetBy(dx: 22.0, dy: 22.0)
let outerRectangle = rectangle.insetBy(dx: -22.0, dy: -22.0)
if innerRectangle.contains(tapPoint) || !outerRectangle.contains(tapPoint) {
return false
}
return true
}
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if gridPanGestureRecognizer.state == .changed {
return false
}
return true
}
}
// MARK: - General extensions
fileprivate extension CGRect {
var isValidBoxSize : Bool {
// Upon init, sometimes the box size is still 0 (or NaN), which can result in CALayer issues
let threashold = CGFloat.ulpOfOne
if (width < threashold || height < threashold) { return false }
if (width.isNaN || height.isNaN ) { return false }
return true
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/UploadWidget/CropView/CLDCropViewCalculator.swift
================================================
//
// CLDCropViewCalculator.swift
//
// Copyright (c) 2020 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import UIKit
import Foundation
import CoreGraphics
fileprivate extension CGSize {
var floored : CGSize {
return CGSize(width : floor(width ),
height: floor(height))
}
}
internal extension CLDCropView.Calculator {
static func bounds(for viewBounds: CGRect, padding: CGFloat, cropRegion insets: UIEdgeInsets) -> CGRect {
var rectangle = CGRect.zero
rectangle.origin.x = padding + insets.left
rectangle.origin.y = padding + insets.top
rectangle.size.width = viewBounds.width - ((padding * 2) + insets.left + insets.right)
rectangle.size.height = viewBounds.height - ((padding * 2) + insets.top + insets.bottom)
return rectangle
}
static func overlayEdge(for point: CGPoint, cropBoxFrame: CGRect) -> CLDCropViewOverlayEdge {
let threashold = CGFloat(64)
let halfThreashold = threashold / 2.0
var rectangle = cropBoxFrame
// Account for padding around the box
rectangle = rectangle.insetBy(dx: -halfThreashold, dy: -halfThreashold)
// Make sure the corners take priority
let rectangleTopLeft = CGRect(origin: rectangle.origin, size: CGSize(width: threashold, height: threashold))
if rectangleTopLeft.contains(point) {
return CLDCropViewOverlayEdge.topLeft
}
var rectangleTopRight = rectangleTopLeft
rectangleTopRight.origin.x = rectangle.maxX - threashold
if rectangleTopRight.contains(point) {
return CLDCropViewOverlayEdge.topRight
}
var rectangleBottomLeft = rectangleTopLeft
rectangleBottomLeft.origin.y = rectangle.maxY - threashold
if rectangleBottomLeft.contains(point) {
return CLDCropViewOverlayEdge.bottomLeft
}
var rectangleBottomRight = rectangleTopRight
rectangleBottomRight.origin.y = rectangleBottomLeft.origin.y
if rectangleBottomRight.contains(point) {
return CLDCropViewOverlayEdge.bottomRight
}
// Check for edges
let topRect = CGRect(origin: rectangle.origin, size: CGSize(width: rectangle.width, height: threashold))
if topRect.contains(point) {
return CLDCropViewOverlayEdge.top
}
var bottomRect = topRect
bottomRect.origin.y = rectangle.maxY - threashold
if bottomRect.contains(point) {
return CLDCropViewOverlayEdge.bottom
}
let leftRect = CGRect(origin: rectangle.origin, size: CGSize(width: threashold, height: rectangle.height))
if leftRect.contains(point) {
return CLDCropViewOverlayEdge.left
}
var rightRect = leftRect
rightRect.origin.x = rectangle.maxX - threashold
if rightRect.contains(point) {
return CLDCropViewOverlayEdge.right
}
return CLDCropViewOverlayEdge.none
}
}
internal extension CLDCropView.Calculator.ImageCrop {
/**
In relation to the coordinate space of the image, the frame that the crop view is focusing on
*/
static func computeFrame(given imageSize: CGSize, scrollView: UIScrollView, cropBox: CGRect) -> CGRect {
let targetSize = imageSize
let contentSize = scrollView.contentSize
let cropRectangle = cropBox
let offset = scrollView.contentOffset
let insets = scrollView.contentInset
let scale = min(targetSize.width / contentSize.width, targetSize.height / contentSize.height)
var rectangle = CGRect.zero
// Calculate the normalized origin
rectangle.origin.x = floor( (floor(offset.x) + insets.left) * (targetSize.width / contentSize.width ) )
rectangle.origin.x = max(0, rectangle.origin.x)
rectangle.origin.y = floor( (floor(offset.y) + insets.top ) * (targetSize.height / contentSize.height) )
rectangle.origin.y = max(0, rectangle.origin.y)
// Calculate the normalized width
rectangle.size.width = ceil(cropRectangle.width * scale)
rectangle.size.width = min(targetSize.width, rectangle.width)
// Calculate normalized height
if floor(cropRectangle.width) == floor(cropRectangle.height) {
rectangle.size.height = rectangle.size.width
} else {
rectangle.size.height = ceil(cropRectangle.height * scale)
rectangle.size.height = min(targetSize.height, rectangle.size.height)
}
rectangle.size.height = min(targetSize.height, rectangle.size.height)
return rectangle
}
static func computeAdjutsments(for cropView: CLDCropView, scrollView: UIScrollView, content contentBounds: CGRect, newValue: CGRect) -> (cropFrame:CGRect, minimumZoomScale: CGFloat, adjustedZoomScale: CGFloat) {
// Convert the image crop frame's size from image space to the screen space
let minimumScale = scrollView.minimumZoomScale
let scaledCropSize = CGSize(width : newValue.width * minimumScale,
height: newValue.height * minimumScale)
// Work out the scale necessary to upscale the crop size to fit the content bounds of the crop bound
let boundsRectangle = contentBounds
let scale = min(boundsRectangle.width / scaledCropSize.width,
boundsRectangle.height / scaledCropSize.height)
// Work out the size and offset of the upscaled crop box
var frameRectangle = CGRect.zero
frameRectangle.size = CGSize(width : scaledCropSize.width * scale,
height: scaledCropSize.height * scale)
//set the crop box
var cropBoxRectangle = CGRect.zero
cropBoxRectangle.size = frameRectangle.size
cropBoxRectangle.origin.x = boundsRectangle.midX - (frameRectangle.width * 0.5)
cropBoxRectangle.origin.y = boundsRectangle.midY - (frameRectangle.height * 0.5)
return (cropFrame: cropBoxRectangle, minimumZoomScale: minimumScale, adjustedZoomScale: scale)
}
}
internal extension CLDCropView.Calculator.CropBox {
/**
Clamp the cropping region to the inset boundaries of the screen
*/
static func clampFrame(given rectangle: CGRect, cropBox: CGRect, minimumBoxSize: CGFloat) -> CGRect {
func clampValueToRange(input value: CGFloat, maximumValue: CGFloat, minimumValue: CGFloat) -> CGFloat {
var temp = value
temp = floor( min(temp, maximumValue) )
temp = ceil ( max(temp, minimumValue) )
return temp
}
func clampedValues(contentOrigin: CGFloat, clampedOrigin: CGFloat, side: CGFloat) -> (origin:CGFloat, side: CGFloat) {
var internalVariable = side
let internalClampedOrigin = floor( max(clampedOrigin, ceil(contentOrigin)) )
// If we clamp the value, ensure we compensate for the subsequent delta
// generated in the width (Or else, the box will keep growing)
let delta = clampedOrigin - ceil(contentOrigin)
if (delta < -CGFloat.ulpOfOne) {
internalVariable += delta
}
return (internalClampedOrigin,internalVariable)
}
let xValues = clampedValues(contentOrigin: rectangle.origin.x, clampedOrigin: cropBox.origin.x, side: cropBox.width)
let yValues = clampedValues(contentOrigin: rectangle.origin.y, clampedOrigin: cropBox.origin.y, side: cropBox.height)
// Given the clamped X values, make sure we can't extend the crop box beyond the edge of the screen in the current state
// Make sure we can't make the crop box too small
let maxWidth = (rectangle.origin.x + rectangle.width) - xValues.origin
let clampedWidth = clampValueToRange(input: xValues.side, maximumValue: maxWidth, minimumValue: minimumBoxSize)
// Given the clamped Y values, make sure we can't extend the crop box beyond the edge of the screen in the current state
// Make sure we can't make the crop box too small
let maxHeight = (rectangle.origin.y + rectangle.height) - yValues.origin
let clampedHeight = clampValueToRange(input: yValues.side, maximumValue: maxHeight, minimumValue: minimumBoxSize)
return CGRect(x: xValues.origin, y: yValues.origin, width: clampedWidth, height: clampedHeight)
}
static func updateScrollView(_ scrollView: UIScrollView, given imageSize : CGSize, boundingRect: CGRect, newCropBox cropBox: CGRect) {
// if necessary, work out the new minimum size of the scroll view so it fills the crop box
let scale = max(cropBox.height / imageSize.height,
cropBox.width / imageSize.width)
// Reset the scroll view insets to match the region of the new crop rect
scrollView.contentInset = UIEdgeInsets(top : cropBox.minY,
left: cropBox.minX,
bottom: boundingRect.maxY - cropBox.maxY,
right : boundingRect.maxX - cropBox.maxX)
scrollView.minimumZoomScale = scale
// Make sure content isn't smaller than the crop box
scrollView.contentSize = scrollView.contentSize.floored
// IMPORTANT: Force the scroll view to update its content after changing the zoom scale
let zoomScale = scrollView.zoomScale
scrollView.zoomScale = zoomScale
}
/**
Calculates the new crop box frame by the users gesture
*/
static func calculateFrame(given gesturePoint: CGPoint, in cropView: CLDCropView) -> CGRect {
var rectangle = cropView.cropBoxFrame
let originFrame = cropView.cropOriginFrame
let contentFrame = cropView.contentBounds
var point = gesturePoint
point.x = max(contentFrame.origin.x - cropView.cropViewPadding, point.x)
point.y = max(contentFrame.origin.y - cropView.cropViewPadding, point.y)
// The delta between where we first tapped, and where our finger is now
var xDelta = ceil(point.x - cropView.panOriginPoint.x)
var yDelta = ceil(point.y - cropView.panOriginPoint.y)
// Current aspect ratio of the crop box in case we need to clamp it
let aspectRatio = (originFrame.width / originFrame.height)
// Note whether we're being aspect transformed horizontally or vertically
var aspectHorizontal = false
var aspectVertical = false
// Depending on which corner we drag from, set the appropriate min flag to
// ensure we can properly clamp the XY value of the box if it overruns the minimum size
// (Otherwise the image itself will slide with the drag gesture)
var clampMinFromTop = false
var clampMinFromLeft = false
switch cropView.tappedEdge {
case .left:
if cropView.aspectRatioLockEnabled {
aspectHorizontal = true
xDelta = max(xDelta, 0)
let scaleOrigin = CGPoint(x: originFrame.maxX, y: originFrame.midY)
rectangle.size.height = rectangle.width / aspectRatio
rectangle.origin.y = scaleOrigin.y - (rectangle.height * 0.5)
}
let newWidth = originFrame.width - xDelta
let newHeight = originFrame.height
if min(newHeight, newWidth) / max(newHeight, newWidth) >= cropView.minimumAspectRatio {
rectangle.origin.x = originFrame.origin.x + xDelta
rectangle.size.width = originFrame.width - xDelta
}
clampMinFromLeft = true
case .right:
if cropView.aspectRatioLockEnabled {
aspectHorizontal = true
let scaleOrigin = CGPoint(x: originFrame.minX, y: originFrame.midY)
rectangle.size.height = rectangle.width / aspectRatio
rectangle.origin.y = scaleOrigin.y - (rectangle.height * 0.5)
rectangle.size.width = originFrame.size.width + xDelta
rectangle.size.width = min(rectangle.width, contentFrame.height * aspectRatio)
} else {
let newWidth = originFrame.width + xDelta
let newHeight = originFrame.height
if min(newHeight, newWidth) / max(newHeight, newWidth) >= cropView.minimumAspectRatio {
rectangle.size.width = originFrame.width + xDelta
}
}
case .bottom:
if cropView.aspectRatioLockEnabled {
aspectVertical = true
let scaleOrigin = CGPoint(x: originFrame.midX, y: originFrame.minY)
rectangle.size.width = rectangle.height * aspectRatio
rectangle.origin.x = scaleOrigin.x - (rectangle.size.width * 0.5)
rectangle.size.height = originFrame.height + yDelta
rectangle.size.height = min(rectangle.height, contentFrame.width / aspectRatio)
} else {
let newWidth = originFrame.width
let newHeight = originFrame.height + yDelta
if min(newHeight, newWidth) / max(newHeight, newWidth) >= cropView.minimumAspectRatio {
rectangle.size.height = originFrame.height + yDelta
}
}
case .top:
if cropView.aspectRatioLockEnabled {
aspectVertical = true
yDelta = max(0,yDelta)
let scaleOrigin = CGPoint(x: originFrame.midX, y: originFrame.midY)
rectangle.origin.x = scaleOrigin.x - (rectangle.size.width * 0.5)
rectangle.origin.y = originFrame.origin.y + yDelta
rectangle.size.width = rectangle.height * aspectRatio
rectangle.size.height = originFrame.height - yDelta
} else {
let newWidth = originFrame.width
let newHeight = originFrame.height - yDelta
if min(newHeight, newWidth) / max(newHeight, newWidth) >= cropView.minimumAspectRatio {
rectangle.origin.y = originFrame.origin.y + yDelta
rectangle.size.height = originFrame.height - yDelta
}
}
clampMinFromTop = true
case .topLeft:
if cropView.aspectRatioLockEnabled {
xDelta = max(xDelta, 0)
yDelta = max(yDelta, 0)
var distance = CGPoint.zero
distance.x = 1.0 - (xDelta / originFrame.width )
distance.y = 1.0 - (yDelta / originFrame.height)
let scale = (distance.x + distance.y) * 0.5
rectangle.size.width = ceil(originFrame.width * scale)
rectangle.size.height = ceil(originFrame.height * scale)
rectangle.origin.x = originFrame.origin.x + (originFrame.width - rectangle.width )
rectangle.origin.y = originFrame.origin.y + (originFrame.height - rectangle.height)
aspectHorizontal = true
aspectVertical = true
} else {
let newWidth = originFrame.width - xDelta
let newHeight = originFrame.height - yDelta
if min(newHeight, newWidth) / max(newHeight, newWidth) >= cropView.minimumAspectRatio {
rectangle.origin.x = originFrame.origin.x + xDelta
rectangle.origin.y = originFrame.origin.y + yDelta
rectangle.size.width = originFrame.width - xDelta
rectangle.size.height = originFrame.height - yDelta
}
}
clampMinFromTop = true
clampMinFromLeft = true
case .topRight:
if cropView.aspectRatioLockEnabled {
xDelta = min(xDelta, 0)
yDelta = max(yDelta, 0)
var distance = CGPoint.zero
distance.x = 1.0 - ((-xDelta) / originFrame.width )
distance.y = 1.0 - (( yDelta) / originFrame.height)
let scale = (distance.x + distance.y) * 0.5
rectangle.size.width = ceil(originFrame.width * scale)
rectangle.size.height = ceil(originFrame.height * scale)
rectangle.origin.y = originFrame.origin.y + (originFrame.height - rectangle.height)
aspectHorizontal = true
aspectVertical = true
} else {
let newWidth = originFrame.width + xDelta
let newHeight = originFrame.height - yDelta
if min(newHeight, newWidth) / max(newHeight, newWidth) >= cropView.minimumAspectRatio {
rectangle.size.width = originFrame.width + xDelta
rectangle.size.height = originFrame.height - yDelta
rectangle.origin.y = originFrame.origin.y + yDelta
}
}
clampMinFromTop = true
case .bottomLeft:
if cropView.aspectRatioLockEnabled {
var distance = CGPoint.zero
distance.x = 1.0 - ( xDelta / originFrame.width )
distance.y = 1.0 - (-yDelta / originFrame.height)
let scale = (distance.x + distance.y) * 0.5
rectangle.size.width = ceil(originFrame.width * scale)
rectangle.size.height = ceil(originFrame.height * scale)
rectangle.origin.x = originFrame.maxX - rectangle.width
aspectHorizontal = true
aspectVertical = true
} else {
let newWidth = originFrame.width - xDelta
let newHeight = originFrame.height + yDelta
if min(newHeight, newWidth) / max(newHeight, newWidth) >= cropView.minimumAspectRatio {
rectangle.origin.x = originFrame.origin.x + xDelta
rectangle.size.height = originFrame.height + yDelta
rectangle.size.width = originFrame.width - xDelta
}
}
clampMinFromLeft = true
case .bottomRight:
if cropView.aspectRatioLockEnabled {
var distance = CGPoint.zero
distance.x = 1.0 - ((-1 * xDelta) / originFrame.width )
distance.y = 1.0 - ((-1 * yDelta) / originFrame.height)
let scale = (distance.x + distance.y) * 0.5
rectangle.size.width = ceil(originFrame.width * scale)
rectangle.size.height = ceil(originFrame.height * scale)
aspectHorizontal = true
aspectVertical = true
} else {
let newWidth = originFrame.width + xDelta
let newHeight = originFrame.height + yDelta
if min(newHeight, newWidth) / max(newHeight, newWidth) >= cropView.minimumAspectRatio {
rectangle.size.height = originFrame.height + yDelta
rectangle.size.width = originFrame.width + xDelta
}
}
case .none:
break
}
//The absolute max/min size the box may be in the bounds of the crop view
var minSize = CGSize(width: CLDCropView.Constants.minimumBoxSize, height: CLDCropView.Constants.minimumBoxSize)
var maxSize = CGSize(width: contentFrame.width , height: contentFrame.height )
//clamp the box to ensure it doesn't go beyond the bounds we've set
if (cropView.aspectRatioLockEnabled && aspectHorizontal) {
minSize.width = CLDCropView.Constants.minimumBoxSize * aspectRatio
maxSize.height = contentFrame.width / aspectRatio
}
if (cropView.aspectRatioLockEnabled && aspectVertical) {
maxSize.width = contentFrame.height * aspectRatio
minSize.height = CLDCropView.Constants.minimumBoxSize / aspectRatio
}
// Clamp the width if it goes over
if (clampMinFromLeft) {
let maxWidth = cropView.cropOriginFrame.maxX - contentFrame.origin.x
rectangle.size.width = min(rectangle.width, maxWidth)
}
if (clampMinFromTop) {
let maxHeight = cropView.cropOriginFrame.maxY - contentFrame.origin.y
rectangle.size.height = min(rectangle.height, maxHeight)
}
// Clamp the minimum size
rectangle.size.width = max(rectangle.width , minSize.width )
rectangle.size.height = max(rectangle.height, minSize.height)
// Clamp the maximum size
rectangle.size.width = min(rectangle.width , maxSize.width )
rectangle.size.height = min(rectangle.height, maxSize.height)
//Clamp the X position of the box to the interior of the cropping bounds
rectangle.origin.x = max(rectangle.origin.x, contentFrame.minX)
rectangle.origin.x = min(rectangle.origin.x, contentFrame.maxX - minSize.width)
//Clamp the Y postion of the box to the interior of the cropping bounds
rectangle.origin.y = max(rectangle.origin.y, contentFrame.minY)
rectangle.origin.y = min(rectangle.origin.y, contentFrame.maxY - minSize.height)
//Once the box is completely shrunk, clamp its ability to move
if (clampMinFromLeft && rectangle.width <= minSize.width + CGFloat.ulpOfOne) {
rectangle.origin.x = originFrame.maxX - minSize.width
}
//Once the box is completely shrunk, clamp its ability to move
if (clampMinFromTop && rectangle.height <= minSize.height + CGFloat.ulpOfOne) {
rectangle.origin.y = originFrame.maxY - minSize.height
}
return rectangle
}
}
internal extension CLDCropView.Calculator.AspectRatio {
static func postChangeAdjustments(given newValue: CGSize, animated: Bool, in cropView: CLDCropView) -> (scrollOffset: CGPoint, cropBoxFrame: CGRect , ShouldZoomOut: Bool){
var newAspectRatio = newValue
// Passing in an empty size will revert back to the image aspect ratio
if (newAspectRatio.width < CGFloat.ulpOfOne && newAspectRatio.height < CGFloat.ulpOfOne ) {
newAspectRatio = CGSize(width: cropView.imageSize.width, height: cropView.imageSize.height)
}
let boundsFrame = cropView.contentBounds
var rectangle = cropView.cropBoxFrame
var offset = cropView.scrollView.contentOffset
var cropBoxIsPortrait = false
if (Int(newAspectRatio.width) == 1 && Int(newAspectRatio.height) == 1) {
cropBoxIsPortrait = cropView.image.size.width > cropView.image.size.height
} else {
cropBoxIsPortrait = newAspectRatio.width < newAspectRatio.height
}
var shouldZoomOut = false
switch cropBoxIsPortrait {
case true :
let newWidth = floor(rectangle.height * (newAspectRatio.width / newAspectRatio.height) )
var delta = rectangle.width - newWidth
rectangle.size.width = newWidth
offset.x += (delta * 0.5)
// Set to 0 to avoid accidental clamping by the crop frame sanitizer
if (delta < CGFloat.ulpOfOne ) {
rectangle.origin.x = boundsFrame.origin.x
}
// If the aspect ratio causes the new width to extend
// beyond the content width, we'll need to zoom the image out
let boundsWidth = boundsFrame.width
if (newWidth > boundsWidth) {
let scale = boundsWidth / newWidth
// Scale the new height
let newHeight = rectangle.height * scale
delta = rectangle.height - newHeight
rectangle.size.height = newHeight
// Offset the Y position so it stays in the middle
offset.y += (delta * 0.5)
// Clamp the width to the bounds width
rectangle.size.width = boundsWidth
shouldZoomOut = true
}
case false:
let newHeight = floor(rectangle.width * (newAspectRatio.height/newAspectRatio.width) )
var delta = rectangle.height - newHeight
rectangle.size.height = newHeight
offset.y += (delta * 0.5)
if delta < CGFloat.ulpOfOne {
rectangle.origin.y = boundsFrame.origin.y
}
// If the aspect ratio causes the new height to extend
// beyond the content width, we'll need to zoom the image out
let boundsHeight = boundsFrame.height
if (newHeight > boundsHeight) {
let scale = boundsHeight / newHeight
// Scale the new width
let newWidth = rectangle.width * scale
delta = rectangle.size.width - newWidth
rectangle.size.width = newWidth
// Offset the Y position so it stays in the middle
offset.x += (delta * 0.5)
// Clamp the width to the bounds height
rectangle.size.height = boundsHeight
shouldZoomOut = true
}
}
return (scrollOffset: offset, cropBoxFrame: rectangle , ShouldZoomOut: shouldZoomOut)
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/UploadWidget/CropView/CLDCropViewUIManager.swift
================================================
//
// CLDCropViewUIManager.swift
// Cloudinary
//
// Created by Oz Deutsch on 04/10/2020.
//
import UIKit
internal class CLDCropViewUIManager {
internal weak var cropView: CLDCropView!
internal init(cropView: CLDCropView) {
self.cropView = cropView
}
/**
layout the initial image and returns the scaled image size
*/
internal func manualLayout_initialImageAndGetScaledImageSize() -> CGSize {
cropView.scrollView.contentSize = cropView.imageSize
let boundsRectangle = cropView.contentBounds
let boundsSize = boundsRectangle.size
// Work out the minimum scale of the object
var scale = CGFloat(0.0)
// Work out the size of the image to fit into the content bounds
scale = min(boundsRectangle.width / cropView.imageSize.width, boundsRectangle.height / cropView.imageSize.height)
let scaledImageSize = CGSize(width : floor(cropView.imageSize.width * scale),
height: floor(cropView.imageSize.height * scale))
// If an aspect ratio was pre-applied to the crop view, use that to work out the minimum scale the image needs to be to fit
var cropBoxSize = CGSize.zero
if (cropView.hasAspectRatio) {
let ratioScale = cropView.aspectRatio.width / cropView.aspectRatio.height // Work out the size of the width in relation to height
let fullSizeRatio = CGSize(width: boundsSize.height * ratioScale, height: boundsSize.height)
let fitScale = min(boundsSize.width / fullSizeRatio.width, boundsSize.height / fullSizeRatio.height)
cropBoxSize = CGSize(width: fullSizeRatio.width * fitScale, height: fullSizeRatio.height * fitScale)
scale = max(cropBoxSize.width / cropView.imageSize.width, cropBoxSize.height / cropView.imageSize.height)
}
//Whether aspect ratio, or original, the final image size we'll base the rest of the calculations off
let scaledSize = CGSize(width: floor(cropView.imageSize.width * scale), height: floor(cropView.imageSize.height * scale))
// Configure the scroll view
cropView.scrollView.minimumZoomScale = scale
cropView.scrollView.maximumZoomScale = scale * cropView.maximumZoomScale
// Set the crop box to the size we calculated and align in the middle of the screen
var rectangle = CGRect.zero
rectangle.size = cropView.hasAspectRatio ? cropBoxSize : scaledSize
rectangle.origin.x = boundsRectangle.origin.x + floor((boundsRectangle.width - rectangle.width ) * 0.5)
rectangle.origin.y = boundsRectangle.origin.y + floor((boundsRectangle.height - rectangle.height) * 0.5)
cropView.cropBoxFrame = rectangle
// Set the fully zoomed out state initially
cropView.scrollView.zoomScale = cropView.scrollView.minimumZoomScale
cropView.scrollView.contentSize = scaledSize
// If we ended up with a smaller crop box than the content, line up the content so its center
// is in the center of the cropbox
if (rectangle.width < scaledSize.width - CGFloat.ulpOfOne || rectangle.height < scaledSize.height - CGFloat.ulpOfOne) {
var offset = CGPoint.zero
offset.x = -floor(boundsRectangle.midX - (scaledSize.width * 0.5))
offset.y = -floor(boundsRectangle.midY - (scaledSize.height * 0.5))
cropView.scrollView.contentOffset = offset
}
return scaledImageSize
}
internal func manualLayout_moveCroppedContentToCenterAnimated(_ animated: Bool) {
if cropView.internalLayoutDisabled { return }
let contentRect = cropView.contentBounds
var cropFrame = cropView.cropBoxFrame
// Ensure we only proceed after the crop frame has been setup for the first time
if (cropFrame.width < CGFloat.ulpOfOne || cropFrame.height < CGFloat.ulpOfOne) {
return
}
// The scale we need to scale up the crop box to fit full screen
let scale = min(contentRect.width / cropFrame.width, contentRect.height / cropFrame.height)
let focusPoint = CGPoint(x: cropFrame.midX, y: cropFrame.midY)
let midPoint = CGPoint(x: contentRect.midX, y: contentRect.midY)
cropFrame.size.width = ceil(cropFrame.width * scale)
cropFrame.size.height = ceil(cropFrame.height * scale)
cropFrame.origin.x = contentRect.origin.x + ceil(0.5 * (contentRect.width - cropFrame.width ))
cropFrame.origin.y = contentRect.origin.y + ceil(0.5 * (contentRect.height - cropFrame.height))
// Work out the point on the scroll content that the focusPoint is aiming at
var contentTargetPoint = CGPoint.zero
contentTargetPoint.x = ((focusPoint.x + cropView.scrollView.contentOffset.x) * scale)
contentTargetPoint.y = ((focusPoint.y + cropView.scrollView.contentOffset.y) * scale)
// Work out where the crop box is focusing, so we can re-align to center that point
var offset = CGPoint.zero
offset.x = -midPoint.x + contentTargetPoint.x
offset.y = -midPoint.y + contentTargetPoint.y
// Clamp the content so it doesn't create any seams around the grid
offset.x = max(-cropFrame.origin.x, offset.x)
offset.y = max(-cropFrame.origin.y, offset.y)
let translateBlock : () -> Void = {
var offsetHelper = offset
// Setting these scroll view properties will trigger
// the foreground matching method via their delegates,
// multiple times inside the same animation block, resulting
// in glitchy animations.
//
// Disable matching for now, and explicitly update at the end.
self.cropView.disableForgroundMatching = true
// Slight hack. This method needs to be called during `[UIViewController viewDidLayoutSubviews]`
// in order for the crop view to resize itself during iPad split screen events.
// On the first run, even though scale is exactly 1.0f, performing this multiplication introduces
// a floating point noise that zooms the image in by about 5 pixels. This fixes that issue.
if scale < 1.0 - CGFloat.ulpOfOne || scale > 1.0 + CGFloat.ulpOfOne {
self.cropView.scrollView.zoomScale *= scale
self.cropView.scrollView.zoomScale = min(self.cropView.scrollView.maximumZoomScale, self.cropView.scrollView.zoomScale)
}
// If it turns out the zoom operation would have exceeded the minizum zoom scale, don't apply
// the content offset
if (self.cropView.scrollView.zoomScale < self.cropView.scrollView.maximumZoomScale - CGFloat.ulpOfOne) {
offsetHelper.x = min(-cropFrame.maxX + self.cropView.scrollView.contentSize.width , offset.x)
offsetHelper.y = min(-cropFrame.maxY + self.cropView.scrollView.contentSize.height, offset.y)
self.cropView.scrollView.contentOffset = offsetHelper
}
self.cropView.cropBoxFrame = cropFrame
self.cropView.disableForgroundMatching = false
// Explicitly update the matching at the end of the calculations
self.cropView.matchForegroundToBackground()
}
switch animated {
case false:
translateBlock()
case true:
cropView.matchForegroundToBackground()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 1.0, options: [.beginFromCurrentState], animations: translateBlock, completion: nil)
}
}
}
internal func manualLayout_rotateImageNinetyDegreesAnimated(_ animated: Bool, clockwise: Bool) {
// Only allow one rotation animation at a time
if cropView.rotateAnimationInProgress { return }
// Cancel any pending resizing timers
if cropView.resetTimer != nil {
cropView.cancelResetTimer()
cropView.setEditing(false, resetCropBox: true, animated: false)
cropView.cropBoxLastEditedAngle = cropView.angle
cropView.captureStateForImageRotation()
}
// Work out the new angle, and wrap around once we exceed 360s
var newAngle = cropView.angle
newAngle = clockwise ? newAngle + 90 : newAngle - 90
if (newAngle <= -360 || newAngle >= 360) {
newAngle = 0
}
cropView._angle = newAngle
// Convert the new angle to radians
var angleInRadians = CGFloat(0.0)
switch (newAngle) {
case 90: angleInRadians = CGFloat(Double.pi / 2)
case -90: angleInRadians = -CGFloat(Double.pi / 2)
case 180: angleInRadians = CGFloat(Double.pi)
case -180: angleInRadians = -CGFloat(Double.pi)
case 270: angleInRadians = CGFloat(Double.pi + (Double.pi / 2))
case -270: angleInRadians = -CGFloat(Double.pi + (Double.pi / 2))
default: break
}
// Set up the transformation matrix for the rotation
let rotation = CGAffineTransform.identity.rotated(by: angleInRadians)
// Work out how much we'll need to scale everything to fit to the new rotation
let contentBounds = cropView.contentBounds
let cropBoxFrame = cropView.cropBoxFrame
let scale = min(contentBounds.width / cropBoxFrame.height, contentBounds.height / cropBoxFrame.width)
// Work out which section of the image we're currently focusing at
let cropMidPoint = CGPoint(x: cropBoxFrame.midX,
y: cropBoxFrame.midY)
var cropTargetPoint = CGPoint(x: cropMidPoint.x + cropView.scrollView.contentOffset.x,
y: cropMidPoint.y + cropView.scrollView.contentOffset.y)
// Work out the dimensions of the crop box when rotated
var newCropFrame = CGRect.zero
if (labs(cropView.angle) * 1) == ((labs(cropView.cropBoxLastEditedAngle) ) ) ||
(labs(cropView.angle) * -1) == ((labs(cropView.cropBoxLastEditedAngle) - 180) % 360)
{
newCropFrame.size = cropView.cropBoxLastEditedSize
cropView.scrollView.minimumZoomScale = cropView.cropBoxLastEditedMinZoomScale
cropView.scrollView.zoomScale = cropView.cropBoxLastEditedZoomScale
}
else
{
newCropFrame.size = CGSize(width : floor(cropBoxFrame.height * scale),
height: floor(cropBoxFrame.width * scale))
// Re-adjust the scrolling dimensions of the scroll view to match the new size
cropView.scrollView.minimumZoomScale *= scale
cropView.scrollView.zoomScale *= scale
}
newCropFrame.origin.x = floor(contentBounds.midX - (newCropFrame.width * 0.5))
newCropFrame.origin.y = floor(contentBounds.midY - (newCropFrame.height * 0.5))
// If we're animated, generate a snapshot view that we'll animate in place of the real view
var snapshotView: UIView? = nil
if (animated) {
snapshotView = cropView.foregroundContainerView.snapshotView(afterScreenUpdates: false)
cropView.rotateAnimationInProgress = true
}
// Rotate the background image view, inside its container view
cropView.backgroundImageView.transform = rotation
// Flip the width/height of the container view so it matches the rotated image view's size
let containerSize = cropView.backgroundContainerView.frame.size
cropView.backgroundContainerView.frame = CGRect(origin: .zero, size: CGSize(width: containerSize.height, height: containerSize.width))
cropView.backgroundImageView.frame = CGRect(origin: .zero, size: cropView.backgroundImageView.frame.size)
// Rotate the foreground image view to match
cropView.foregroundContainerView.transform = CGAffineTransform.identity
cropView.foregroundImageView.transform = rotation
// Flip the content size of the scroll view to match the rotated bounds
cropView.scrollView.contentSize = cropView.backgroundContainerView.frame.size
// Assign the new crop box frame and re-adjust the content to fill it
cropView.cropBoxFrame = newCropFrame
cropView.moveCroppedContentToCenterAnimated(false)
newCropFrame = cropView.cropBoxFrame
// Work out how to line up out point of interest into the middle of the crop box
cropTargetPoint.x *= scale
cropTargetPoint.y *= scale
// Swap the target dimensions to match a 90 degree rotation (clockwise or counterclockwise)
let swapValue = cropTargetPoint.x
if (clockwise) {
cropTargetPoint.x = cropView.scrollView.contentSize.width - cropTargetPoint.y
cropTargetPoint.y = swapValue
} else {
cropTargetPoint.x = cropTargetPoint.y
cropTargetPoint.y = cropView.scrollView.contentSize.height - swapValue
}
// Reapply the translated scroll offset to the scroll view
let midPoint = CGPoint(x: newCropFrame.midX, y: newCropFrame.midY)
var offset = CGPoint.zero
offset.x = floor(-midPoint.x + cropTargetPoint.x)
offset.y = floor(-midPoint.y + cropTargetPoint.y)
offset.x = max(-cropView.scrollView.contentInset.left, offset.x)
offset.y = max(-cropView.scrollView.contentInset.top , offset.y)
offset.x = min(cropView.scrollView.contentSize.width - (newCropFrame.width - cropView.scrollView.contentInset.right ), offset.x)
offset.y = min(cropView.scrollView.contentSize.height - (newCropFrame.height - cropView.scrollView.contentInset.bottom), offset.y)
// If the scroll view's new scale is 1 and the new offset is equal to the old, will not trigger the delegate 'scrollViewDidScroll:'
// so we should call the method manually to update the foregroundImageView's frame
if (offset.x == cropView.scrollView.contentOffset.x && offset.y == cropView.scrollView.contentOffset.y && scale == 1) {
cropView.matchForegroundToBackground()
}
cropView.scrollView.contentOffset = offset
// If we're animated, play an animation of the snapshot view rotating,
// then fade it out over the live content
if (animated) {
if let snapshotView = snapshotView {
snapshotView.center = CGPoint(x: contentBounds.midX, y: contentBounds.midY)
cropView.addSubview(snapshotView)
}
cropView.backgroundContainerView.isHidden = true
cropView.foregroundContainerView.isHidden = true
cropView.translucencyView.isHidden = true
cropView.gridOverlayView.isHidden = true
UIView.animate(withDuration: 0.45, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.8, options: [.beginFromCurrentState], animations: {
let target = CGAffineTransform.identity
.rotated(by: clockwise ? CGFloat(Double.pi / 2) : -CGFloat(Double.pi / 2))
.scaledBy(x: scale, y: scale)
if let snapshotView = snapshotView {
snapshotView.transform = target
}
}, completion: { (complete) in
self.cropView.backgroundContainerView.isHidden = false
self.cropView.foregroundContainerView.isHidden = false
self.cropView.translucencyView.isHidden = self.cropView.translucencyAlwaysHidden
self.cropView.gridOverlayView.isHidden = false
self.cropView.backgroundContainerView.alpha = 0.0
self.cropView.gridOverlayView.alpha = 0.0
self.cropView.translucencyView.alpha = 1.0
UIView.animate(withDuration: 0.45, animations: {
snapshotView?.alpha = 0.0
self.cropView.backgroundContainerView.alpha = 1.0
self.cropView.gridOverlayView.alpha = 1.0
}, completion: { (complete) in
self.cropView.rotateAnimationInProgress = false
snapshotView?.removeFromSuperview()
// If the aspect ratio lock is not enabled, allow a swap
// If the aspect ratio lock is on, allow a aspect ratio swap
// only if the allowDimensionSwap option is specified.
let aspectRatioCanSwapDimensions =
(!self.cropView.aspectRatioLockEnabled) || (self.cropView.aspectRatioLockEnabled && self.cropView.aspectRatioLockDimensionSwapEnabled)
if (!aspectRatioCanSwapDimensions) {
//This will animate the aspect ratio back to the desired locked ratio after the image is rotated.
self.cropView.setAspectRatio(self.cropView.aspectRatio, animated: animated)
}
})
})
}
cropView.checkForCanReset()
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/UploadWidget/WidgetConfiguration/CLDWidgetConfiguration.swift
================================================
//
// CLDWidgetConfiguration.swift
//
// Copyright (c) 2020 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import UIKit
@objcMembers public class CLDWidgetConfiguration: NSObject {
public var allowRotate : Bool
public var initialAspectLockState: AspectRatioLockState
public var uploadType : CLDUploadType
// MARK: - init
/**
Initializes the `CLDWidgetConfiguration` instance with the specified allowRotate, initialAspectLockState and uploadType.
- parameter allowRotate: A boolean value specifying whether or not to allow image rotation. true by default.
- parameter initialAspectLockState: Enum value specifying the initial aspect ratio lock state. enabledAndOn by default.
- parameter uploadType: CLDUploadType object specifying the upload request type. signed without preset by default.
- returns: The new `CLDWidgetConfiguration` instance.
*/
public init(
allowRotate : Bool = true,
initialAspectLockState: AspectRatioLockState = .enabledAndOff,
uploadType : CLDUploadType = CLDUploadType(signed: true, preset: nil)
) {
self.allowRotate = allowRotate
self.initialAspectLockState = initialAspectLockState
self.uploadType = uploadType
super.init()
}
// MARK: - AspectRatioLockState
/**
Aspect ratio lock state
* enabledAndOff: User can change the aspect ratio lock state - initial state is aspect ratio not locked.
* enabledAndOn: User can change the aspect ratio lock state - initial state is aspect ratio locked.
* disabled: Aspect ratio lock state button is removed. User will be able to change the aspect ratio of the image, but not the lock state.
*/
@objc public enum AspectRatioLockState: Int {
case enabledAndOff
case enabledAndOn
case disabled
}
}
@objcMembers public class CLDUploadType: NSObject {
private(set) var signed: Bool
private(set) var preset: String?
/**
Initializes the `CLDUploadType` instance with the specified signed and preset.
- parameter signed: A boolean value specifying whether to use signed or unsigned upload.
- parameter preset: A string represents the preset for unsigned upload requests. preset MUST be set when choosing unsigned requests.
- returns: The new `CLDUploadType` instance.
*/
public init(signed: Bool, preset: String?) {
self.signed = signed
self.preset = preset
if !signed, (preset == nil || preset == String()) {
print("unsigned upload must have a predefined preset!")
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/UploadWidget/WidgetElements/CLDWidgetAssetContainer.swift
================================================
//
// CLDWidgetAssetContainer.swift
//
// Copyright (c) 2020 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import UIKit
import AVKit
class CLDWidgetAssetContainer: NSObject {
enum AssetType: Int {
case image
case video
}
private(set) var originalImage : UIImage?
var editedImage : UIImage?
private(set) var originalVideo : AVPlayerItem?
var presentationImage: UIImage
private(set) var assetType : AssetType
init(originalImage: UIImage, editedImage: UIImage) {
self.originalImage = originalImage
self.editedImage = editedImage
self.presentationImage = editedImage
assetType = .image
super.init()
}
init(videoUrl: URL) {
let playerItem = AVPlayerItem(url: videoUrl)
self.originalVideo = playerItem
self.presentationImage = CLDWidgetAssetContainer.createThumbnailForVideo(playerItem: self.originalVideo)
assetType = .video
super.init()
}
init(videoItem: AVPlayerItem) {
self.originalVideo = videoItem
self.presentationImage = CLDWidgetAssetContainer.createThumbnailForVideo(playerItem: self.originalVideo)
assetType = .video
super.init()
}
private static func createThumbnailForVideo(playerItem: AVPlayerItem?) -> UIImage {
if let urlAsset = playerItem?.asset as? AVURLAsset {
let url = urlAsset.url
let asset = AVAsset(url: url)
let avAssetImageGenerator = AVAssetImageGenerator(asset: asset)
avAssetImageGenerator.appliesPreferredTrackTransform = true
let thumnailTime = CMTimeMake(value: 2, timescale: 1)
do {
let cgThumbImage = try avAssetImageGenerator.copyCGImage(at: thumnailTime, actualTime: nil)
return UIImage(cgImage: cgThumbImage)
} catch {
print(error.localizedDescription)
return UIImage()
}
}
else {
return UIImage()
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/UploadWidget/WidgetElements/CLDWidgetPreviewCollectionCell.swift
================================================
//
// CLDWidgetPreviewCollectionCell.swift
//
// Copyright (c) 2020 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import UIKit
class CLDWidgetPreviewCollectionCell: UICollectionViewCell {
var imageView: UIImageView!
// MARK: - init
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - private methods
private func initialize() {
self.imageView = UIImageView(frame: self.contentView.bounds)
addSubview(imageView)
addImageViewProperties()
}
private func addImageViewProperties() {
imageView.contentMode = .scaleAspectFill
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/UploadWidget/WidgetImages/BackIconInstructions.swift
================================================
//
// BackIconInstructions.swift
//
// Copyright (c) 2020 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import UIKit
class BackIconInstructions : CLDImageDrawingInstructions {
var targetSize : CGSize
var fillColor : UIColor
// MARK: - Initialization
init(targetSize size: CGSize = CGSize(width: 24.0, height: 24.0), fillColor: UIColor = .white) {
self.targetSize = size
self.fillColor = fillColor
}
// MARK: - draw
func draw(in context: CGContext) {
let container = CGRect(origin: .zero, size: targetSize)
let pathBackArrowPath = UIBezierPath()
pathBackArrowPath.move(to: CGPoint(x: container.minX + 0.83333 * container.width, y: container.minY + 0.45833 * container.height))
pathBackArrowPath.addLine(to: CGPoint(x: container.minX + 0.32625 * container.width, y: container.minY + 0.45833 * container.height))
pathBackArrowPath.addLine(to: CGPoint(x: container.minX + 0.55917 * container.width, y: container.minY + 0.22542 * container.height))
pathBackArrowPath.addLine(to: CGPoint(x: container.minX + 0.50000 * container.width, y: container.minY + 0.16667 * container.height))
pathBackArrowPath.addLine(to: CGPoint(x: container.minX + 0.16667 * container.width, y: container.minY + 0.50000 * container.height))
pathBackArrowPath.addLine(to: CGPoint(x: container.minX + 0.50000 * container.width, y: container.minY + 0.83333 * container.height))
pathBackArrowPath.addLine(to: CGPoint(x: container.minX + 0.55875 * container.width, y: container.minY + 0.77458 * container.height))
pathBackArrowPath.addLine(to: CGPoint(x: container.minX + 0.32625 * container.width, y: container.minY + 0.54167 * container.height))
pathBackArrowPath.addLine(to: CGPoint(x: container.minX + 0.83333 * container.width, y: container.minY + 0.54167 * container.height))
pathBackArrowPath.addLine(to: CGPoint(x: container.minX + 0.83333 * container.width, y: container.minY + 0.45833 * container.height))
pathBackArrowPath.close()
pathBackArrowPath.usesEvenOddFillRule = true
fillColor.setFill()
pathBackArrowPath.fill()
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/UploadWidget/WidgetImages/CropIconInstructions.swift
================================================
//
// CropIconInstructions.swift
//
// Copyright (c) 2020 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import UIKit
class CropIconInstructions : CLDImageDrawingInstructions {
var targetSize : CGSize
var fillColor : UIColor
// MARK: - Initialization
init(targetSize size: CGSize = CGSize(width: 24.0, height: 24.0), fillColor: UIColor = .white) {
self.targetSize = size
self.fillColor = fillColor
}
// MARK: - draw
func draw(in context: CGContext) {
let container = CGRect(origin: .zero, size: targetSize)
let pathCropPath = UIBezierPath()
pathCropPath.move(to: CGPoint(x: container.minX + 0.70833 * container.width, y: container.minY + 0.62500 * container.height))
pathCropPath.addLine(to: CGPoint(x: container.minX + 0.79167 * container.width, y: container.minY + 0.62500 * container.height))
pathCropPath.addLine(to: CGPoint(x: container.minX + 0.79167 * container.width, y: container.minY + 0.29167 * container.height))
pathCropPath.addCurve(to: CGPoint(x: container.minX + 0.70833 * container.width, y: container.minY + 0.20833 * container.height), controlPoint1: CGPoint(x: container.minX + 0.79167 * container.width, y: container.minY + 0.24583 * container.height), controlPoint2: CGPoint(x: container.minX + 0.75417 * container.width, y: container.minY + 0.20833 * container.height))
pathCropPath.addLine(to: CGPoint(x: container.minX + 0.37500 * container.width, y: container.minY + 0.20833 * container.height))
pathCropPath.addLine(to: CGPoint(x: container.minX + 0.37500 * container.width, y: container.minY + 0.29167 * container.height))
pathCropPath.addLine(to: CGPoint(x: container.minX + 0.70833 * container.width, y: container.minY + 0.29167 * container.height))
pathCropPath.addLine(to: CGPoint(x: container.minX + 0.70833 * container.width, y: container.minY + 0.62500 * container.height))
pathCropPath.close()
pathCropPath.move(to: CGPoint(x: container.minX + 0.29167 * container.width, y: container.minY + 0.70833 * container.height))
pathCropPath.addLine(to: CGPoint(x: container.minX + 0.29167 * container.width, y: container.minY + 0.04167 * container.height))
pathCropPath.addLine(to: CGPoint(x: container.minX + 0.20833 * container.width, y: container.minY + 0.04167 * container.height))
pathCropPath.addLine(to: CGPoint(x: container.minX + 0.20833 * container.width, y: container.minY + 0.20833 * container.height))
pathCropPath.addLine(to: CGPoint(x: container.minX + 0.04167 * container.width, y: container.minY + 0.20833 * container.height))
pathCropPath.addLine(to: CGPoint(x: container.minX + 0.04167 * container.width, y: container.minY + 0.29167 * container.height))
pathCropPath.addLine(to: CGPoint(x: container.minX + 0.20833 * container.width, y: container.minY + 0.29167 * container.height))
pathCropPath.addLine(to: CGPoint(x: container.minX + 0.20833 * container.width, y: container.minY + 0.70833 * container.height))
pathCropPath.addCurve(to: CGPoint(x: container.minX + 0.29167 * container.width, y: container.minY + 0.79167 * container.height), controlPoint1: CGPoint(x: container.minX + 0.20833 * container.width, y: container.minY + 0.75417 * container.height), controlPoint2: CGPoint(x: container.minX + 0.24583 * container.width, y: container.minY + 0.79167 * container.height))
pathCropPath.addLine(to: CGPoint(x: container.minX + 0.70833 * container.width, y: container.minY + 0.79167 * container.height))
pathCropPath.addLine(to: CGPoint(x: container.minX + 0.70833 * container.width, y: container.minY + 0.95833 * container.height))
pathCropPath.addLine(to: CGPoint(x: container.minX + 0.79167 * container.width, y: container.minY + 0.95833 * container.height))
pathCropPath.addLine(to: CGPoint(x: container.minX + 0.79167 * container.width, y: container.minY + 0.79167 * container.height))
pathCropPath.addLine(to: CGPoint(x: container.minX + 0.95833 * container.width, y: container.minY + 0.79167 * container.height))
pathCropPath.addLine(to: CGPoint(x: container.minX + 0.95833 * container.width, y: container.minY + 0.70833 * container.height))
pathCropPath.addLine(to: CGPoint(x: container.minX + 0.29167 * container.width, y: container.minY + 0.70833 * container.height))
pathCropPath.close()
pathCropPath.usesEvenOddFillRule = true
fillColor.setFill()
pathCropPath.fill()
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/UploadWidget/WidgetImages/CropRotateIconInstructions.swift
================================================
//
// CropRotateIconInstructions.swift
//
// Copyright (c) 2020 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import UIKit
class CropRotateIconInstructions : CLDImageDrawingInstructions {
var targetSize : CGSize
var fillColor : UIColor
// MARK: - Initialization
init(targetSize size: CGSize = CGSize(width: 24.0, height: 24.0), fillColor: UIColor = .white) {
self.targetSize = size
self.fillColor = fillColor
}
// MARK: - draw
func draw(in context: CGContext) {
let container = CGRect(origin: .zero, size: targetSize)
let pathCropRotatePath = UIBezierPath()
pathCropRotatePath.move(to: CGPoint(x: container.minX + 0.62500 * container.width, y: container.minY + 0.62500 * container.height))
pathCropRotatePath.addLine(to: CGPoint(x: container.minX + 0.70833 * container.width, y: container.minY + 0.62500 * container.height))
pathCropRotatePath.addLine(to: CGPoint(x: container.minX + 0.70833 * container.width, y: container.minY + 0.37500 * container.height))
pathCropRotatePath.addCurve(to: CGPoint(x: container.minX + 0.62500 * container.width, y: container.minY + 0.29167 * container.height), controlPoint1: CGPoint(x: container.minX + 0.70833 * container.width, y: container.minY + 0.32875 * container.height), controlPoint2: CGPoint(x: container.minX + 0.67083 * container.width, y: container.minY + 0.29167 * container.height))
pathCropRotatePath.addLine(to: CGPoint(x: container.minX + 0.37500 * container.width, y: container.minY + 0.29167 * container.height))
pathCropRotatePath.addLine(to: CGPoint(x: container.minX + 0.37500 * container.width, y: container.minY + 0.37500 * container.height))
pathCropRotatePath.addLine(to: CGPoint(x: container.minX + 0.62500 * container.width, y: container.minY + 0.37500 * container.height))
pathCropRotatePath.addLine(to: CGPoint(x: container.minX + 0.62500 * container.width, y: container.minY + 0.62500 * container.height))
pathCropRotatePath.close()
pathCropRotatePath.move(to: CGPoint(x: container.minX + 0.29167 * container.width, y: container.minY + 0.70833 * container.height))
pathCropRotatePath.addLine(to: CGPoint(x: container.minX + 0.29167 * container.width, y: container.minY + 0.20833 * container.height))
pathCropRotatePath.addLine(to: CGPoint(x: container.minX + 0.20833 * container.width, y: container.minY + 0.20833 * container.height))
pathCropRotatePath.addLine(to: CGPoint(x: container.minX + 0.20833 * container.width, y: container.minY + 0.29167 * container.height))
pathCropRotatePath.addLine(to: CGPoint(x: container.minX + 0.12500 * container.width, y: container.minY + 0.29167 * container.height))
pathCropRotatePath.addLine(to: CGPoint(x: container.minX + 0.12500 * container.width, y: container.minY + 0.37500 * container.height))
pathCropRotatePath.addLine(to: CGPoint(x: container.minX + 0.20833 * container.width, y: container.minY + 0.37500 * container.height))
pathCropRotatePath.addLine(to: CGPoint(x: container.minX + 0.20833 * container.width, y: container.minY + 0.70833 * container.height))
pathCropRotatePath.addCurve(to: CGPoint(x: container.minX + 0.29167 * container.width, y: container.minY + 0.79167 * container.height), controlPoint1: CGPoint(x: container.minX + 0.20833 * container.width, y: container.minY + 0.75417 * container.height), controlPoint2: CGPoint(x: container.minX + 0.24542 * container.width, y: container.minY + 0.79167 * container.height))
pathCropRotatePath.addLine(to: CGPoint(x: container.minX + 0.62500 * container.width, y: container.minY + 0.79167 * container.height))
pathCropRotatePath.addLine(to: CGPoint(x: container.minX + 0.62500 * container.width, y: container.minY + 0.87500 * container.height))
pathCropRotatePath.addLine(to: CGPoint(x: container.minX + 0.70833 * container.width, y: container.minY + 0.87500 * container.height))
pathCropRotatePath.addLine(to: CGPoint(x: container.minX + 0.70833 * container.width, y: container.minY + 0.79167 * container.height))
pathCropRotatePath.addLine(to: CGPoint(x: container.minX + 0.79167 * container.width, y: container.minY + 0.79167 * container.height))
pathCropRotatePath.addLine(to: CGPoint(x: container.minX + 0.79167 * container.width, y: container.minY + 0.70833 * container.height))
pathCropRotatePath.addLine(to: CGPoint(x: container.minX + 0.29167 * container.width, y: container.minY + 0.70833 * container.height))
pathCropRotatePath.close()
pathCropRotatePath.move(to: CGPoint(x: container.minX + 0.46042 * container.width, y: container.minY + 0.04167 * container.height))
pathCropRotatePath.addCurve(to: CGPoint(x: container.minX + 0.43292 * container.width, y: container.minY + 0.04333 * container.height), controlPoint1: CGPoint(x: container.minX + 0.45083 * container.width, y: container.minY + 0.04167 * container.height), controlPoint2: CGPoint(x: container.minX + 0.44208 * container.width, y: container.minY + 0.04250 * container.height))
pathCropRotatePath.addLine(to: CGPoint(x: container.minX + 0.59167 * container.width, y: container.minY + 0.20208 * container.height))
pathCropRotatePath.addLine(to: CGPoint(x: container.minX + 0.64708 * container.width, y: container.minY + 0.14667 * container.height))
pathCropRotatePath.addCurve(to: CGPoint(x: container.minX + 0.89583 * container.width, y: container.minY + 0.50000 * container.height), controlPoint1: CGPoint(x: container.minX + 0.78333 * container.width, y: container.minY + 0.21125 * container.height), controlPoint2: CGPoint(x: container.minX + 0.88083 * container.width, y: container.minY + 0.34333 * container.height))
pathCropRotatePath.addLine(to: CGPoint(x: container.minX + 0.95833 * container.width, y: container.minY + 0.50000 * container.height))
pathCropRotatePath.addCurve(to: CGPoint(x: container.minX + 0.46042 * container.width, y: container.minY + 0.04167 * container.height), controlPoint1: CGPoint(x: container.minX + 0.93708 * container.width, y: container.minY + 0.24333 * container.height), controlPoint2: CGPoint(x: container.minX + 0.72250 * container.width, y: container.minY + 0.04167 * container.height))
pathCropRotatePath.close()
pathCropRotatePath.usesEvenOddFillRule = true
fillColor.setFill()
pathCropRotatePath.fill()
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/UploadWidget/WidgetImages/DoneIconInstructions.swift
================================================
//
// DoneIconInstructions.swift
//
// Copyright (c) 2020 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import UIKit
class DoneIconInstructions : CLDImageDrawingInstructions {
var targetSize : CGSize
var fillColor : UIColor
var ovalBackgroundColor: UIColor
// MARK: - Initialization
init(targetSize size: CGSize = CGSize(width: 60.0, height: 60.0), fillColor: UIColor = .white, ovalBackgroundColor: UIColor = UIColor(red: 38.0/255, green: 91.0/255, blue: 220.0/255, alpha: 1.0)) {
self.targetSize = size
self.fillColor = fillColor
self.ovalBackgroundColor = ovalBackgroundColor
}
// MARK: - draw
func draw(in context: CGContext) {
let container = CGRect(origin: .zero, size: targetSize)
let vContainer = CGRect(origin: .zero, size: CGSize(width: targetSize.width/2, height: targetSize.height/2))
// This non-generic function dramatically improves compilation times of complex expressions.
func fastFloor(_ x: CGFloat) -> CGFloat { return floor(x) }
// Oval Drawing
let ovalPath = UIBezierPath(ovalIn: CGRect(x: container.minX + fastFloor(container.width * 0.00000 + 0.5), y: container.minY + fastFloor(container.height * 0.00000 + 0.5), width: fastFloor(container.width * 1.00000 + 0.5) - fastFloor(container.width * 0.00000 + 0.5), height: fastFloor(container.height * 1.00000 + 0.5) - fastFloor(container.height * 0.00000 + 0.5)))
ovalBackgroundColor.setFill()
ovalPath.fill()
//// pathDone Drawing
let pathDonePath = UIBezierPath()
pathDonePath.move(to: CGPoint(x: vContainer.midX + 0.37500 * vContainer.width, y: vContainer.midY + 0.67500 * vContainer.height))
pathDonePath.addLine(to: CGPoint(x: vContainer.midX + 0.20000 * vContainer.width, y: vContainer.midY + 0.50000 * vContainer.height))
pathDonePath.addLine(to: CGPoint(x: vContainer.midX + 0.14167 * vContainer.width, y: vContainer.midY + 0.55833 * vContainer.height))
pathDonePath.addLine(to: CGPoint(x: vContainer.midX + 0.37500 * vContainer.width, y: vContainer.midY + 0.79167 * vContainer.height))
pathDonePath.addLine(to: CGPoint(x: vContainer.midX + 0.87500 * vContainer.width, y: vContainer.midY + 0.29167 * vContainer.height))
pathDonePath.addLine(to: CGPoint(x: vContainer.midX + 0.81667 * vContainer.width, y: vContainer.midY + 0.23333 * vContainer.height))
pathDonePath.addLine(to: CGPoint(x: vContainer.midX + 0.37500 * vContainer.width, y: vContainer.midY + 0.67500 * vContainer.height))
pathDonePath.close()
pathDonePath.usesEvenOddFillRule = true
fillColor.setFill()
pathDonePath.fill()
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/UploadWidget/WidgetImages/RatioLockedIconInstructions.swift
================================================
//
// RatioLockedIconInstructions.swift
//
// Copyright (c) 2020 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import UIKit
class RatioLockedIconInstructions : CLDImageDrawingInstructions {
var targetSize : CGSize
var fillColor : UIColor
// MARK: - Initialization
init(targetSize size: CGSize = CGSize(width: 24.0, height: 24.0), fillColor: UIColor = .white) {
self.targetSize = size
self.fillColor = fillColor
}
// MARK: - draw
func draw(in context: CGContext) {
let container = CGRect(origin: .zero, size: targetSize)
let pathRatioLockedPath = UIBezierPath()
pathRatioLockedPath.move(to: CGPoint(x: container.minX + 0.75000 * container.width, y: container.minY + 0.33333 * container.height))
pathRatioLockedPath.addLine(to: CGPoint(x: container.minX + 0.70833 * container.width, y: container.minY + 0.33333 * container.height))
pathRatioLockedPath.addLine(to: CGPoint(x: container.minX + 0.70833 * container.width, y: container.minY + 0.25000 * container.height))
pathRatioLockedPath.addCurve(to: CGPoint(x: container.minX + 0.50000 * container.width, y: container.minY + 0.04167 * container.height), controlPoint1: CGPoint(x: container.minX + 0.70833 * container.width, y: container.minY + 0.13500 * container.height), controlPoint2: CGPoint(x: container.minX + 0.61500 * container.width, y: container.minY + 0.04167 * container.height))
pathRatioLockedPath.addCurve(to: CGPoint(x: container.minX + 0.29167 * container.width, y: container.minY + 0.25000 * container.height), controlPoint1: CGPoint(x: container.minX + 0.38500 * container.width, y: container.minY + 0.04167 * container.height), controlPoint2: CGPoint(x: container.minX + 0.29167 * container.width, y: container.minY + 0.13500 * container.height))
pathRatioLockedPath.addLine(to: CGPoint(x: container.minX + 0.29167 * container.width, y: container.minY + 0.33333 * container.height))
pathRatioLockedPath.addLine(to: CGPoint(x: container.minX + 0.25000 * container.width, y: container.minY + 0.33333 * container.height))
pathRatioLockedPath.addCurve(to: CGPoint(x: container.minX + 0.16667 * container.width, y: container.minY + 0.41667 * container.height), controlPoint1: CGPoint(x: container.minX + 0.20417 * container.width, y: container.minY + 0.33333 * container.height), controlPoint2: CGPoint(x: container.minX + 0.16667 * container.width, y: container.minY + 0.37083 * container.height))
pathRatioLockedPath.addLine(to: CGPoint(x: container.minX + 0.16667 * container.width, y: container.minY + 0.83333 * container.height))
pathRatioLockedPath.addCurve(to: CGPoint(x: container.minX + 0.25000 * container.width, y: container.minY + 0.91667 * container.height), controlPoint1: CGPoint(x: container.minX + 0.16667 * container.width, y: container.minY + 0.87917 * container.height), controlPoint2: CGPoint(x: container.minX + 0.20417 * container.width, y: container.minY + 0.91667 * container.height))
pathRatioLockedPath.addLine(to: CGPoint(x: container.minX + 0.75000 * container.width, y: container.minY + 0.91667 * container.height))
pathRatioLockedPath.addCurve(to: CGPoint(x: container.minX + 0.83333 * container.width, y: container.minY + 0.83333 * container.height), controlPoint1: CGPoint(x: container.minX + 0.79583 * container.width, y: container.minY + 0.91667 * container.height), controlPoint2: CGPoint(x: container.minX + 0.83333 * container.width, y: container.minY + 0.87917 * container.height))
pathRatioLockedPath.addLine(to: CGPoint(x: container.minX + 0.83333 * container.width, y: container.minY + 0.41667 * container.height))
pathRatioLockedPath.addCurve(to: CGPoint(x: container.minX + 0.75000 * container.width, y: container.minY + 0.33333 * container.height), controlPoint1: CGPoint(x: container.minX + 0.83333 * container.width, y: container.minY + 0.37083 * container.height), controlPoint2: CGPoint(x: container.minX + 0.79583 * container.width, y: container.minY + 0.33333 * container.height))
pathRatioLockedPath.close()
pathRatioLockedPath.move(to: CGPoint(x: container.minX + 0.50000 * container.width, y: container.minY + 0.70833 * container.height))
pathRatioLockedPath.addCurve(to: CGPoint(x: container.minX + 0.41667 * container.width, y: container.minY + 0.62500 * container.height), controlPoint1: CGPoint(x: container.minX + 0.45417 * container.width, y: container.minY + 0.70833 * container.height), controlPoint2: CGPoint(x: container.minX + 0.41667 * container.width, y: container.minY + 0.67083 * container.height))
pathRatioLockedPath.addCurve(to: CGPoint(x: container.minX + 0.50000 * container.width, y: container.minY + 0.54167 * container.height), controlPoint1: CGPoint(x: container.minX + 0.41667 * container.width, y: container.minY + 0.57917 * container.height), controlPoint2: CGPoint(x: container.minX + 0.45417 * container.width, y: container.minY + 0.54167 * container.height))
pathRatioLockedPath.addCurve(to: CGPoint(x: container.minX + 0.58333 * container.width, y: container.minY + 0.62500 * container.height), controlPoint1: CGPoint(x: container.minX + 0.54583 * container.width, y: container.minY + 0.54167 * container.height), controlPoint2: CGPoint(x: container.minX + 0.58333 * container.width, y: container.minY + 0.57917 * container.height))
pathRatioLockedPath.addCurve(to: CGPoint(x: container.minX + 0.50000 * container.width, y: container.minY + 0.70833 * container.height), controlPoint1: CGPoint(x: container.minX + 0.58333 * container.width, y: container.minY + 0.67083 * container.height), controlPoint2: CGPoint(x: container.minX + 0.54583 * container.width, y: container.minY + 0.70833 * container.height))
pathRatioLockedPath.close()
pathRatioLockedPath.move(to: CGPoint(x: container.minX + 0.37500 * container.width, y: container.minY + 0.33333 * container.height))
pathRatioLockedPath.addLine(to: CGPoint(x: container.minX + 0.37500 * container.width, y: container.minY + 0.25000 * container.height))
pathRatioLockedPath.addCurve(to: CGPoint(x: container.minX + 0.50000 * container.width, y: container.minY + 0.12500 * container.height), controlPoint1: CGPoint(x: container.minX + 0.37500 * container.width, y: container.minY + 0.18083 * container.height), controlPoint2: CGPoint(x: container.minX + 0.43083 * container.width, y: container.minY + 0.12500 * container.height))
pathRatioLockedPath.addCurve(to: CGPoint(x: container.minX + 0.62500 * container.width, y: container.minY + 0.25000 * container.height), controlPoint1: CGPoint(x: container.minX + 0.56917 * container.width, y: container.minY + 0.12500 * container.height), controlPoint2: CGPoint(x: container.minX + 0.62500 * container.width, y: container.minY + 0.18083 * container.height))
pathRatioLockedPath.addLine(to: CGPoint(x: container.minX + 0.62500 * container.width, y: container.minY + 0.33333 * container.height))
pathRatioLockedPath.addLine(to: CGPoint(x: container.minX + 0.37500 * container.width, y: container.minY + 0.33333 * container.height))
pathRatioLockedPath.close()
pathRatioLockedPath.usesEvenOddFillRule = true
fillColor.setFill()
pathRatioLockedPath.fill()
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/UploadWidget/WidgetImages/RatioOpenedIconInstructions.swift
================================================
//
// RatioOpenedIconInstructions.swift
//
// Copyright (c) 2020 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import UIKit
class RatioOpenedIconInstructions : CLDImageDrawingInstructions {
var targetSize : CGSize
var fillColor : UIColor
// MARK: - Initialization
init(targetSize size: CGSize = CGSize(width: 24.0, height: 24.0), fillColor: UIColor = .white) {
self.targetSize = size
self.fillColor = fillColor
}
// MARK: - draw
func draw(in context: CGContext) {
let container = CGRect(origin: .zero, size: targetSize)
let pathRatioOpenedPath = UIBezierPath()
pathRatioOpenedPath.move(to: CGPoint(x: container.minX + 0.75000 * container.width, y: container.minY + 0.33333 * container.height))
pathRatioOpenedPath.addLine(to: CGPoint(x: container.minX + 0.70833 * container.width, y: container.minY + 0.33333 * container.height))
pathRatioOpenedPath.addLine(to: CGPoint(x: container.minX + 0.70833 * container.width, y: container.minY + 0.25000 * container.height))
pathRatioOpenedPath.addCurve(to: CGPoint(x: container.minX + 0.50000 * container.width, y: container.minY + 0.04167 * container.height), controlPoint1: CGPoint(x: container.minX + 0.70833 * container.width, y: container.minY + 0.13500 * container.height), controlPoint2: CGPoint(x: container.minX + 0.61500 * container.width, y: container.minY + 0.04167 * container.height))
pathRatioOpenedPath.addCurve(to: CGPoint(x: container.minX + 0.29167 * container.width, y: container.minY + 0.25000 * container.height), controlPoint1: CGPoint(x: container.minX + 0.38500 * container.width, y: container.minY + 0.04167 * container.height), controlPoint2: CGPoint(x: container.minX + 0.29167 * container.width, y: container.minY + 0.13500 * container.height))
pathRatioOpenedPath.addLine(to: CGPoint(x: container.minX + 0.29167 * container.width, y: container.minY + 0.25000 * container.height))
pathRatioOpenedPath.addLine(to: CGPoint(x: container.minX + 0.37500 * container.width, y: container.minY + 0.25000 * container.height))
pathRatioOpenedPath.addLine(to: CGPoint(x: container.minX + 0.37500 * container.width, y: container.minY + 0.25000 * container.height))
pathRatioOpenedPath.addCurve(to: CGPoint(x: container.minX + 0.50000 * container.width, y: container.minY + 0.12500 * container.height), controlPoint1: CGPoint(x: container.minX + 0.37500 * container.width, y: container.minY + 0.18083 * container.height), controlPoint2: CGPoint(x: container.minX + 0.43083 * container.width, y: container.minY + 0.12500 * container.height))
pathRatioOpenedPath.addCurve(to: CGPoint(x: container.minX + 0.62500 * container.width, y: container.minY + 0.25000 * container.height), controlPoint1: CGPoint(x: container.minX + 0.56917 * container.width, y: container.minY + 0.12500 * container.height), controlPoint2: CGPoint(x: container.minX + 0.62500 * container.width, y: container.minY + 0.18083 * container.height))
pathRatioOpenedPath.addLine(to: CGPoint(x: container.minX + 0.62500 * container.width, y: container.minY + 0.33333 * container.height))
pathRatioOpenedPath.addLine(to: CGPoint(x: container.minX + 0.25000 * container.width, y: container.minY + 0.33333 * container.height))
pathRatioOpenedPath.addCurve(to: CGPoint(x: container.minX + 0.16667 * container.width, y: container.minY + 0.41667 * container.height), controlPoint1: CGPoint(x: container.minX + 0.20417 * container.width, y: container.minY + 0.33333 * container.height), controlPoint2: CGPoint(x: container.minX + 0.16667 * container.width, y: container.minY + 0.37083 * container.height))
pathRatioOpenedPath.addLine(to: CGPoint(x: container.minX + 0.16667 * container.width, y: container.minY + 0.83333 * container.height))
pathRatioOpenedPath.addCurve(to: CGPoint(x: container.minX + 0.25000 * container.width, y: container.minY + 0.91667 * container.height), controlPoint1: CGPoint(x: container.minX + 0.16667 * container.width, y: container.minY + 0.87917 * container.height), controlPoint2: CGPoint(x: container.minX + 0.20417 * container.width, y: container.minY + 0.91667 * container.height))
pathRatioOpenedPath.addLine(to: CGPoint(x: container.minX + 0.75000 * container.width, y: container.minY + 0.91667 * container.height))
pathRatioOpenedPath.addCurve(to: CGPoint(x: container.minX + 0.83333 * container.width, y: container.minY + 0.83333 * container.height), controlPoint1: CGPoint(x: container.minX + 0.79583 * container.width, y: container.minY + 0.91667 * container.height), controlPoint2: CGPoint(x: container.minX + 0.83333 * container.width, y: container.minY + 0.87917 * container.height))
pathRatioOpenedPath.addLine(to: CGPoint(x: container.minX + 0.83333 * container.width, y: container.minY + 0.41667 * container.height))
pathRatioOpenedPath.addCurve(to: CGPoint(x: container.minX + 0.75000 * container.width, y: container.minY + 0.33333 * container.height), controlPoint1: CGPoint(x: container.minX + 0.83333 * container.width, y: container.minY + 0.37083 * container.height), controlPoint2: CGPoint(x: container.minX + 0.79583 * container.width, y: container.minY + 0.33333 * container.height))
pathRatioOpenedPath.close()
pathRatioOpenedPath.move(to: CGPoint(x: container.minX + 0.75000 * container.width, y: container.minY + 0.83333 * container.height))
pathRatioOpenedPath.addLine(to: CGPoint(x: container.minX + 0.25000 * container.width, y: container.minY + 0.83333 * container.height))
pathRatioOpenedPath.addLine(to: CGPoint(x: container.minX + 0.25000 * container.width, y: container.minY + 0.41667 * container.height))
pathRatioOpenedPath.addLine(to: CGPoint(x: container.minX + 0.75000 * container.width, y: container.minY + 0.41667 * container.height))
pathRatioOpenedPath.addLine(to: CGPoint(x: container.minX + 0.75000 * container.width, y: container.minY + 0.83333 * container.height))
pathRatioOpenedPath.close()
pathRatioOpenedPath.move(to: CGPoint(x: container.minX + 0.50000 * container.width, y: container.minY + 0.70833 * container.height))
pathRatioOpenedPath.addCurve(to: CGPoint(x: container.minX + 0.58333 * container.width, y: container.minY + 0.62500 * container.height), controlPoint1: CGPoint(x: container.minX + 0.54583 * container.width, y: container.minY + 0.70833 * container.height), controlPoint2: CGPoint(x: container.minX + 0.58333 * container.width, y: container.minY + 0.67083 * container.height))
pathRatioOpenedPath.addCurve(to: CGPoint(x: container.minX + 0.50000 * container.width, y: container.minY + 0.54167 * container.height), controlPoint1: CGPoint(x: container.minX + 0.58333 * container.width, y: container.minY + 0.57917 * container.height), controlPoint2: CGPoint(x: container.minX + 0.54583 * container.width, y: container.minY + 0.54167 * container.height))
pathRatioOpenedPath.addCurve(to: CGPoint(x: container.minX + 0.41667 * container.width, y: container.minY + 0.62500 * container.height), controlPoint1: CGPoint(x: container.minX + 0.45417 * container.width, y: container.minY + 0.54167 * container.height), controlPoint2: CGPoint(x: container.minX + 0.41667 * container.width, y: container.minY + 0.57917 * container.height))
pathRatioOpenedPath.addCurve(to: CGPoint(x: container.minX + 0.50000 * container.width, y: container.minY + 0.70833 * container.height), controlPoint1: CGPoint(x: container.minX + 0.41667 * container.width, y: container.minY + 0.67083 * container.height), controlPoint2: CGPoint(x: container.minX + 0.45417 * container.width, y: container.minY + 0.70833 * container.height))
pathRatioOpenedPath.close()
pathRatioOpenedPath.usesEvenOddFillRule = true
fillColor.setFill()
pathRatioOpenedPath.fill()
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/UploadWidget/WidgetImages/RotateIconInstructions.swift
================================================
//
// RotateIconInstructions.swift
//
// Copyright (c) 2020 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import UIKit
class RotateIconInstructions : CLDImageDrawingInstructions {
var targetSize : CGSize
var fillColor : UIColor
// MARK: - Initialization
init(targetSize size: CGSize = CGSize(width: 24.0, height: 24.0), fillColor: UIColor = .white) {
self.targetSize = size
self.fillColor = fillColor
}
// MARK: - draw
func draw(in context: CGContext) {
let container = CGRect(origin: .zero, size: targetSize)
let pathRotatePath = UIBezierPath()
pathRotatePath.move(to: CGPoint(x: container.minX + 0.30583 * container.width, y: container.minY + 0.26708 * container.height))
pathRotatePath.addLine(to: CGPoint(x: container.minX + 0.03583 * container.width, y: container.minY + 0.53750 * container.height))
pathRotatePath.addLine(to: CGPoint(x: container.minX + 0.30625 * container.width, y: container.minY + 0.80750 * container.height))
pathRotatePath.addLine(to: CGPoint(x: container.minX + 0.57667 * container.width, y: container.minY + 0.53750 * container.height))
pathRotatePath.addLine(to: CGPoint(x: container.minX + 0.30583 * container.width, y: container.minY + 0.26708 * container.height))
pathRotatePath.close()
pathRotatePath.move(to: CGPoint(x: container.minX + 0.15375 * container.width, y: container.minY + 0.53750 * container.height))
pathRotatePath.addLine(to: CGPoint(x: container.minX + 0.30625 * container.width, y: container.minY + 0.38500 * container.height))
pathRotatePath.addLine(to: CGPoint(x: container.minX + 0.45833 * container.width, y: container.minY + 0.53750 * container.height))
pathRotatePath.addLine(to: CGPoint(x: container.minX + 0.30583 * container.width, y: container.minY + 0.69000 * container.height))
pathRotatePath.addLine(to: CGPoint(x: container.minX + 0.15375 * container.width, y: container.minY + 0.53750 * container.height))
pathRotatePath.close()
pathRotatePath.move(to: CGPoint(x: container.minX + 0.80667 * container.width, y: container.minY + 0.27667 * container.height))
pathRotatePath.addCurve(to: CGPoint(x: container.minX + 0.54167 * container.width, y: container.minY + 0.16667 * container.height), controlPoint1: CGPoint(x: container.minX + 0.73375 * container.width, y: container.minY + 0.20333 * container.height), controlPoint2: CGPoint(x: container.minX + 0.63750 * container.width, y: container.minY + 0.16667 * container.height))
pathRotatePath.addLine(to: CGPoint(x: container.minX + 0.54167 * container.width, y: container.minY + 0.03167 * container.height))
pathRotatePath.addLine(to: CGPoint(x: container.minX + 0.36500 * container.width, y: container.minY + 0.20833 * container.height))
pathRotatePath.addLine(to: CGPoint(x: container.minX + 0.54167 * container.width, y: container.minY + 0.38500 * container.height))
pathRotatePath.addLine(to: CGPoint(x: container.minX + 0.54167 * container.width, y: container.minY + 0.25000 * container.height))
pathRotatePath.addCurve(to: CGPoint(x: container.minX + 0.74792 * container.width, y: container.minY + 0.33542 * container.height), controlPoint1: CGPoint(x: container.minX + 0.61625 * container.width, y: container.minY + 0.25000 * container.height), controlPoint2: CGPoint(x: container.minX + 0.69083 * container.width, y: container.minY + 0.27833 * container.height))
pathRotatePath.addCurve(to: CGPoint(x: container.minX + 0.74792 * container.width, y: container.minY + 0.74792 * container.height), controlPoint1: CGPoint(x: container.minX + 0.86167 * container.width, y: container.minY + 0.44917 * container.height), controlPoint2: CGPoint(x: container.minX + 0.86167 * container.width, y: container.minY + 0.63417 * container.height))
pathRotatePath.addCurve(to: CGPoint(x: container.minX + 0.54167 * container.width, y: container.minY + 0.83333 * container.height), controlPoint1: CGPoint(x: container.minX + 0.69083 * container.width, y: container.minY + 0.80500 * container.height), controlPoint2: CGPoint(x: container.minX + 0.61625 * container.width, y: container.minY + 0.83333 * container.height))
pathRotatePath.addCurve(to: CGPoint(x: container.minX + 0.42333 * container.width, y: container.minY + 0.80792 * container.height), controlPoint1: CGPoint(x: container.minX + 0.50125 * container.width, y: container.minY + 0.83333 * container.height), controlPoint2: CGPoint(x: container.minX + 0.46083 * container.width, y: container.minY + 0.82458 * container.height))
pathRotatePath.addLine(to: CGPoint(x: container.minX + 0.36125 * container.width, y: container.minY + 0.87000 * container.height))
pathRotatePath.addCurve(to: CGPoint(x: container.minX + 0.54167 * container.width, y: container.minY + 0.91667 * container.height), controlPoint1: CGPoint(x: container.minX + 0.41750 * container.width, y: container.minY + 0.90083 * container.height), controlPoint2: CGPoint(x: container.minX + 0.47958 * container.width, y: container.minY + 0.91667 * container.height))
pathRotatePath.addCurve(to: CGPoint(x: container.minX + 0.80667 * container.width, y: container.minY + 0.80667 * container.height), controlPoint1: CGPoint(x: container.minX + 0.63750 * container.width, y: container.minY + 0.91667 * container.height), controlPoint2: CGPoint(x: container.minX + 0.73375 * container.width, y: container.minY + 0.88000 * container.height))
pathRotatePath.addCurve(to: CGPoint(x: container.minX + 0.80667 * container.width, y: container.minY + 0.27667 * container.height), controlPoint1: CGPoint(x: container.minX + 0.95333 * container.width, y: container.minY + 0.66042 * container.height), controlPoint2: CGPoint(x: container.minX + 0.95333 * container.width, y: container.minY + 0.42292 * container.height))
pathRotatePath.close()
pathRotatePath.usesEvenOddFillRule = true
fillColor.setFill()
pathRotatePath.fill()
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/UploadWidget/WidgetVideo/CLDDisplayLinkObserver.swift
================================================
//
// CLDDisplayLinkObservers.swift
//
// Copyright (c) 2020 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import UIKit
protocol CLDDisplayLinkObserverDelegate: AnyObject {
func displayLinkObserverDidTick(_ linkObserver: CLDDisplayLinkObserver)
}
class CLDDisplayLinkObserver: NSObject {
private(set) var tickerTimestamp : CFTimeInterval
private(set) var displayLinkTicker: CADisplayLink?
var delayValue : Double
weak var delegate: CLDDisplayLinkObserverDelegate?
// MARK: - Init
init(delegate: CLDDisplayLinkObserverDelegate?) {
self.delayValue = 0.0
self.tickerTimestamp = CFTimeInterval(0.0)
self.displayLinkTicker = nil
super.init()
self.delegate = delegate
}
deinit {
stopTicker()
}
// MARK: - private methods
@objc fileprivate func updateTicker(with displayLink: CADisplayLink) {
guard tickerTimestamp != 0.0 else { tickerTimestamp = displayLink.timestamp ; return }
let delta = displayLink.timestamp - tickerTimestamp
guard delta >= delayValue else { return }
delegate?.displayLinkObserverDidTick(self)
tickerTimestamp = displayLink.timestamp
}
}
// MARK: - internal methods
extension CLDDisplayLinkObserver {
func startTicker() {
guard displayLinkTicker == nil else { return }
let displayLink = CADisplayLink(target: self, selector: #selector(updateTicker(with:)))
displayLink.add(to: RunLoop.main, forMode: RunLoop.Mode.common)
displayLink.add(to: RunLoop.main, forMode: RunLoop.Mode.tracking)
displayLinkTicker = displayLink
}
func stopTicker() {
displayLinkTicker?.remove(from: RunLoop.main, forMode: RunLoop.Mode.common)
displayLinkTicker?.remove(from: RunLoop.main, forMode: RunLoop.Mode.tracking)
displayLinkTicker?.invalidate()
displayLinkTicker = nil
tickerTimestamp = 0.0
}
func isValid() -> Bool {
return displayLinkTicker != nil
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/UploadWidget/WidgetVideo/CLDVideoControlsState.swift
================================================
//
// CLDVideoControlsState.swift
//
// Copyright (c) 2020 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
protocol CLDVideoControlsState: AnyObject {
var controlsView: CLDVideoControlsView { get set }
init(controlsView: CLDVideoControlsView)
func playPausePressed()
func backgroundPressed()
func timerFinished()
func videoEnded()
}
================================================
FILE: Cloudinary/Classes/Core/Features/UploadWidget/WidgetVideo/CLDVideoControlsView.swift
================================================
//
// CLDVideoControlsView.swift
//
// Copyright (c) 2020 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import UIKit
protocol CLDVideoControlsViewDelegate: AnyObject {
func playPressedOnVideoControls (_ videoControls: CLDVideoControlsView)
func pausePressedOnVideoControls(_ videoControls: CLDVideoControlsView)
}
class CLDVideoControlsView: UIControl {
private(set) var playPauseButton : UIButton!
private(set) var visibilityTimer : CLDDisplayLinkObserver!
weak var delegate : CLDVideoControlsViewDelegate?
private(set) var currentState : CLDVideoControlsState!
private(set) var shownAndPlayingState : CLDVideoControlsState!
private(set) var shownAndPausedState : CLDVideoControlsState!
private(set) var hiddenAndPlayingState: CLDVideoControlsState!
private(set) var hiddenAndPausedState : CLDVideoControlsState!
private let transparentBackgroundColor = UIColor.black.withAlphaComponent(0.5)
private let controlTransitionsDuration = 0.2
private let controlAppearanceDuration = 2.5
// MARK: - init
init(frame: CGRect, delegate: CLDVideoControlsViewDelegate?) {
self.delegate = delegate
super.init(frame: frame)
// handle state
shownAndPlayingState = CLDVideoShownAndPlayingState (controlsView: self)
shownAndPausedState = CLDVideoShownAndPausedState (controlsView: self)
hiddenAndPlayingState = CLDVideoHiddenAndPlayingState(controlsView: self)
hiddenAndPausedState = CLDVideoHiddenAndPausedState (controlsView: self)
currentState = shownAndPlayingState
// initial timer state
visibilityTimer = CLDDisplayLinkObserver(delegate: self)
startTimer()
addTarget(self, action: #selector(backgroundPressed), for: .touchUpInside)
createUI()
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
func resetControls() {
currentState = shownAndPlayingState
showControls()
playVideo()
}
}
// MARK: - handle events
extension CLDVideoControlsView {
@objc private func backgroundPressed() {
currentState.backgroundPressed()
}
@objc private func playPausePressed() {
currentState.playPausePressed()
}
func videoEnded() {
currentState.videoEnded()
}
func pauseVideo() {
delegate?.pausePressedOnVideoControls(self)
playPauseButton.setTitle("▶", for: .normal)
stopTimer()
}
func playVideo() {
delegate?.playPressedOnVideoControls(self)
playPauseButton.setTitle("||", for: .normal)
startTimer()
}
}
// MARK: - handle state
extension CLDVideoControlsView {
func setNewState(newState: CLDVideoControlsState) {
currentState = newState
}
}
// MARK: - display link timer
extension CLDVideoControlsView: CLDDisplayLinkObserverDelegate {
func startTimer() {
visibilityTimer.stopTicker()
visibilityTimer.delayValue = controlAppearanceDuration
visibilityTimer.startTicker()
}
func stopTimer() {
visibilityTimer.stopTicker()
}
func displayLinkObserverDidTick(_ linkObserver: CLDDisplayLinkObserver) {
currentState.timerFinished()
}
}
// MARK: - UI
extension CLDVideoControlsView {
private func createUI() {
backgroundColor = transparentBackgroundColor
playPauseButton = UIButton(type: .custom)
playPauseButton.accessibilityIdentifier = "videoViewPausePlayButton"
playPauseButton.setTitle("||", for: .normal)
playPauseButton.addTarget(self, action: #selector(playPausePressed), for: .touchUpInside)
playPauseButton.titleLabel?.font = playPauseButton.titleLabel?.font.withSize(40)
addSubview(playPauseButton)
playPauseButton.cld_addConstraintsToCenter(self)
}
func showControls() {
UIView.animate(withDuration: controlTransitionsDuration, animations: {
self.backgroundColor = self.transparentBackgroundColor
self.playPauseButton.alpha = 1.0
})
}
func hideControls() {
// we dont set the background alpha to 0 in order to keep getting touch events
UIView.animate(withDuration: controlTransitionsDuration, animations: {
self.backgroundColor = UIColor.clear
self.playPauseButton.alpha = 0.0
})
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/UploadWidget/WidgetVideo/CLDVideoHiddenAndPausedState.swift
================================================
//
// CLDVideoHiddenAndPausedState.swift
//
// Copyright (c) 2020 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
class CLDVideoHiddenAndPausedState: NSObject, CLDVideoControlsState {
var controlsView: CLDVideoControlsView
required init(controlsView: CLDVideoControlsView) {
self.controlsView = controlsView
super.init()
}
func playPausePressed() {
print("playPausePressed shouldn't be called in CLDVideoHiddenAndPausedState")
}
func backgroundPressed() {
controlsView.setNewState(newState: controlsView.shownAndPausedState)
controlsView.stopTimer()
controlsView.showControls()
}
func timerFinished() {
print("timerFinished shouldn't be called in CLDVideoHiddenAndPausedState")
}
func videoEnded() {
print("videoEnded shouldn't be called in CLDVideoHiddenAndPausedState")
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/UploadWidget/WidgetVideo/CLDVideoHiddenAndPlayingState.swift
================================================
//
// CLDVideoHiddenAndPlayingState.swift
//
// Copyright (c) 2020 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
class CLDVideoHiddenAndPlayingState: NSObject, CLDVideoControlsState {
var controlsView: CLDVideoControlsView
required init(controlsView: CLDVideoControlsView) {
self.controlsView = controlsView
super.init()
}
func playPausePressed() {
print("playPausePressed shouldn't be called in CLDVideoHiddenAndPlayingState")
}
func backgroundPressed() {
controlsView.setNewState(newState: controlsView.shownAndPlayingState)
controlsView.startTimer()
controlsView.showControls()
}
func timerFinished() {
print("timerFinished shouldn't be called in CLDVideoHiddenAndPlayingState")
}
func videoEnded() {
controlsView.pauseVideo()
controlsView.setNewState(newState: controlsView.shownAndPausedState)
controlsView.showControls()
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/UploadWidget/WidgetVideo/CLDVideoPlayerView.swift
================================================
//
// CLDVideoPlayerView.swift
//
// Copyright (c) 2020 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import UIKit
import AVKit
class CLDVideoPlayerView: UIView {
override class var layerClass: AnyClass {
return AVPlayerLayer.self
}
var player : AVPlayer? {
get {
return playerLayer?.player
}
set {
playerLayer?.player = newValue
}
}
weak var playerLayer : AVPlayerLayer? {
guard let layer = layer as? AVPlayerLayer else { return nil }
return layer as AVPlayerLayer
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/UploadWidget/WidgetVideo/CLDVideoShownAndPausedState.swift
================================================
//
// CLDVideoShownAndPausedState.swift
//
// Copyright (c) 2020 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
class CLDVideoShownAndPausedState: NSObject, CLDVideoControlsState {
var controlsView: CLDVideoControlsView
required init(controlsView: CLDVideoControlsView) {
self.controlsView = controlsView
super.init()
}
func playPausePressed() {
controlsView.playVideo()
controlsView.setNewState(newState: controlsView.shownAndPlayingState)
}
func backgroundPressed() {
controlsView.setNewState(newState: controlsView.hiddenAndPausedState)
controlsView.hideControls()
}
func timerFinished() {
print("timerFinished shouldn't be called in CLDVideoShownAndPausedState")
}
func videoEnded() {
print("videoEnded shouldn't be called in CLDVideoShownAndPausedState")
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/UploadWidget/WidgetVideo/CLDVideoShownAndPlayingState.swift
================================================
//
// CLDVideoShownAndPlayingState.swift
//
// Copyright (c) 2020 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
class CLDVideoShownAndPlayingState: NSObject, CLDVideoControlsState {
var controlsView: CLDVideoControlsView
required init(controlsView: CLDVideoControlsView) {
self.controlsView = controlsView
super.init()
}
func playPausePressed() {
controlsView.pauseVideo()
controlsView.setNewState(newState: controlsView.shownAndPausedState)
}
func backgroundPressed() {
hideControls()
}
func timerFinished() {
hideControls()
}
private func hideControls() {
controlsView.stopTimer()
controlsView.setNewState(newState: controlsView.hiddenAndPlayingState)
controlsView.hideControls()
}
func videoEnded() {
controlsView.pauseVideo()
controlsView.setNewState(newState: controlsView.shownAndPausedState)
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/UploadWidget/WidgetVideo/CLDVideoView.swift
================================================
//
// CLDVideoView.swift
//
// Copyright (c) 2020 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import UIKit
import AVKit
class CLDVideoView: UIView {
private(set) var videoControlsView: CLDVideoControlsView!
private(set) var videoPlayerView : CLDVideoPlayerView!
private(set) var player : AVPlayer
/**
Initializes the CLDVideoView
- parameter frame: The view's frame
- parameter player: The player for the presented video
- returns: A new CLDVideoView instance.
*/
init(frame: CGRect, playerItem: AVPlayerItem?, isMuted: Bool) {
self.player = AVPlayer(playerItem: playerItem)
self.player.isMuted = isMuted
super.init(frame: frame)
createUI()
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
/**
Plays a new video and adjust the controls
- parameter item: The new AVPlayerItem to play
*/
func replaceCurrentItem(with item: AVPlayerItem) {
player.seek(to: CMTime.zero)
player.replaceCurrentItem(with: item)
videoControlsView.resetControls()
}
/**
Pausing the video player
*/
func pauseVideo() {
player.pause()
}
}
// MARK: - CLDVideoControlsViewDelegate
extension CLDVideoView: CLDVideoControlsViewDelegate {
func playPressedOnVideoControls(_ videoControls: CLDVideoControlsView) {
player.play()
}
func pausePressedOnVideoControls(_ videoControls: CLDVideoControlsView) {
player.pause()
}
}
// MARK: - create UI
extension CLDVideoView {
private func createUI() {
videoControlsView = CLDVideoControlsView(frame: frame, delegate: self)
videoControlsView.accessibilityIdentifier = "videoControlsView"
videoPlayerView = CLDVideoPlayerView (frame: frame)
videoPlayerView.accessibilityIdentifier = "videoPlayerView"
addSubview(videoPlayerView)
addSubview(videoControlsView)
videoPlayerView .cld_addConstraintsToFill(self)
videoControlsView.cld_addConstraintsToFill(self)
guard let playerLayer = videoPlayerView.playerLayer else { return }
playerLayer.player = player
NotificationCenter.default.addObserver(self, selector: #selector(videoEnded), name: .AVPlayerItemDidPlayToEndTime, object: nil)
player.play()
}
@objc private func videoEnded() {
player.seek(to: CMTime.zero)
videoControlsView.videoEnded()
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/UploadWidget/WidgetViewControllers/CLDWidgetEditViewController.swift
================================================
//
// CLDWidgetEditViewController.swift
//
// Copyright (c) 2020 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import UIKit
internal protocol CLDWidgetEditDelegate: AnyObject {
func widgetEditViewController(_ controller: CLDWidgetEditViewController, didFinishEditing image: CLDWidgetAssetContainer)
func widgetEditViewControllerDidReset (_ controller: CLDWidgetEditViewController)
func widgetEditViewControllerDidCancel(_ controller: CLDWidgetEditViewController)
}
internal class CLDWidgetEditViewController: UIViewController {
private(set) var buttonsView : UIView!
private(set) var cancelButton: UIButton!
private(set) var rotateButton: UIButton!
private(set) var doneButton : UIButton!
private(set) var cropView : CLDCropView!
private(set) var image : CLDWidgetAssetContainer
private(set) var configuration : CLDWidgetConfiguration?
private(set) var initialAspectLockState: CLDWidgetConfiguration.AspectRatioLockState
internal weak var delegate : CLDWidgetEditDelegate?
private var firstTime : Bool = true
private let bottomCropViewPadding : CGFloat = 10
private let bottomButtonsViewPadding: CGFloat = 10
// MARK: - init
init(
image: CLDWidgetAssetContainer,
configuration: CLDWidgetConfiguration? = nil,
delegate: CLDWidgetEditDelegate? = nil,
initialAspectLockState: CLDWidgetConfiguration.AspectRatioLockState = .enabledAndOff
) {
self.image = image
self.configuration = configuration
self.delegate = delegate
self.initialAspectLockState = initialAspectLockState
super.init(nibName: nil, bundle: nil)
if #available(iOS 11.0, *) {} else {
automaticallyAdjustsScrollViewInsets = false
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
super.loadView()
createUI()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
adjustCropViewLocation(cropView)
if firstTime {
firstTime = false
cropView.performInitialSetup()
}
}
}
// MARK: - crop view
extension CLDWidgetEditViewController {
private func frameForCropView() -> CGRect {
let bounds = view.bounds
var frame = CGRect.zero
frame.size.height = bounds.height - buttonsView.frame.height - bottomButtonsViewPadding - bottomCropViewPadding
frame.size.width = bounds.width
return frame
}
func aspectRatioLockPressed() {
cropView.aspectRatioLockEnabled.toggle()
if cropView.aspectRatioLockEnabled {
cropView.lockAspectRatio(to: cropView.cropBoxFrame.size)
}
}
}
// MARK: - actions
private extension CLDWidgetEditViewController {
@objc func resetPressed(sender: UIButton) {
image.editedImage = image.originalImage
// crete a new crop view with original image
let oldView = cropView
let newView = createCropView(for: image.originalImage!, aspectLockState: initialAspectLockState == .enabledAndOn)
view.insertSubview(newView, belowSubview: cropView)
adjustCropViewLocation(newView)
newView.performInitialSetup()
newView.alpha = 0.0
cropView = newView
UIView.animateKeyframes(withDuration: 1, delay: 0.0, options: [], animations: {
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.4) {
oldView?.alpha = 0.0
}
UIView.addKeyframe(withRelativeStartTime: 0.6, relativeDuration: 0.4) {
newView.alpha = 1.0
}
}) { (success) in
oldView?.isHidden = true
oldView?.removeFromSuperview()
}
cropView.aspectRatioLockEnabled = false
delegate?.widgetEditViewControllerDidReset(self)
}
@objc func rotatePressed(sender: UIButton) {
cropView.rotateImageNinetyDegreesAnimated(true)
}
@objc func donePressed(sender: UIButton) {
let cropFrame = cropView.imageCropFrame
let angle = cropView.angle
let editedImage: CLDWidgetAssetContainer = image
// check if image edited
if angle != 0 || !cropFrame.equalTo(CGRect(origin: CGPoint.zero, size: image.editedImage!.size)) {
let newImage = image.editedImage?.cld_cropImage(to: cropFrame, angle: angle)
editedImage.editedImage = newImage
if let presentationImage = newImage {
editedImage.presentationImage = presentationImage
}
}
delegate?.widgetEditViewController(self, didFinishEditing: editedImage)
}
}
extension UIImage {
func cld_hasAlpha() -> Bool {
guard let cgimage = self.cgImage else { return false }
let alphaInfo = cgimage.alphaInfo
return (alphaInfo == .first || alphaInfo == .last ||
alphaInfo == .premultipliedFirst || alphaInfo == .premultipliedLast)
}
func cld_cropImage(to frame: CGRect, angle: NSInteger) -> UIImage {
var croppedImage: UIImage?
UIGraphicsBeginImageContextWithOptions(frame.size, !cld_hasAlpha(), scale)
let context = UIGraphicsGetCurrentContext()
//To conserve memory in not needing to completely re-render the image re-rotated,
//map the image to a view and then use Core Animation to manipulate its rotation
if (angle != 0) {
let imageView = UIImageView(image: self)
imageView.layer.minificationFilter = .nearest
imageView.layer.magnificationFilter = .nearest
imageView.transform = CGAffineTransform.identity.rotated(by: CGFloat(angle) * (CGFloat(Double.pi)/180.0))
let rotatedRect = imageView.bounds.applying(imageView.transform)
let containerView = UIView(frame: CGRect(origin: .zero, size: rotatedRect.size))
containerView.addSubview(imageView)
imageView.center = containerView.center
context?.translateBy(x: -frame.origin.x, y: -frame.origin.y)
containerView.layer.render(in: context!)
}
else {
context?.translateBy(x: -frame.origin.x, y: -frame.origin.y)
draw(at: .zero)
}
croppedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
if let croppedCGImage = croppedImage?.cgImage {
return UIImage(cgImage:croppedCGImage , scale: scale, orientation: .up)
}
else {
return self
}
}
}
// MARK: - create UI
private extension CLDWidgetEditViewController {
var buttonsViewHeight: CGFloat { return 70 }
// MARK: - methods
func createUI() {
view.backgroundColor = .black
createAllSubviews()
addAllSubviews()
addAllConstraints()
configureScreen()
}
func createCropView(for image: UIImage, aspectLockState: Bool) -> CLDCropView {
let aView = CLDCropView(image: image)
aView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
aView.aspectRatioLockEnabled = aspectLockState
return aView
}
func createAllSubviews() {
buttonsView = UIView()
cropView = createCropView(for: image.editedImage!, aspectLockState: initialAspectLockState == .enabledAndOn)
cancelButton = UIButton(type: .custom)
cancelButton.setTitle("Reset", for: .normal)
cancelButton.addTarget(self, action: #selector(resetPressed), for: .touchUpInside)
let buttonImage = CLDImageGenerator.generateImage(from: RotateIconInstructions())
rotateButton = UIButton(type: .custom)
rotateButton.accessibilityIdentifier = "editViewControllerRotateButton"
rotateButton.setImage(buttonImage, for: .normal)
rotateButton.addTarget(self, action: #selector(rotatePressed), for: .touchUpInside)
doneButton = UIButton(type: .custom)
doneButton.setTitle("Done", for: .normal)
doneButton.addTarget(self, action: #selector(donePressed), for: .touchUpInside)
}
func adjustCropViewLocation(_ aView: CLDCropView) {
aView.frame = frameForCropView()
aView.cropRegionInsets = UIEdgeInsets.zero
aView.moveCroppedContentToCenterAnimated(false)
}
func addAllSubviews() {
view.addSubview(cropView)
view.addSubview(buttonsView)
buttonsView.addSubview(cancelButton)
buttonsView.addSubview(rotateButton)
buttonsView.addSubview(doneButton)
}
func addAllConstraints() {
buttonsView .translatesAutoresizingMaskIntoConstraints = false
cancelButton.translatesAutoresizingMaskIntoConstraints = false
rotateButton.translatesAutoresizingMaskIntoConstraints = false
doneButton .translatesAutoresizingMaskIntoConstraints = false
var buttonsViewConstraints = [
NSLayoutConstraint(item: buttonsView!, attribute: .leading , relatedBy: .equal, toItem: view, attribute: .leading , multiplier: 1, constant: 0),
NSLayoutConstraint(item: buttonsView!, attribute: .trailing , relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: 0),
]
let bottomConstraint: NSLayoutConstraint
if #available(iOS 11.0, *) {
bottomConstraint = buttonsView!.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -bottomButtonsViewPadding)
} else {
bottomConstraint = NSLayoutConstraint(item: buttonsView!, attribute: .bottom , relatedBy: .equal, toItem: bottomLayoutGuide, attribute: .top, multiplier: 1, constant: -bottomButtonsViewPadding)
}
buttonsViewConstraints.append(bottomConstraint)
buttonsViewConstraints.append(NSLayoutConstraint(item: buttonsView!,
attribute: .height , relatedBy: .equal, toItem: nil,
attribute: .notAnAttribute, multiplier: 1, constant: buttonsViewHeight))
NSLayoutConstraint.activate(buttonsViewConstraints)
let cancelButtonConstraints = [
NSLayoutConstraint(item: cancelButton!, attribute: .leading , relatedBy: .equal, toItem: buttonsView , attribute: .leading, multiplier: 1, constant: 0),
NSLayoutConstraint(item: cancelButton!, attribute: .trailing, relatedBy: .equal, toItem: rotateButton, attribute: .leading, multiplier: 1, constant: 0),
NSLayoutConstraint(item: cancelButton!, attribute: .top , relatedBy: .equal, toItem: buttonsView , attribute: .top , multiplier: 1, constant: 0),
NSLayoutConstraint(item: cancelButton!, attribute: .bottom , relatedBy: .equal, toItem: buttonsView , attribute: .bottom , multiplier: 1, constant: 0)
]
NSLayoutConstraint.activate(cancelButtonConstraints)
let rotateButtonConstraints = [
NSLayoutConstraint(item: rotateButton!, attribute: .trailing, relatedBy: .equal, toItem: doneButton , attribute: .leading, multiplier: 1, constant: 0),
NSLayoutConstraint(item: rotateButton!, attribute: .top , relatedBy: .equal, toItem: buttonsView , attribute: .top , multiplier: 1, constant: 0),
NSLayoutConstraint(item: rotateButton!, attribute: .height , relatedBy: .equal, toItem: cancelButton, attribute: .height , multiplier: 1, constant: 0),
NSLayoutConstraint(item: rotateButton!, attribute: .width , relatedBy: .equal, toItem: cancelButton, attribute: .width , multiplier: 1, constant: 0)
]
NSLayoutConstraint.activate(rotateButtonConstraints)
let doneButtonConstraints = [
NSLayoutConstraint(item: doneButton!, attribute: .trailing, relatedBy: .equal, toItem: buttonsView , attribute: .trailing, multiplier: 1, constant: 0),
NSLayoutConstraint(item: doneButton!, attribute: .top , relatedBy: .equal, toItem: buttonsView , attribute: .top , multiplier: 1, constant: 0),
NSLayoutConstraint(item: doneButton!, attribute: .height , relatedBy: .equal, toItem: cancelButton, attribute: .height , multiplier: 1, constant: 0),
NSLayoutConstraint(item: doneButton!, attribute: .width , relatedBy: .equal, toItem: cancelButton, attribute: .width , multiplier: 1, constant: 0)
]
NSLayoutConstraint.activate(doneButtonConstraints)
}
func configureScreen() {
guard let configuration = configuration else { return }
if !configuration.allowRotate {
rotateButton.isEnabled = false
rotateButton.isHidden = true
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/UploadWidget/WidgetViewControllers/CLDWidgetPreviewViewController.swift
================================================
//
// CLDWidgetPreviewViewController.swift
//
// Copyright (c) 2020 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import UIKit
import AVKit
protocol CLDWidgetPreviewDelegate: AnyObject {
func widgetPreviewViewController(_ controller: CLDWidgetPreviewViewController, didFinishEditing assets: [CLDWidgetAssetContainer])
func widgetPreviewViewController(_ controller: CLDWidgetPreviewViewController, didSelect asset: CLDWidgetAssetContainer)
func widgetPreviewViewControllerDidCancel(_ controller: CLDWidgetPreviewViewController)
}
class CLDWidgetPreviewViewController: UIViewController {
private(set) var collectionView: UICollectionView!
private(set) var mainImageView : UIImageView!
private(set) var uploadButton : UIButton!
private(set) var videoView : CLDVideoView!
private(set) var assets : [CLDWidgetAssetContainer]
internal weak var delegate : CLDWidgetPreviewDelegate?
private(set) var selectedIndex : Int
// MARK: - init
init(assets: [CLDWidgetAssetContainer], delegate: CLDWidgetPreviewDelegate? = nil) {
self.assets = assets
self.delegate = delegate
self.selectedIndex = 0
super.init(nibName: nil, bundle: nil)
modalPresentationStyle = .fullScreen
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
super.loadView()
createUI()
}
func selectedImageEdited(newImage: CLDWidgetAssetContainer) {
assets[selectedIndex] = newImage
collectionView.reloadItems(at: [IndexPath(row: selectedIndex, section: 0)])
mainImageView.image = newImage.editedImage
}
func selectedImage() -> CLDWidgetAssetContainer {
return assets[selectedIndex]
}
// MARK: - action
@objc private func uploadPressed(sender: UIButton) {
delegate?.widgetPreviewViewController(self, didFinishEditing: assets)
}
}
// MARK: - UICollectionViewDataSource
extension CLDWidgetPreviewViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return assets.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! CLDWidgetPreviewCollectionCell
cell.imageView.image = assets[indexPath.row].presentationImage
return cell
}
}
// MARK: - UICollectionViewDelegate
extension CLDWidgetPreviewViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard selectedIndex != indexPath.row else { return }
selectedIndex = indexPath.row
let selectedAsset = assets[selectedIndex]
switch selectedAsset.assetType {
case .video: videoSelected(video: selectedAsset.originalVideo!)
case .image: imageSelected(image: selectedAsset.editedImage!)
}
delegate?.widgetPreviewViewController(self, didSelect: selectedAsset)
}
}
// MARK: - present asset
extension CLDWidgetPreviewViewController {
var videoTransitionDelay : TimeInterval { 0.1 }
var videoTransitionDuration: TimeInterval { 0.2 }
func videoSelected(video: AVPlayerItem) {
UIView.animate(withDuration: videoTransitionDuration, animations: {
self.videoView.alpha = 0.0
self.mainImageView.alpha = 0.0
}) { (complete) in
self.videoView.replaceCurrentItem(with: video)
UIView.animate(withDuration: self.videoTransitionDuration, delay: self.videoTransitionDelay, options: .curveEaseInOut, animations: {
self.videoView.alpha = 1
}, completion: nil)
}
}
func imageSelected(image: UIImage) {
videoView.pauseVideo()
mainImageView.image = image
showImageView()
}
func showVideoView() {
UIView.animate(withDuration: 0.4) {
self.videoView.alpha = 1.0
self.mainImageView.alpha = 0.0
}
}
func showImageView() {
UIView.animate(withDuration: 0.4) {
self.videoView.alpha = 0.0
self.mainImageView.alpha = 1.0
}
}
}
// MARK: - create UI
private extension CLDWidgetPreviewViewController {
var cellId : String { return "CLDWidgetPreviewCollectionCell" }
var collectionCellSpacing: CGFloat { return 2 }
var collectionHeight : CGFloat { return 70 }
var buttonSize : CGSize { return CGSize(width: 80, height: 80) }
var sectionInsets : UIEdgeInsets { return UIEdgeInsets(top: 0.0, left: 5.0, bottom: 0.0, right: 5.0) }
var collectionCellSize : CGSize { return CGSize(width: collectionHeight * 0.9, height: collectionHeight) }
// MARK: - private methods
func createUI() {
view.backgroundColor = .black
createAllSubviews()
addAllSubviews()
addAllConstraints()
// initial asset presentation state
switch assets[0].assetType {
case .video: mainImageView.alpha = 0
case .image: videoView.alpha = 0
}
}
func createAllSubviews() {
// collectionView
let layout = UICollectionViewFlowLayout()
layout.sectionInset = sectionInsets
layout.minimumLineSpacing = collectionCellSpacing
layout.minimumInteritemSpacing = collectionCellSpacing
layout.itemSize = collectionCellSize
layout.scrollDirection = .horizontal
collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout)
collectionView.allowsSelection = true
collectionView.allowsMultipleSelection = false
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(CLDWidgetPreviewCollectionCell.self, forCellWithReuseIdentifier: cellId)
// imageView
mainImageView = UIImageView(image: assets[0].editedImage)
mainImageView.contentMode = .scaleAspectFit
// videoView
let playerItem = assets[0].originalVideo
videoView = CLDVideoView(frame: CGRect.zero, playerItem: playerItem, isMuted: true)
videoView.accessibilityIdentifier = "previewViewControllerVideoView"
// upload button
let buttonImage = CLDImageGenerator.generateImage(from: DoneIconInstructions())
uploadButton = UIButton(type: .custom)
uploadButton.setTitle(String(), for: .normal)
uploadButton.setImage(buttonImage, for: .normal)
uploadButton.addTarget(self, action: #selector(uploadPressed), for: .touchUpInside)
}
func addAllSubviews() {
view.addSubview(mainImageView)
view.addSubview(collectionView)
view.addSubview(videoView)
view.addSubview(uploadButton)
}
func addAllConstraints() {
mainImageView .translatesAutoresizingMaskIntoConstraints = false
collectionView.translatesAutoresizingMaskIntoConstraints = false
uploadButton .translatesAutoresizingMaskIntoConstraints = false
videoView .translatesAutoresizingMaskIntoConstraints = false
// collectionView
var collectionConstraints = [
NSLayoutConstraint(item: collectionView!, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 0),
NSLayoutConstraint(item: collectionView!, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: 0),
NSLayoutConstraint(item: collectionView!, attribute: .top, relatedBy: .equal, toItem: mainImageView, attribute: .bottom, multiplier: 1, constant: 5)
]
let bottomConstraint: NSLayoutConstraint
if #available(iOS 11.0, *) {
bottomConstraint = collectionView!.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -10)
} else {
bottomConstraint = NSLayoutConstraint(item: collectionView!, attribute: .bottom , relatedBy: .equal, toItem: bottomLayoutGuide, attribute: .top, multiplier: 1, constant: -10)
}
collectionConstraints.append(bottomConstraint)
collectionConstraints.append(NSLayoutConstraint(item: collectionView!,
attribute: .height , relatedBy : .equal, toItem: nil,
attribute: .notAnAttribute, multiplier: 1, constant: collectionHeight))
NSLayoutConstraint.activate(collectionConstraints)
// imageView
var imageViewConstraints = [
NSLayoutConstraint(item: mainImageView!, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 0),
NSLayoutConstraint(item: mainImageView!, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: 0),
]
if #available(iOS 11.0, *) {
imageViewConstraints.append(mainImageView!.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0))
} else {
imageViewConstraints.append(NSLayoutConstraint(item: mainImageView!,
attribute: .top , relatedBy: .equal, toItem: topLayoutGuide,
attribute: .bottom, multiplier: 1, constant: 0))
}
NSLayoutConstraint.activate(imageViewConstraints)
// videoView
let videoViewConstraints = [
NSLayoutConstraint(item: videoView!, attribute: .leading , relatedBy: .equal, toItem: mainImageView, attribute: .leading , multiplier: 1, constant: 0),
NSLayoutConstraint(item: videoView!, attribute: .trailing, relatedBy: .equal, toItem: mainImageView, attribute: .trailing, multiplier: 1, constant: 0),
NSLayoutConstraint(item: videoView!, attribute: .top , relatedBy: .equal, toItem: mainImageView, attribute: .top , multiplier: 1, constant: 0),
NSLayoutConstraint(item: videoView!, attribute: .bottom , relatedBy: .equal, toItem: mainImageView, attribute: .bottom , multiplier: 1, constant: 0)
]
NSLayoutConstraint.activate(videoViewConstraints)
// uploadButton
let uploadButtonConstraints = [
NSLayoutConstraint(item: uploadButton!, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing , multiplier: 1, constant: 0),
NSLayoutConstraint(item: uploadButton!, attribute: .width , relatedBy: .equal, toItem: nil , attribute: .notAnAttribute , multiplier: 1, constant: buttonSize.width),
NSLayoutConstraint(item: uploadButton!, attribute: .height , relatedBy: .equal, toItem: nil , attribute: .notAnAttribute , multiplier: 1, constant: buttonSize.height),
NSLayoutConstraint(item: uploadButton!, attribute: .bottom , relatedBy: .equal, toItem: mainImageView, attribute: .bottom, multiplier: 1, constant: 0)
]
NSLayoutConstraint.activate(uploadButtonConstraints)
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/UploadWidget/WidgetViewControllers/CLDWidgetViewController.swift
================================================
//
// CLDWidgetViewController.swift
//
// Copyright (c) 2020 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import UIKit
internal protocol CLDWidgetViewControllerDelegate: AnyObject {
func widgetViewController(_ controller: CLDWidgetViewController, didFinishEditing editedAssets: [CLDWidgetAssetContainer])
func widgetViewControllerDidCancel(_ controller: CLDWidgetViewController)
}
internal class CLDWidgetViewController: UIViewController {
private(set) var configuration : CLDWidgetConfiguration?
private(set) var assets : [CLDWidgetAssetContainer]
internal weak var delegate : CLDWidgetViewControllerDelegate?
private(set) var topButtonsView : UIView!
private(set) var backButton : UIButton!
private(set) var actionButton : UIButton!
private(set) var containerView : UIView!
private(set) var previewViewController : CLDWidgetPreviewViewController!
private(set) var editViewController : CLDWidgetEditViewController!
private(set) var editIsPresented : Bool
private var currentAspectLockState: CLDWidgetConfiguration.AspectRatioLockState
private lazy var previewActionButtonTitle = NSAttributedString(string: "edit ", withSuffix: CLDImageGenerator.generateImage(from: CropRotateIconInstructions()))
private lazy var editActionButtonLockedTitle = NSAttributedString(string: "Aspect ratio locked ", withSuffix: CLDImageGenerator.generateImage(from: RatioLockedIconInstructions()))
private lazy var editActionButtonUnlockedTitle = NSAttributedString(string: "Aspect ratio unlocked ", withSuffix: CLDImageGenerator.generateImage(from: RatioOpenedIconInstructions()))
private lazy var editActionButtonEmptyTitle = NSAttributedString(string: String())
private lazy var transitionDuration = 0.5
// MARK: - init
internal init(
assets : [CLDWidgetAssetContainer],
configuration: CLDWidgetConfiguration? = nil,
delegate : CLDWidgetViewControllerDelegate? = nil
) {
self.configuration = configuration
self.assets = assets
self.delegate = delegate
self.editIsPresented = false
self.currentAspectLockState = .enabledAndOff
super.init(nibName: nil, bundle: nil)
if let initialAspectLockState = configuration?.initialAspectLockState {
currentAspectLockState = initialAspectLockState
}
previewViewController = CLDWidgetPreviewViewController(assets: assets, delegate: self)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
super.loadView()
createUI()
}
}
// MARK: - private methods
private extension CLDWidgetViewController {
func moveToPreviewScreen() {
editIsPresented = false
transition(from: editViewController, to: previewViewController, inView: containerView)
updateActionButton(by: .goToEditScreen)
}
func moveToEditScreen(with image: CLDWidgetAssetContainer) {
editIsPresented = true
editViewController = nil
editViewController = CLDWidgetEditViewController(image: image, configuration: configuration, delegate: self, initialAspectLockState: currentAspectLockState)
transition(from: previewViewController, to: editViewController, inView: containerView)
updateActionButton(by: currentAspectLockState)
}
}
// MARK: - CLDWidgetPreviewDelegate
extension CLDWidgetViewController: CLDWidgetPreviewDelegate {
func widgetPreviewViewController(_ controller: CLDWidgetPreviewViewController, didFinishEditing assets: [CLDWidgetAssetContainer]) {
delegate?.widgetViewController(self, didFinishEditing: assets)
}
func widgetPreviewViewControllerDidCancel(_ controller: CLDWidgetPreviewViewController) {
delegate?.widgetViewControllerDidCancel(self)
}
func widgetPreviewViewController(_ controller: CLDWidgetPreviewViewController, didSelect asset: CLDWidgetAssetContainer) {
changeActionButtonState(by: asset)
}
func changeActionButtonState(by asset: CLDWidgetAssetContainer) {
asset.assetType == .image ? updateActionButton(by: .goToEditScreen) : updateActionButton(by: .goToEditScreenDisabled)
}
}
// MARK: - CLDWidgetEditDelegate
extension CLDWidgetViewController: CLDWidgetEditDelegate {
func widgetEditViewController(_ controller: CLDWidgetEditViewController, didFinishEditing image: CLDWidgetAssetContainer) {
previewViewController.selectedImageEdited(newImage: image)
moveToPreviewScreen()
}
func widgetEditViewControllerDidReset(_ controller: CLDWidgetEditViewController) {
if currentAspectLockState != .disabled {
currentAspectLockState = .enabledAndOff
updateActionButton(by: .enabledAndOff)
}
}
func widgetEditViewControllerDidCancel(_ controller: CLDWidgetEditViewController) {
moveToPreviewScreen()
}
}
// MARK: - child presentation
private extension CLDWidgetViewController {
func transition(from oldViewController: UIViewController, to newViewController: UIViewController, inView containerView: UIView) {
// invoke viewWillAppear()...
newViewController.beginAppearanceTransition(true, animated: true)
oldViewController.beginAppearanceTransition(false, animated: true)
// check if its already self child
if newViewController.parent != self {
addChild(newViewController)
newViewController.didMove(toParent: self)
}
// adding newVC.view to fill containerView
newViewController.view.removeFromSuperview()
containerView.addSubview(newViewController.view)
newViewController.view.cld_addConstraintsToFill(containerView)
// prepare for animation
newViewController.view.alpha = 0
containerView.bringSubviewToFront(newViewController.view)
newViewController.view.layoutIfNeeded()
// animate
UIView.animate(withDuration: transitionDuration, delay: 0, options: [], animations: {
newViewController.view.alpha = 1
oldViewController.view.alpha = 0
}) { (complete) in
newViewController.endAppearanceTransition()
oldViewController.endAppearanceTransition()
oldViewController.view.removeFromSuperview()
}
}
func presentAsChild(_ viewController: UIViewController, inView containerView: UIView) {
viewController.beginAppearanceTransition(true, animated: true)
if viewController.parent != self {
addChild(viewController)
viewController.didMove(toParent: self)
}
viewController.view.removeFromSuperview()
containerView.addSubview(viewController.view)
viewController.view.cld_addConstraintsToFill(containerView)
containerView.bringSubviewToFront(viewController.view)
viewController.view.layoutIfNeeded()
viewController.endAppearanceTransition()
}
}
// MARK: - top buttons
private extension CLDWidgetViewController {
enum ActionButtonState: Int {
case aspectRatioEnabledAndOff
case aspectRatioEnabledAndOn
case aspectRatioDisabled
case goToEditScreen
case goToEditScreenDisabled
}
@objc func actionPressed(_ sender: UIButton) {
if editIsPresented {
toggleAspectRatioLockStates()
editViewController.aspectRatioLockPressed()
}
else {
moveToEditScreen(with: previewViewController.selectedImage())
}
}
@objc func backPressed(_ sender: UIButton) {
if editIsPresented {
moveToPreviewScreen()
}
else {
delegate?.widgetViewControllerDidCancel(self)
}
}
func toggleAspectRatioLockStates() {
currentAspectLockState = currentAspectLockState == .enabledAndOff ? .enabledAndOn : .enabledAndOff
updateActionButton(by: currentAspectLockState)
}
func updateActionButton(by aspectLockState: CLDWidgetConfiguration.AspectRatioLockState) {
switch aspectLockState {
case .enabledAndOff: updateActionButton(by: .aspectRatioEnabledAndOff)
case .enabledAndOn : updateActionButton(by: .aspectRatioEnabledAndOn)
case .disabled : updateActionButton(by: .aspectRatioDisabled)
}
}
func updateActionButton(by actionButtonState: ActionButtonState) {
UIView.transition(with: actionButton, duration: transitionDuration, options: .transitionCrossDissolve, animations: {
switch actionButtonState {
case .aspectRatioEnabledAndOff:
self.actionButton.setAttributedTitle(self.editActionButtonUnlockedTitle, for: .normal)
case .aspectRatioEnabledAndOn:
self.actionButton.setAttributedTitle(self.editActionButtonLockedTitle, for: .normal)
case .aspectRatioDisabled : fallthrough
case .goToEditScreenDisabled:
self.actionButton.setAttributedTitle(self.editActionButtonEmptyTitle, for: .normal)
self.actionButton.isEnabled = false
case .goToEditScreen:
self.setActionButtonToGoToEdit()
}
}, completion: nil)
}
func setActionButtonToGoToEdit() {
self.actionButton.setAttributedTitle(previewActionButtonTitle, for: .normal)
self.actionButton.isEnabled = true
}
}
// MARK: - create UI
private extension CLDWidgetViewController {
var topButtonsViewHeight: CGFloat { return 44 }
var backButtonWidthRatio: CGFloat { return 0.25 }
func createUI() {
view.backgroundColor = .black
createAllSubviews()
addAllSubviews()
addAllConstraints()
presentAsChild(previewViewController, inView: containerView)
}
func createAllSubviews() {
// top buttons view
topButtonsView = UIView(frame: CGRect.zero)
// buttons
let buttonImage = CLDImageGenerator.generateImage(from: BackIconInstructions())
backButton = UIButton(type: .custom)
backButton.setImage(buttonImage, for: .normal)
backButton.contentHorizontalAlignment = .left
backButton.contentEdgeInsets = UIEdgeInsets(top: 0, left: 15, bottom: 0, right: 0)
backButton.addTarget(self, action: #selector(backPressed), for: .touchUpInside)
actionButton = UIButton(type: .custom)
actionButton.accessibilityIdentifier = "widgetViewControllerActionButton"
changeActionButtonState(by: assets[0])
actionButton.contentHorizontalAlignment = .right
actionButton.contentEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 15)
actionButton.addTarget(self, action: #selector(actionPressed), for: .touchUpInside)
// container view
containerView = UIView(frame: CGRect.zero)
}
func addAllSubviews() {
view.addSubview(topButtonsView)
topButtonsView.addSubview(backButton)
topButtonsView.addSubview(actionButton)
view.addSubview(containerView)
}
func addAllConstraints() {
topButtonsView.translatesAutoresizingMaskIntoConstraints = false
backButton .translatesAutoresizingMaskIntoConstraints = false
actionButton .translatesAutoresizingMaskIntoConstraints = false
containerView .translatesAutoresizingMaskIntoConstraints = false
// top buttons view
var collectionConstraints = [
NSLayoutConstraint(item: topButtonsView!, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 0),
NSLayoutConstraint(item: topButtonsView!, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: 0),
]
if #available(iOS 11.0, *) {
collectionConstraints.append(topButtonsView!.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0))
} else {
collectionConstraints.append(NSLayoutConstraint(item: topButtonsView!,
attribute: .top , relatedBy: .equal, toItem: topLayoutGuide,
attribute: .bottom, multiplier: 1, constant: 0))
}
collectionConstraints.append(NSLayoutConstraint(item: topButtonsView!,
attribute: .height , relatedBy: .equal, toItem: nil,
attribute: .notAnAttribute, multiplier: 1, constant: topButtonsViewHeight))
NSLayoutConstraint.activate(collectionConstraints)
// buttons
let backButtonConstraints = [
NSLayoutConstraint(item: backButton!, attribute: .leading, relatedBy: .equal, toItem: topButtonsView, attribute: .leading, multiplier: 1, constant: 0),
NSLayoutConstraint(item: backButton!, attribute: .top, relatedBy: .equal, toItem: topButtonsView, attribute: .top, multiplier: 1, constant: 0),
NSLayoutConstraint(item: backButton!, attribute: .bottom, relatedBy: .equal, toItem: topButtonsView, attribute: .bottom, multiplier: 1, constant: 0),
NSLayoutConstraint(item: backButton!, attribute: .width
, relatedBy: .equal, toItem: topButtonsView, attribute: .width, multiplier: backButtonWidthRatio, constant: 0)
]
NSLayoutConstraint.activate(backButtonConstraints)
backButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)
let actionButtonConstraints = [
NSLayoutConstraint(item: actionButton!, attribute: .trailing, relatedBy: .equal, toItem: topButtonsView, attribute: .trailing, multiplier: 1, constant: 0),
NSLayoutConstraint(item: actionButton!, attribute: .top, relatedBy: .equal, toItem: topButtonsView, attribute: .top, multiplier: 1, constant: 0),
NSLayoutConstraint(item: actionButton!, attribute: .bottom, relatedBy: .equal, toItem: topButtonsView, attribute: .bottom, multiplier: 1, constant: 0),
NSLayoutConstraint(item: actionButton!, attribute: .leading, relatedBy: .lessThanOrEqual, toItem: backButton, attribute: .trailing, multiplier: 1, constant: view.frame.width * 0.25),
]
NSLayoutConstraint.activate(actionButtonConstraints)
// container view
var containerViewConstraints = [
NSLayoutConstraint(item: containerView!, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 0),
NSLayoutConstraint(item: containerView!, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: 0),
NSLayoutConstraint(item: containerView!, attribute: .top, relatedBy: .equal, toItem: topButtonsView, attribute: .bottom, multiplier: 1, constant: 0),
]
if #available(iOS 11.0, *) {
containerViewConstraints.append(containerView!.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 0))
} else {
containerViewConstraints.append(NSLayoutConstraint(item: containerView!,
attribute: .bottom, relatedBy : .equal, toItem: bottomLayoutGuide,
attribute: .top , multiplier: 1, constant: 0))
}
NSLayoutConstraint.activate(containerViewConstraints)
}
}
// MARK: - extension UIView
extension UIView {
func cld_addConstraintsToFill(_ parentView: UIView) {
translatesAutoresizingMaskIntoConstraints = false
let constraints = [
NSLayoutConstraint(item: self, attribute: .leading, relatedBy: .equal, toItem: parentView, attribute: .leading, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self, attribute: .trailing, relatedBy: .equal, toItem: parentView, attribute: .trailing, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self, attribute: .top, relatedBy: .equal, toItem: parentView, attribute: .top, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self, attribute: .bottom, relatedBy: .equal, toItem: parentView, attribute: .bottom, multiplier: 1, constant: 0)
]
NSLayoutConstraint.activate(constraints)
}
func cld_addConstraintsToCenter(_ parentView: UIView) {
translatesAutoresizingMaskIntoConstraints = false
let constraints = [
NSLayoutConstraint(item: self, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 80),
NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 80),
NSLayoutConstraint(item: self, attribute: .centerX, relatedBy: .equal, toItem: parentView, attribute: .centerX, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self, attribute: .centerY, relatedBy: .equal, toItem: parentView, attribute: .centerY, multiplier: 1, constant: 0),
]
NSLayoutConstraint.activate(constraints)
}
}
// MARK: - extension NSAttributedString
private extension NSAttributedString {
convenience init(string: String, color: UIColor = .white, withSuffix image: UIImage? = nil) {
if let image = image {
// Create Attachment
let imageAttachment = NSTextAttachment()
imageAttachment.image = image
// Set bound to reposition
let imageOffsetY: CGFloat = -5.0
imageAttachment.bounds = CGRect(x: 0, y: imageOffsetY, width: image.size.width, height: image.size.height)
// Create string with attachment
let attachmentString = NSAttributedString(attachment: imageAttachment)
// Initialize mutable string
let completeText = NSMutableAttributedString(string: string, attributes: [NSAttributedString.Key.foregroundColor: color])
// Add image to mutable string
completeText.append(attachmentString)
self.init(attributedString: completeText)
}
else {
self.init(string: string)
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Uploader/CLDUploader.swift
================================================
//
// CLDUploader.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
The CLDUploader class is used to upload assets to your Cloudinary account's cloud.
*/
@objcMembers open class CLDUploader: CLDBaseNetworkObject {
static public let defaultChunkSize = 20 * 1024 * 1024
// MARK: - Init
fileprivate override init() {
super.init()
}
internal override init(networkCoordinator: CLDNetworkCoordinator) {
super.init(networkCoordinator: networkCoordinator)
}
// MARK: - Actions
/**
Uploads the given data to the configured cloud.
- parameter data: The data to upload.
- parameter uploadPreset: The upload preset to use for unsigned upload.
- parameter params: An object holding all the available parameters for uploading.
- parameter progress: The closure that is called periodically during the data transfer.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: An instance implementing the protocol `CLDNetworkDataRequest`,
allowing the options to add a progress closure that is called periodically during the upload
and a response closure to be called once the upload is finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@discardableResult
open func upload(data: Data, uploadPreset: String, params: CLDUploadRequestParams? = nil, progress: ((Progress) -> Void)? = nil, completionHandler:((_ response: CLDUploadResult?, _ error: NSError?) -> ())? = nil) -> CLDUploadRequest {
let params = params ?? CLDUploadRequestParams()
params.setSigned(false)
params.setUploadPreset(uploadPreset)
return performUpload(data: data, params: params, preprocessChain: CLDPreprocessChain(), progress: progress, completionHandler: completionHandler)
}
/**
Uploads the given data to the configured cloud.
- parameter data: The data to upload.
- parameter uploadPreset: The upload preset to use for unsigned upload.
- parameter params: An object holding all the available parameters for uploading.
- parameter preprocessChain A CLDPreprocessing chain containing processing steps and/or custom encoder to run on the resource before uploading
- parameter progress: The closure that is called periodically during the data transfer.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: An instance implementing the protocol `CLDNetworkDataRequest`,
allowing the options to add a progress closure that is called periodically during the upload
and a response closure to be called once the upload is finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@discardableResult
open func upload(data: Data, uploadPreset: String, params: CLDUploadRequestParams? = nil, preprocessChain:CLDPreprocessChain, progress: ((Progress) -> Void)? = nil, completionHandler:((_ response: CLDUploadResult?, _ error: NSError?) -> ())? = nil) -> CLDUploadRequest {
let params = params ?? CLDUploadRequestParams()
params.setSigned(false)
params.setUploadPreset(uploadPreset)
return performUpload(data: data, params: params, preprocessChain: preprocessChain, progress: progress, completionHandler: completionHandler)
}
/**
Uploads a file from the specified URL to the configured cloud.
The URL can either be of a local file (i.e. from the bundle) or can point to a remote file.
- parameter url: The URL pointing to the file to upload.
- parameter uploadPreset: The upload preset to use for unsigned upload.
- parameter params: An object holding all the available parameters for uploading.
- parameter progress: The closure that is called periodically during the data transfer.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: An instance implementing the protocol `CLDNetworkDataRequest`,
allowing the options to add a progress closure that is called periodically during the upload
and a response closure to be called once the upload is finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@discardableResult
open func upload(url: URL, uploadPreset: String, params: CLDUploadRequestParams? = nil, progress: ((Progress) -> Void)? = nil, completionHandler:((_ response: CLDUploadResult?, _ error: NSError?) -> ())? = nil) -> CLDUploadRequest {
let params = params ?? CLDUploadRequestParams()
params.setSigned(false)
params.setUploadPreset(uploadPreset)
return performUpload(data: url, params: params, preprocessChain: CLDPreprocessChain(), progress: progress, completionHandler: completionHandler)
}
/**
Uploads a file from the specified URL to the configured cloud.
The URL can either be of a local file (i.e. from the bundle) or can point to a remote file.
- parameter url: The URL pointing to the file to upload.
- parameter uploadPreset: The upload preset to use for unsigned upload.
- parameter params: An object holding all the available parameters for uploading.
- parameter preprocessChain A CLDPreprocessing chain containing processing steps and/or custom encoder to run on the resource before uploading
- parameter progress: The closure that is called periodically during the data transfer.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: An instance implementing the protocol `CLDNetworkDataRequest`,
allowing the options to add a progress closure that is called periodically during the upload
and a response closure to be called once the upload is finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@discardableResult
open func upload(url: URL, uploadPreset: String, params: CLDUploadRequestParams? = nil, preprocessChain: CLDPreprocessChain, progress: ((Progress) -> Void)? = nil, completionHandler:((_ response: CLDUploadResult?, _ error: NSError?) -> ())? = nil) -> CLDUploadRequest {
let params = params ?? CLDUploadRequestParams()
params.setSigned(false)
params.setUploadPreset(uploadPreset)
return performUpload(data: url, params: params, preprocessChain: preprocessChain, progress: progress, completionHandler: completionHandler)
}
/**
Uploads the given data to the configured cloud.
- parameter data: The data to upload.
- parameter params: An object holding all the available parameters for uploading.
- parameter progress: The closure that is called periodically during the data transfer.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: An instance implementing the protocol `CLDNetworkDataRequest`,
allowing the options to add a progress closure that is called periodically during the upload
and a response closure to be called once the upload is finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@discardableResult
open func signedUpload(data: Data, params: CLDUploadRequestParams? = nil, progress: ((Progress) -> Void)? = nil, completionHandler:((_ response: CLDUploadResult?, _ error: NSError?) -> ())? = nil) -> CLDUploadRequest {
let params = params ?? CLDUploadRequestParams()
params.setSigned(true)
return performUpload(data: data, params: params, preprocessChain: CLDPreprocessChain(), progress: progress, completionHandler: completionHandler)
}
/**
Uploads the given data to the configured cloud.
- parameter data: The data to upload.
- parameter params: An object holding all the available parameters for uploading.
- parameter preprocessChain A CLDPreprocessing chain containing processing steps and/or custom encoder to run on the resource before uploading
- parameter progress: The closure that is called periodically during the data transfer.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: An instance implementing the protocol `CLDNetworkDataRequest`,
allowing the options to add a progress closure that is called periodically during the upload
and a response closure to be called once the upload is finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@discardableResult
open func signedUpload(data: Data, params: CLDUploadRequestParams? = nil, preprocessChain:CLDPreprocessChain, progress: ((Progress) -> Void)? = nil, completionHandler:((_ response: CLDUploadResult?, _ error: NSError?) -> ())? = nil) -> CLDUploadRequest {
let params = params ?? CLDUploadRequestParams()
params.setSigned(true)
return performUpload(data: data, params: params, preprocessChain:preprocessChain, progress: progress, completionHandler: completionHandler)
}
/**
Uploads a file from the specified URL to the configured cloud.
The URL can either be of a local file (i.e. from the bundle) or can point to a remote file.
- parameter url: The URL pointing to the file to upload.
- parameter params: An object holding all the available parameters for uploading.
- parameter progress: The closure that is called periodically during the data transfer.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: An instance implementing the protocol `CLDNetworkDataRequest`,
allowing the options to add a progress closure that is called periodically during the upload
and a response closure to be called once the upload is finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@discardableResult
open func signedUpload(url: URL, params: CLDUploadRequestParams? = nil, progress: ((Progress) -> Void)? = nil, completionHandler:((_ response: CLDUploadResult?, _ error: NSError?) -> ())? = nil) -> CLDUploadRequest {
let params = params ?? CLDUploadRequestParams()
params.setSigned(true)
return performUpload(data: url, params: params, preprocessChain: CLDPreprocessChain(), progress: progress, completionHandler: completionHandler)
}
/**
Uploads a file from the specified URL to the configured cloud.
The URL can either be of a local file (i.e. from the bundle) or can point to a remote file.
- parameter url: The URL pointing to the file to upload.
- parameter params: An object holding all the available parameters for uploading.
- parameter preprocessChain A CLDPreprocessing chain containing processing steps and/or custom encoder to run on the resource before uploading
- parameter progress: The closure that is called periodically during the data transfer.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: An instance implementing the protocol `CLDNetworkDataRequest`,
allowing the options to add a progress closure that is called periodically during the upload
and a response closure to be called once the upload is finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@discardableResult
open func signedUpload(url: URL, params: CLDUploadRequestParams? = nil, preprocessChain: CLDPreprocessChain, progress: ((Progress) -> Void)? = nil, completionHandler:((_ response: CLDUploadResult?, _ error: NSError?) -> ())? = nil) -> CLDUploadRequest {
let params = params ?? CLDUploadRequestParams()
params.setSigned(true)
return performUpload(data: url, params: params, preprocessChain: preprocessChain, progress: progress, completionHandler: completionHandler)
}
/**
Uploads a file in chunks from the specified local-file URL to the configured cloud
- parameter url: The URL pointing to the file to upload.
- parameter uploadPreset: The upload preset to use for unsigned upload.
- parameter params: An object holding all the available parameters for uploading.
- parameter progress: The closure that is called periodically during the data transfer.
- parameter completionHandler: The closure to be called once the request is prepared, holding either the request object or an error
*/
@discardableResult
open func uploadLarge(url: URL, uploadPreset: String, params: CLDUploadRequestParams = CLDUploadRequestParams(), chunkSize: Int = defaultChunkSize, progress: ((Progress) -> Void)? = nil,
completionHandler: CLDUploadCompletionHandler? = nil) -> CLDUploadRequest{
params.setSigned(false)
params.setUploadPreset(uploadPreset)
return performUploadLarge(url: url, params: params, preprocessChain: CLDPreprocessChain(), chunkSize: chunkSize, progress: progress, completionHandler: completionHandler)
}
/**
Uploads a file in chunks from the specified local-file URL to the configured cloud
- parameter url: The URL pointing to the file to upload.
- parameter uploadPreset: The upload preset to use for unsigned upload.
- parameter params: An object holding all the available parameters for uploading.
- parameter preprocessChain A CLDPreprocessing chain containing processing steps and/or custom encoder to run on the resource before uploading
- parameter progress: The closure that is called periodically during the data transfer.
- parameter completionHandler: The closure to be called once the request is prepared, holding either the request object or an error
*/
@discardableResult
open func uploadLarge(url: URL, uploadPreset: String, params: CLDUploadRequestParams = CLDUploadRequestParams(), preprocessChain: CLDPreprocessChain, chunkSize: Int = defaultChunkSize, progress: ((Progress) -> Void)? = nil,
completionHandler: CLDUploadCompletionHandler? = nil) -> CLDUploadRequest{
params.setSigned(false)
params.setUploadPreset(uploadPreset)
return performUploadLarge(url: url, params: params, preprocessChain: preprocessChain, chunkSize: chunkSize, progress: progress, completionHandler: completionHandler)
}
/**
Uploads a file in chunks from the specified local-file URL to the configured cloud
- parameter url: The URL pointing to the local file to upload.
- parameter params: An object holding all the available parameters for uploading.
- parameter progress: The closure that is called periodically during the data transfer.
- parameter completionHandler: The closure to be called once the request is prepared, holding either the request object or an error
*/
@discardableResult
open func signedUploadLarge(url: URL, params: CLDUploadRequestParams = CLDUploadRequestParams(), chunkSize: Int = defaultChunkSize, progress: ((Progress) -> Void)? = nil,
completionHandler: CLDUploadCompletionHandler? = nil) -> CLDUploadRequest{
params.setSigned(true)
return performUploadLarge(url: url, params: params, preprocessChain: CLDPreprocessChain(), chunkSize: chunkSize, progress: progress, completionHandler: completionHandler)
}
/**
Uploads a file in chunks from the specified local-file URL to the configured cloud
- parameter url: The URL pointing to the local file to upload.
- parameter params: An object holding all the available parameters for uploading.
- parameter preprocessChain A CLDPreprocessing chain containing processing steps and/or custom encoder to run on the resource before uploading
- parameter progress: The closure that is called periodically during the data transfer.
- parameter completionHandler: The closure to be called once the request is prepared, holding either the request object or an error
*/
@discardableResult
open func signedUploadLarge(url: URL, params: CLDUploadRequestParams = CLDUploadRequestParams(), preprocessChain: CLDPreprocessChain, chunkSize: Int = defaultChunkSize, progress: ((Progress) -> Void)? = nil,
completionHandler: CLDUploadCompletionHandler? = nil) -> CLDUploadRequest{
params.setSigned(true)
return performUploadLarge(url: url, params: params, preprocessChain: preprocessChain, chunkSize: chunkSize, progress: progress, completionHandler: completionHandler)
}
fileprivate func performUploadLarge(url: URL, params: CLDUploadRequestParams, preprocessChain: CLDPreprocessChain, chunkSize: Int, progress: ((Progress) -> Void)? = nil,
completionHandler: CLDUploadCompletionHandler? = nil) -> CLDUploadRequest {
if (url.absoluteString.cldIsRemoteUrl()){
// send the request to the regular upload function (that handles remote urls)
return performUpload(data: url, params: params, preprocessChain: preprocessChain)
}
let uploadRequest = CLDUploadRequestWrapper()
if let handler = completionHandler {
uploadRequest.response(handler)
}
if let progress = progress {
uploadRequest.progress(progress)
}
guard chunkSize >= 5 * 1024 * 1024 else {
uploadRequest.setRequestError(CLDError.error(code: CLDError.CloudinaryErrorCode.generalErrorCode, message: "Chunk size must be greater than 5[MB]"))
return uploadRequest
}
DispatchQueue.global().async {
let newUrl:URL
if (preprocessChain.isEmpty()){
newUrl = url
} else {
do {
newUrl = try preprocessChain.execute(resourceData: url)
} catch let error as NSError {
uploadRequest.setRequestError(error)
return
} catch {
uploadRequest.setRequestError(CLDError.generalError())
return
}
}
let totalLength = CLDFileUtils.getFileSize(url: newUrl)
guard totalLength != nil else {
uploadRequest.setRequestError (CLDError.error(code: CLDError.CloudinaryErrorCode.failedRetrievingFileInfo, message: "zero file length"))
return
}
let randomId = NSUUID().uuidString
if let (baseUrl, parts) = CLDFileUtils.splitFile(url: newUrl, chunkSize: chunkSize) {
uploadRequest.setRequestsData(count: parts.count, totalLength: totalLength)
uploadRequest.cleanupHandler { success in
DispatchQueue.global().async {
CLDFileUtils.removeFile(file: baseUrl!)
}
}
for part in parts {
let range = "bytes \(part.offset)-\(part.offset + Int64(part.length - 1))/\(totalLength!)"
uploadRequest.addRequest(self.performUpload(data: part.url, params: params, extraHeaders: ["X-Unique-Upload-Id": randomId, "Content-Range": range], preprocessChain: CLDPreprocessChain()))
}
} else {
uploadRequest.setRequestError (CLDError.error(code: CLDError.CloudinaryErrorCode.failedRetrievingFileInfo, message: "Could not process file."))
}
}
return uploadRequest
}
fileprivate func performUpload(data: Any, params: CLDUploadRequestParams, extraHeaders: [String:String]? = [:], preprocessChain:CLDPreprocessChain, progress: ((Progress) -> Void)? = nil, completionHandler:((_ response: CLDUploadResult?, _ error: NSError?) -> ())? = nil) -> CLDUploadRequest {
let uploadRequest:CLDUploadRequest
// if preprocess or validations are required, we'll need to offload the work to a background queue
if preprocessChain.isEmpty(){
let request = networkCoordinator.upload(data, params: params, extraHeaders: extraHeaders)
uploadRequest = CLDDefaultUploadRequest(networkDataRequest: request)
} else {
let newRequest = CLDUploadRequestWrapper()
uploadRequest = newRequest
DispatchQueue.global().async {
do {
let newUrl = try preprocessChain.execute(resourceData: data)
let length = CLDFileUtils.getFileSize(url: newUrl)
newRequest.setRequestsData(count: 1, totalLength: length)
// perform upload with the newly created file url, and empty preprocessing (it's done already)
newRequest.addRequest(self.performUpload(data: newUrl, params: params, extraHeaders: extraHeaders, preprocessChain: CLDPreprocessChain()))
} catch let error as NSError {
newRequest.setRequestError(error)
} catch {
newRequest.setRequestError(CLDError.generalError())
}
}
}
if let completionHandler = completionHandler {
uploadRequest.response(completionHandler)
}
if let progress = progress {
uploadRequest.progress(progress)
}
return uploadRequest
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Uploader/Preupload/CLDPreprocessChain.swift
================================================
//
//
// CLDPreprocessChain.swift
//
// Copyright (c) 2017 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
public typealias CLDPreprocessStep = (T) throws -> T
public typealias CLDResourceEncoder = (T) throws -> URL?
/**
The CLDPreprocessChain is a generic base class used to run preprocessing on resources before uploading.
Supports processing, validations and encoders, all fully customizable. Note: This class should not be used
directly, use a concrete subclass (e.g. CLDImagePreprocessingChain).
*/
public class CLDPreprocessChain {
internal var encoder: CLDResourceEncoder?
var chain = [CLDPreprocessStep]()
internal init() {
}
/**
Returns true if the chain is empty (= NOP chain).
*/
public func isEmpty() -> Bool {
return encoder == nil && chain.isEmpty
}
/**
Add a step to the chain, for preprocess or validation
- parameter preprocess: A CLDPreprocessStep to be used to encode the resource after the processing
*/
@discardableResult
public func addStep(_ preprocess: @escaping CLDPreprocessStep) -> Self {
chain.append(preprocess)
return self
}
/**
Set a custom resource encoder to use when saving the resource after executing the chain
- parameter encoder: A CLDResourceEncoder to be used to encode the resource after the processing
*/
@discardableResult
public func setEncoder(_ encoder: @escaping CLDResourceEncoder) -> Self {
self.encoder = encoder
return self
}
internal func execute(resourceData: Any) throws -> URL {
try verifyEncoder()
let resource: T? = try decodeResource(resourceData)
if var resource = resource {
for preprocess in chain {
resource = try preprocess(resource)
}
if let url = try encoder!(resource) {
return url
} else {
throw CLDError.error(code: CLDError.CloudinaryErrorCode.preprocessingError, message: "Error encoding resource")
}
} else {
throw CLDError.error(code: CLDError.CloudinaryErrorCode.preprocessingError, message: "Error decoding resource")
}
}
internal func decodeResource(_ resourceData: Any) throws -> T? {
throw CLDError.error(code: CLDError.CloudinaryErrorCode.preprocessingError, message: "No decoder implemented - Did you mean to use ImagePreprocessChain?")
}
internal func verifyEncoder() throws {
if (encoder == nil) {
throw CLDError.error(code: CLDError.CloudinaryErrorCode.preprocessingError, message: "No encoder set - Did you mean to use ImagePreprocessChain?")
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Uploader/RequestParams/CLDUploadRequestParams.swift
================================================
//
// CLDUploadRequestParams.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
This class represents the different parameters that can be passed when performing an upload request.
For more information see the [documentation](http://cloudinary.com/documentation/image_upload_api_reference#upload).
*/
@objcMembers open class CLDUploadRequestParams: CLDRequestParams {
/**
A boolean variable representing whether or not the request should be signed.
*/
internal var signed: Bool = false
// MARK: Init
public override init() {
super.init()
}
/**
Initializes a CLDUploadRequestParams instance.
- parameter params: A dictionary of the request parameters.
- returns: A new instance of CLDUploadRequestParams.
*/
public init(params: [String : AnyObject]) {
super.init()
self.params = params
}
internal override func merge(_ other: CLDRequestParams?){
super.merge(other)
if let uploadRequest = other as? CLDUploadRequestParams {
self.signed = uploadRequest.signed
}
}
// MARK: - Get Values
open var publicId: String? {
return getParam(.PublicId) as? String
}
open var format: String? {
return getParam(.Format) as? String
}
open var type: String? {
return getParam(.FileType) as? String
}
open var notificationUrl: String? {
return getParam(.NotificationUrl) as? String
}
open var eagerNotificationUrl: String? {
return getParam(.EagerNotificationUrl) as? String
}
open var proxy: String? {
return getParam(.Proxy) as? String
}
open var folder: String? {
return getParam(.Folder) as? String
}
open var moderation: String? {
return getParam(.Moderation) as? String
}
open var accessControl: String? {
return getParam(.AccessControl) as? String
}
open var eval: String? {
return getParam(.Eval) as? String
}
open var rawConvert: String? {
return getParam(.RawConvert) as? String
}
open var detection: String? {
return getParam(.Detection) as? String
}
open var categorization: String? {
return getParam(.Categorization) as? String
}
open var similaritySearch: String? {
return getParam(.SimilaritySearch) as? String
}
open var autoTagging: String? {
return getParam(.AutoTagging) as? String
}
open var backup: Bool? {
return getParam(.Backup) as? Bool
}
open var useFilename: Bool? {
return getParam(.UseFilename) as? Bool
}
open var uniqueFilename: Bool? {
return getParam(.UniqueFilename) as? Bool
}
open var discardOriginalFilename: Bool? {
return getParam(.DiscardOriginalFilename) as? Bool
}
open var async: Bool? {
return getParam(.Async) as? Bool
}
open var eagerAsync: Bool? {
return getParam(.EagerAsync) as? Bool
}
open var invalidate: Bool? {
return getParam(.Invalidate) as? Bool
}
open var overwrite: Bool? {
return getParam(.Overwrite) as? Bool
}
open var imageMetadata: Bool? {
return getParam(.ImageMetadata) as? Bool
}
open var colors: Bool? {
return getParam(.Colors) as? Bool
}
open var phash: Bool? {
return getParam(.Phash) as? Bool
}
open var faces: Bool? {
return getParam(.Faces) as? Bool
}
open var returnDeleteToken: Bool? {
return getParam(.ReturnDeleteToken) as? Bool
}
open var transformation: String? {
return getParam(.Transformation) as? String
}
open var tags: String? {
return getParam(.Tags) as? String
}
open var allowedFormats: String? {
return getParam(.AllowedFormats) as? String
}
open var context: String? {
return getParam(.Context) as? String
}
open var faceCoordinates: String? {
return getParam(.FaceCoordinates) as? String
}
open var customCoordinates: String? {
return getParam(.CustomCoordinates) as? String
}
open var eager: String? {
return getParam(.Eager) as? String
}
open var headers: String? {
return getParam(.Headers) as? String
}
open var qualityAnalysis: Bool? {
return getParam(.QualityAnalysis) as? Bool
}
open var accessibilityAnalysis: Bool? {
return getParam(.AccessibilityAnalysis) as? Bool
}
open var ocr: Bool {
return getParam(.Ocr) as? String != nil
}
open var backgroundRemoval: String? {
return getParam(.BackgroundRemoval) as? String
}
open var filenameOverride: String? {
return getParam(.FilenameOverride) as? String
}
open var assetFolder: String? {
return getParam(.AssetFolder) as? String
}
open var publicIdPrefix: String? {
return getParam(.PublicIdPrefix) as? String
}
open var useFilenameAsDisplayName: Bool? {
return getParam(.UseFilenameAsDisplayName) as? Bool
}
open var displayName: String? {
return getParam(.DisplayName) as? String
}
open var autoChaptering: Bool? {
return getParam(.AutoChaptering) as? Bool
}
open var autoTranscription: Bool? {
return getParam(.AutoTranscription) as? Bool
}
fileprivate func getParam(_ param: UploadRequestParams) -> AnyObject? {
return params[param.rawValue] as AnyObject
}
// MARK: Set Simple Params
/**
Set the identifier that is used for accessing the uploaded resource.
A randomly generated ID is assigned if not specified. The Public ID may contain a full path including folders separated by a slash (/).
- parameter publicId: The identifier to set.
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
open func setPublicId(_ publicId: String) -> Self {
setParam(UploadRequestParams.PublicId.rawValue, value: publicId)
return self
}
/**
Set the identifier prefix that is used for accessing the uploaded resource.
- parameter prefix: The prefix to prepend.
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
open func setPublicIdPrefix(_ prefix: String) -> Self {
setParam(UploadRequestParams.PublicIdPrefix.rawValue, value: prefix)
return self
}
/**
Set an optional format to convert the uploaded resource to before saving in the cloud. For example: jpg.
- parameter format: The format to convert to.
- returns: The same instance of CLDUploadRequestParams.
*/
open func setFormat(_ format: String) -> Self {
setParam(UploadRequestParams.Format.rawValue, value: format)
return self
}
/**
Allows uploading resources as 'private' or 'authenticated' instead of the default public mode.
- parameter type: The type to set.
- returns: The same instance of CLDUploadRequestParams.
*/
@objc(setTypeFromType:)
open func setType(_ type: CLDType) -> Self {
return setType(String(describing: type))
}
/**
Allows uploading resources as 'private' or 'authenticated' instead of the default public mode.
- parameter type: The type to set.
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
open func setType(_ type: String) -> Self {
setParam(UploadRequestParams.FileType.rawValue, value: type)
return self
}
/**
Set an HTTP URL to send notification to (a webhook) when the upload is completed.
- parameter notificationUrl: The URL to set.
- returns: The same instance of CLDUploadRequestParams.
*/
open func setNotificationUrl(_ notificationUrl: String) -> Self {
setParam(UploadRequestParams.NotificationUrl.rawValue, value: notificationUrl)
return self
}
/**
The default background removal behavior detects the foreground objects(s) of the image and removes the background.
You activate this behavior by setting the 'backgroundRemoval' parameter to 'cloudinary_ai' when uploading an image (Upload method)
or by using the Update method of the Admin API for existing images.
- parameter backgroundRemoval: Use 'cloudinary_ai' or 'cloudinary_ai:[object_to_keep]'
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
open func setBackgroundRemoval(_ backgroundRemoval: String) -> Self {
setParam(UploadRequestParams.BackgroundRemoval.rawValue, value: backgroundRemoval)
return self
}
/**
Setting this params will override the original asset name.
- parameter filenameOverride: New file name
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
open func setFilenameOverride(_ filenameOverride: String) -> Self {
setParam(UploadRequestParams.FilenameOverride.rawValue, value: filenameOverride)
return self
}
/**
Setting this will generate chapters file.
- parameter autoChaptering: The boolean paramter
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
open func setAutoChpatering(_ autoChaptering: Bool) -> Self {
setParam(UploadRequestParams.AutoChaptering.rawValue, value: autoChaptering)
return self
}
/**
Setting this will generate transcription file.
- parameter autoChaptering: The boolean paramter
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
open func setAutoTranscription(_ autoTranscription: Bool) -> Self {
setParam(UploadRequestParams.AutoTranscription.rawValue, value: autoTranscription)
return self
}
/**
Set an HTTP URL to send notification to (a webhook) when the generation of eager transformations is completed.
- parameter eagerNotificationUrl: The URL to set.
- returns: The same instance of CLDUploadRequestParams.
*/
open func setEagerNotificationUrl(_ eagerNotificationUrl: String) -> Self {
setParam(UploadRequestParams.EagerNotificationUrl.rawValue, value: eagerNotificationUrl)
return self
}
/**
Tells Cloudinary to upload resources from remote URLs through the given proxy. Format: http://hostname:port.
- parameter proxy: The proxy URL.
- returns: The same instance of CLDUploadRequestParams.
*/
open func setProxy(_ proxy: String) -> Self {
setParam(UploadRequestParams.Proxy.rawValue, value: proxy)
return self
}
/**
Set an optional folder name where the uploaded resource will be stored. The Public ID contains the full path of the uploaded resource, including the folder name.
- parameter folder: The folder URL.
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
open func setFolder(_ folder: String) -> Self {
setParam(UploadRequestParams.Folder.rawValue, value: folder)
return self
}
/**
Set an optional asset folder url where the uploaded resource will be stored.
- parameter assetFolder: The folder URL.
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
open func setAssetFolder(_ assetFolder: String) -> Self {
setParam(UploadRequestParams.AssetFolder.rawValue, value: assetFolder)
return self
}
/**
Set the display name of the uploaded resource.
- parameter displayName: The name of the resource.
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
open func setDisplayName(_ displayName: String) -> Self {
setParam(UploadRequestParams.DisplayName.rawValue, value: displayName)
return self
}
/**
Set A boolean parameter that determines whether to use the filename as the display name of the uploaded resource.
- parameter useFilenameAsDisplayName: The boolean parameter.
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
open func setUseFilenameAsDisplayName(_ useFilenameAsDisplayName: Bool) -> Self {
setParam(UploadRequestParams.UseFilenameAsDisplayName.rawValue, value: useFilenameAsDisplayName)
return self
}
/**
Set to manual to add the uploaded image to a queue of pending moderation images that can be moderated using the Admin API or the Cloudinary Management Console.
Set to webpurify to automatically moderate the uploaded image using the WebPurify Image Moderation add-on.
- parameter moderation: The moderation to set.
- returns: The same instance of CLDUploadRequestParams.
*/
@objc(setModerationFromModeration:)
@discardableResult
open func setModeration(_ moderation: CLDModeration) -> Self {
return setModeration(String(describing: moderation))
}
/**
Set to manual to add the uploaded image to a queue of pending moderation images that can be moderated using the [Cloudinary Management Console](https://cloudinary.com/console/media_library).
Set to webpurify to automatically moderate the uploaded image using the [WebPurify Image Moderation add-on](http://dev.cloudinary.com:3002/documentation/webpurify_image_moderation_addon).
- parameter moderation: The moderation to set.
- returns: The same instance of CLDUploadRequestParams.
*/
open func setModeration(_ moderation: String) -> Self {
setParam(UploadRequestParams.Moderation.rawValue, value: moderation)
return self
}
/**
Set to aspose to automatically convert Office documents to PDF files and other image formats using the [Aspose Document Conversion add-on](http://dev.cloudinary.com:3002/documentation/aspose_document_conversion_addon).
- parameter rawConvert: The rawConvert to set.
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
open func setRawConvert(_ rawConvert: String) -> Self {
setParam(UploadRequestParams.RawConvert.rawValue, value: rawConvert)
return self
}
/**
Set to adv_face to extract an extensive list of face attributes from a image using the [Advanced Facial Attribute Detection add-on](http://cloudinary.com/documentation/advanced_facial_attributes_detection_addon).
- parameter detection: The detection to set.
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
open func setDetection(_ detection: String) -> Self {
setParam(UploadRequestParams.Detection.rawValue, value: detection)
return self
}
/**
By setting the categorization parameter to imagga_tagging, Imagga is used to automatically classify the scenes of the uploaded image.
- parameter categorization: The categorization to set.
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
open func setCategorization(_ categorization: String) -> Self {
setParam(UploadRequestParams.Categorization.rawValue, value: categorization)
return self
}
/**
- parameter similaritySearch: The similarity search to set.
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
open func setSimilaritySearch(_ similaritySearch: String) -> Self {
setParam(UploadRequestParams.SimilaritySearch.rawValue, value: similaritySearch)
return self
}
/**
- parameter accessControl: A list of access control rules for the upload request
- returns: The same instance CLDUploadRequestParams
*/
@discardableResult
open func setAccessControl(_ accessControl: [CLDAccessControlRule]) -> Self {
setParam(UploadRequestParams.AccessControl.rawValue, value: asJsonArray(arr: accessControl))
return self
}
/**
- parameter accessControl: A json string representing a list of access control rules for upload
- returns: The same instance CLDUploadRequestParams
*/
@objc(setAccessControlWithString:)
@discardableResult
open func setAccessControl(_ accessControl: String) -> Self {
setParam(UploadRequestParams.AccessControl.rawValue, value: accessControl)
return self
}
/**
Set whether to assign tags to an image according to detected scene categories with a confidence score higher than the given value (between 0.0 and 1.0).
See [Imagga Auto Tagging](http://dev.cloudinary.com:3002/documentation/imagga_auto_tagging_addon) for more details.
- parameter autoTagging: The auto tagging parameter to use.
- returns: The same instance of CLDUploadRequestParams.
*/
@objc(setAutoTaggingWithDouble:)
@discardableResult
open func setAutoTagging(_ autoTagging: Double) -> Self {
setAutoTagging(autoTagging.cldFormat(f: ".1"))
return self
}
/**
Set whether to assign tags to an image according to detected scene categories with a confidence score higher than the given value (between 0.0 and 1.0).
See [Imagga Auto Tagging](http://dev.cloudinary.com:3002/documentation/imagga_auto_tagging_addon) for more details.
- parameter autoTagging: The auto tagging parameter to use.
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
open func setAutoTagging(_ autoTagging: String) -> Self {
setParam(UploadRequestParams.AutoTagging.rawValue, value: autoTagging)
return self
}
// MARK: Set Boolean Params
/**
Set A boolean parameter that determines whether to backup the uploaded resource. Overrides the default backup settings of your account.
- parameter backup: The boolean parameter.
- returns: The same instance of CLDExplicitRequestParams.
*/
@discardableResult
open func setBackup(_ backup: Bool) -> Self {
setBoolParam(.Backup, value: backup)
return self
}
/**
Set A boolean parameter that determines whether to use the original file name of the uploaded resource if available for the Public ID.
The file name is normalized and random characters are appended to ensure uniqueness. Default: false.
- parameter useFilename: The boolean parameter.
- returns: The same instance of CLDExplicitRequestParams.
*/
@discardableResult
open func setUseFilename(_ useFilename: Bool) -> Self {
setBoolParam(.UseFilename, value: useFilename)
return self
}
/**
When set to false, should not add random characters at the end of the filename that guarantee its uniqueness. Only relevant if use_filename is also set to true. Default: true.
- parameter uniqueFilename: The boolean parameter.
- returns: The same instance of CLDExplicitRequestParams.
*/
@discardableResult
open func setUniqueFilename(_ uniqueFilename: Bool) -> Self {
setBoolParam(.UniqueFilename, value: uniqueFilename)
return self
}
/**
Set A boolean parameter that determines whether to discard the name of the original uploaded file.
Relevant when delivering resources as attachments (setting the flag transformation parameter to attachment).
Default: false.
- parameter discardOriginalFilename: The boolean parameter.
- returns: The same instance of CLDExplicitRequestParams.
*/
@discardableResult
open func setDiscardOriginalFilename(_ discardOriginalFilename: Bool) -> Self {
setBoolParam(.DiscardOriginalFilename, value: discardOriginalFilename)
return self
}
/**
Set a boolean parameter indicating whether to perform the image generation asynchronously. default is false.
- parameter async: The boolean parameter.
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
open func setAsync(_ async: Bool) -> Self {
setBoolParam(UploadRequestParams.Async, value: async)
return self
}
/**
Set A boolean parameter that determines whether to generate the eager transformations asynchronously in the background. default is false.
- parameter eagerAsync: The boolean parameter.
- returns: The same instance of CLDExplicitRequestParams.
*/
@discardableResult
open func setEagerAsync(_ eagerAsync: Bool) -> Self {
setBoolParam(.EagerAsync, value: eagerAsync)
return self
}
/**
Set boolean parameter indicating whether or not the asset should be invalidated through the CDN. default is false.
- parameter invalidate: The boolean parameter.
- returns: The same instance of CLDExplicitRequestParams.
*/
@discardableResult
open func setInvalidate(_ invalidate: Bool) -> Self {
setBoolParam(.Invalidate, value: invalidate)
return self
}
/**
Set boolean parameter indicating whether to overwrite existing resources with the same Public ID. When set to false, return immediately if a resource with the same Public ID was found.
Default: true (when using unsigned upload, the default is false and cannot be changed to true).
- parameter overwrite: The boolean parameter.
- returns: The same instance of CLDExplicitRequestParams.
*/
@discardableResult
open func setOverwrite(_ overwrite: Bool) -> Self {
setBoolParam(.Overwrite, value: overwrite)
return self
}
/**
Set a boolean parameter indicating whether to retrieve IPTC and detailed Exif metadata of the uploaded asset. default is false.
- parameter imageMetadata: The boolean parameter.
- returns: The same instance of CLDExplicitRequestParams.
*/
@available(*, deprecated, message: "Use the new method 'setMediaMetadata' instead.")
@discardableResult
open func setImageMetadata(_ imageMetadata: Bool) -> Self {
setBoolParam(.ImageMetadata, value: imageMetadata)
return self
}
/**
Set a boolean parameter indicating whether to return IPTC, XMP, and detailed Exif metadata of the uploaded asset in the response. default is false.
- parameter mediaMetadata: The boolean parameter.
- returns: The same instance of CLDExplicitRequestParams.
*/
@discardableResult
open func setMediaMetadata(_ mediaMetadata: Bool) -> Self {
setBoolParam(.MediaMetadata, value: mediaMetadata)
return self
}
/**
Set a boolean parameter indicating whether to retrieve predominant colors & color histogram of the uploaded asset. default is false.
- parameter colors: The boolean parameter.
- returns: The same instance of CLDExplicitRequestParams.
*/
@discardableResult
open func setColors(_ colors: Bool) -> Self {
setBoolParam(.Colors, value: colors)
return self
}
/**
Set a boolean parameter indicating whether to return the perceptual hash (pHash) on the uploaded asset.
The pHash acts as a fingerprint that allows checking image similarity. default is false.
- parameter phash: The boolean parameter.
- returns: The same instance of CLDExplicitRequestParams.
*/
@discardableResult
open func setPhash(_ phash: Bool) -> Self {
setBoolParam(.Phash, value: phash)
return self
}
/**
Set a boolean parameter indicating whether to return the coordinates of faces contained in an uploaded asset (automatically detected or manually defined).
Each face is specified by the X & Y coordinates of the top left corner and the width & height of the face. default is false.
- parameter faces: The boolean parameter.
- returns: The same instance of CLDExplicitRequestParams.
*/
@discardableResult
open func setFaces(_ faces: Bool) -> Self {
setBoolParam(.Faces, value: faces)
return self
}
/**
Set a boolean parameter indicating whether to return a deletion token in the upload response.
The token can be used to delete the uploaded resource within 10 minutes using an unauthenticated API request.
Default: false.
- parameter returnDeleteToken: The boolean parameter.
- returns: The same instance of CLDExplicitRequestParams.
*/
@discardableResult
open func setReturnDeleteToken(_ returnDeleteToken: Bool) -> Self {
setBoolParam(.ReturnDeleteToken, value: returnDeleteToken)
return self
}
/**
Set a boolean parameter indicating whether to return quality analysis of the image.
Default: false.
- parameter qualityAnalysis: The boolean parameter.
- returns: The same instance of CLDExplicitRequestParams.
*/
@discardableResult
open func setQualityAnalysis(_ qualityAnalysis: Bool) -> Self {
setBoolParam(.QualityAnalysis, value: qualityAnalysis)
return self
}
/**
Set a boolean parameter indicating whether to return accessibility analysis of the image.
Default: false.
- parameter accessibilityAnalysis: The boolean parameter.
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
open func setAccessibilityAnalysis(_ accessibilityAnalysis: Bool) -> Self {
setBoolParam(.AccessibilityAnalysis, value: accessibilityAnalysis)
return self
}
/**
A setter for any one of the simple boolean parameters. This is used to normalize boolean values in requests
to be consistent across different platforms.
- parameter value: The parameter value.
- parameter param: The boolean parameter to set.
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
fileprivate func setBoolParam(_ param: UploadRequestParams, value: Bool) -> Self {
let boolNumber = NSNumber(value: value as Bool)
setParam(param.rawValue, value: boolNumber)
return self
}
// MARK: Set Params
/**
Set the upload preset.
For more information see the [documentation](http://cloudinary.com/documentation/upload_images#unsigned_upload).
- parameter uploadPreset: The upload preset from your account settings.
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
open func setUploadPreset(_ uploadPreset: String) -> Self {
setParam(UploadRequestParams.UploadPreset.rawValue, value: uploadPreset)
return self
}
/**
Set the request to be an unsigned request, using an upload preset that can be set in the Cloudinary account dashboard.
For more information see the [documentation](http://cloudinary.com/documentation/upload_images#unsigned_upload).
- parameter uploadPreset: The upload preset from your account settings.
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
@objc(setSignedWithBool:)
internal func setSigned(_ signed: Bool) -> Self {
self.signed = signed
return self
}
/**
Apply an incoming transformation as part of the upload request.
Any image transformation parameter can be specified as an option in the upload call and these transformations are applied before saving the image in the cloud.
For more information see the [documentation](http://cloudinary.com/documentation/upload_images#incoming_transformations).
- parameter transformation: The transformation to apply on the uploaded asset.
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
@objc(setTransformationFromTransformation:)
open func setTransformation(_ transformation: CLDTransformation) -> Self {
if let stringRep = transformation.asString() {
setTransformation(stringRep)
}
return self
}
/**
Apply an incoming transformation as part of the upload request.
Any image transformation parameter can be specified as an option in the upload call and these transformations are applied before saving the image in the cloud.
For more information see the [documentation](http://cloudinary.com/documentation/upload_images#incoming_transformations).
- parameter transformation: The transformation to apply on the uploaded asset.
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
open func setTransformation(_ transformation: String) -> Self {
setParam(UploadRequestParams.Transformation.rawValue, value: transformation)
return self
}
/**
Assign tags to the uploaded files.
For more information see the [documentation](http://cloudinary.com/documentation/upload_images#tagging_images).
- parameter tags: The tags to aggign to the uploaded asset.
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
@objc(setTagsWithArray:)
open func setTags(_ tags: [String]) -> Self {
return setTags(tags.joined(separator: ","))
}
/**
Assign tags to the uploaded files.
For more information see the [documentation](http://cloudinary.com/documentation/upload_images#tagging_images).
- parameter tags: The tags to aggign to the uploaded asset.
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
open func setTags(_ tags: String) -> Self {
setParam(UploadRequestParams.Tags.rawValue, value: tags)
return self
}
/**
Set An array of file formats that are allowed for uploading.
The default is any supported image format for images, and any kind of raw file.
Files of other types will be rejected.
The formats can be any combination of image types, video formats or raw file extensions.
For example: `mp4,ogv,jpg,png,pdf`
- parameter allowedFormats: The array of allowed formats.
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
@objc(setAllowedFormatsWithArray:)
open func setAllowedFormats(_ allowedFormats: [String]) -> Self {
return setAllowedFormats(allowedFormats.joined(separator: ","))
}
/**
Set An array of file formats that are allowed for uploading.
The default is any supported image format for images, and any kind of raw file.
Files of other types will be rejected.
The formats can be any combination of image types, video formats or raw file extensions.
For example: `mp4,ogv,jpg,png,pdf`
- parameter allowedFormats: The array of allowed formats.
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
open func setAllowedFormats(_ allowedFormats: String) -> Self {
setParam(UploadRequestParams.AllowedFormats.rawValue, value: allowedFormats)
return self
}
/**
Set a dictionary of the key-value pairs of general textual context metadata to attach to an uploaded resource.
The context values of uploaded files are available for fetching using the Admin API.
For example: `alt=My image❘caption=Profile image`.
- parameter context: The context dictionary.
- returns: The same instance of CLDUploadRequestParams.
*/
@objc(setContextFromDictionary:)
@discardableResult
open func setContext(_ context: [String : String]) -> Self {
return setContext(buildContextString(context))
}
/**
Set a dictionary of the key-value pairs of general textual context metadata to attach to an uploaded resource.
The context values of uploaded files are available for fetching using the Admin API.
For example: `alt=My image❘caption=Profile image`.
- parameter context: The context dictionary.
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
open func setContext(_ context: String) -> Self {
setParam(UploadRequestParams.Context.rawValue, value: context)
return self
}
/**
Sets the coordinates of faces contained in an uploaded image and overrides the automatically detected faces.
Each face is specified by the X & Y coordinates of the top left corner and the width & height of the face.
- parameter faceCoordinates: The array of `CLDCoodinate` objects, each object holds a CGRect for a single face coordinate.
- returns: The same instance of CLDUploadRequestParams.
*/
@objc(setFaceCoordinatesFromCoordinates:)
@discardableResult
open func setFaceCoordinates(_ faceCoordinates: [CLDCoordinate]) -> Self {
return setFaceCoordinates(buildCoordinatesString(faceCoordinates))
}
/**
Sets the coordinates of faces contained in an uploaded image and overrides the automatically detected faces.
Each face is specified by the X & Y coordinates of the top left corner and the width & height of the face.
- parameter faceCoordinates: The array of `CLDCoodinate` objects, each object holds a CGRect for a single face coordinate.
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
open func setFaceCoordinates(_ faceCoordinates: String) -> Self {
setParam(UploadRequestParams.FaceCoordinates.rawValue, value: faceCoordinates)
return self
}
/**
Sets the coordinates of a region contained in an uploaded image that is subsequently used for cropping uploaded images using the custom gravity mode.
The region is specified by the X & Y coordinates of the top left corner and the width & height of the region.
- parameter customCoordinates: The array of `CLDCoodinate` objects, each object holds a CGRect for a single custom coordinate.
- returns: The same instance of CLDUploadRequestParams.
*/
@objc(setCustomCoordinatesFromCoordinates:)
@discardableResult
open func setCustomCoordinates(_ customCoordinates: [CLDCoordinate]) -> Self {
return setCustomCoordinates(buildCoordinatesString(customCoordinates))
}
/**
Sets the coordinates of a region contained in an uploaded image that is subsequently used for cropping uploaded images using the custom gravity mode.
The region is specified by the X & Y coordinates of the top left corner and the width & height of the region.
- parameter customCoordinates: The array of `CLDCoodinate` objects, each object holds a CGRect for a single custom coordinate.
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
open func setCustomCoordinates(_ customCoordinates: String) -> Self {
setParam(UploadRequestParams.CustomCoordinates.rawValue, value: customCoordinates)
return self
}
/**
Set an array of transformations to create for the uploaded resource during the upload process, instead of lazily creating each of them when first accessed by your site's visitors.
- parameter eager: The array of transformations (CLDTransformation|CLDEagerTransformation)
- returns: The same instance of CLDUploadRequestParams.
*/
@objc(setEagerFromTransformationArray:)
@discardableResult
open func setEager(_ eager: [CLDTransformation]) -> Self {
return setEager(buildEagerString(eager))
}
/**
Set an array of transformations to create for the uploaded resource during the upload process, instead of lazily creating each of them when first accessed by your site's visitors.
- parameter eager: The array of transformations.
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
open func setEager(_ eager: String) -> Self {
setParam(UploadRequestParams.Eager.rawValue, value: eager)
return self
}
/**
Override quality settings for the resource
- parameter quality: The quality configuration instance, see CLDQuality.
- returns: The same instance of CLDUploadRequestParams.
*/
@objc(setQualityOverrideFromQuality:)
@discardableResult
open func setQualityOverride(_ quality: CLDTransformation.CLDQuality) -> Self {
setParam(UploadRequestParams.QualityOverride.rawValue, value: quality.description)
return self
}
/**
Override quality settings for the resource
- parameter quality: The quality as a string.
- returns: The same instance of CLDUploadRequestParams.
*/
@objc(setQualityOverrideFromString:)
@discardableResult
open func setQualityOverride(_ quality: String) -> Self {
setParam(UploadRequestParams.QualityOverride.rawValue, value: quality)
return self
}
/**
Allows modification on upload parameters by specifying custom logic with JavaScript code that is evaluated when uploading a file.
For more information see the [documentation](https://cloudinary.com/documentation/analysis_on_upload#evaluating_and_modifying_upload_parameters).
- parameter eval: The java script code to assign to the uploaded asset.
- returns: The same instance of CLDUploadRequestParams.
*/
@objc(setEvalFromString:)
@discardableResult
open func setEval(_ eval: String) -> Self {
setParam(UploadRequestParams.Eval.rawValue, value: eval)
return self
}
/**
Set an array of headers lines for returning as response HTTP headers when delivering the uploaded resource to your users.
Supported headers: `Link, X-Robots-Tag`.
For example: `X-Robots-Tag: noindex`.
- parameter headers: The array of headers.
- returns: The same instance of CLDUploadRequestParams.
*/
@objc(setHeadersWithDictionary:)
@discardableResult
open func setHeaders(_ headers: [String : String]) -> Self {
return setHeaders(buildHeadersString(headers))
}
/**
Set an array of headers lines for returning as response HTTP headers when delivering the uploaded resource to your users.
Supported headers: `Link, X-Robots-Tag`.
For example: `X-Robots-Tag: noindex`.
- parameter headers: The array of headers.
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
open func setHeaders(_ headers: String) -> Self {
setParam(UploadRequestParams.Headers.rawValue, value: headers)
return self
}
/**
Requests that Cloudinary automatically find the best breakpoints, using an array of CLDResponsiveBreakpoints objects.
- parameter responsiveBreakpoints: The array of responsive breakpoints setting.
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
open func setResponsiveBreakpoints(_ responsiveBreakpoints: [CLDResponsiveBreakpoints]) -> Self {
var responsiveBreakpointsJSON: [String] = []
for rb in responsiveBreakpoints {
responsiveBreakpointsJSON.append(rb.description)
}
super.setParam(UploadRequestParams.ResponsiveBreakpoints.rawValue, value: "[\(responsiveBreakpointsJSON.joined(separator: ","))]")
return self
}
/**
Set a boolean parameter that determines whether to retrieve detected text information in the uploaded image file. default is false.
- parameter enable: The boolean parameter.
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
open func setOcr(_ enable: Bool) -> Self {
if enable {
setOcr("adv_ocr")
} else {
setOcr(nil)
}
return self
}
/**
Set a boolean parameter that determines whether to retrieve detected text information in the uploaded image file. default is false.
- parameter enable: The boolean parameter.
- returns: The same instance of CLDUploadRequestParams.
*/
@discardableResult
@objc(setOcrString:)
open func setOcr(_ ocrString: String?) -> Self {
if let string = ocrString , !string.isEmpty {
setParam(UploadRequestParams.Ocr.rawValue, value: string)
} else {
params.removeValue(forKey: UploadRequestParams.Ocr.rawValue)
}
return self
}
// MARK: - Helpers
fileprivate enum UploadRequestParams: String {
case PublicId = "public_id"
case PublicIdPrefix = "public_id_prefix"
case Callback = "callback"
case Format = "format"
case FileType = "type"
case NotificationUrl = "notification_url"
case EagerNotificationUrl = "eager_notification_url"
case Proxy = "proxy"
case Folder = "folder"
case AssetFolder = "asset_folder"
case Moderation = "moderation"
case RawConvert = "raw_convert"
case Categorization = "categorization"
case Detection = "detection"
case SimilaritySearch = "similarity_search"
case AutoTagging = "auto_tagging"
case UploadPreset = "upload_preset"
case AccessControl = "access_control"
case QualityOverride = "quality_override"
case Eval = "eval"
case DisplayName = "display_name"
// Boolean params
case UseFilenameAsDisplayName = "use_filename_as_display_name"
case Backup = "backup"
case Exif = "exif"
case Faces = "faces"
case Colors = "colors"
case ImageMetadata = "image_metadata"
case MediaMetadata = "media_metadata"
case UseFilename = "use_filename"
case UniqueFilename = "unique_filename"
case DiscardOriginalFilename = "discard_original_filename"
case Async = "async"
case EagerAsync = "eager_async"
case Invalidate = "invalidate"
case Overwrite = "overwrite"
case Phash = "phash"
case ReturnDeleteToken = "return_delete_token"
case QualityAnalysis = "quality_analysis"
case AccessibilityAnalysis = "accessibility_analysis"
case Transformation = "transformation"
case Tags = "tags"
case AllowedFormats = "allowed_formats"
case Context = "context"
case FaceCoordinates = "face_coordinates"
case CustomCoordinates = "custom_coordinates"
case Eager = "eager"
case Headers = "headers"
case ResponsiveBreakpoints = "responsive_breakpoints"
case Ocr = "ocr"
case BackgroundRemoval = "background_removal"
case FilenameOverride = "filename_override"
case AutoChaptering = "auto_chaptering"
case AutoTranscription = "auto_transcription"
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Uploader/Requests/CLDDefaultUploadRequest.swift
================================================
//
// CLDDefaultUploadRequest.swift
//
// Copyright (c) 2017 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
A `CLDUploadRequest` object is returned when creating a data transfer request to Cloudinary, e.g. uploading a file.
It allows the options to add a progress closure that is called periodically during the transfer
and a response closure to be called once the transfer has finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
internal class CLDDefaultUploadRequest: CLDUploadRequest {
internal var networkRequest: CLDNetworkDataRequest
internal init(networkDataRequest: CLDNetworkDataRequest) {
networkRequest = networkDataRequest
}
// MARK: - Public
/**
Resume the request.
*/
open override func resume() {
networkRequest.resume()
}
/**
Suspend the request.
*/
open override func suspend() {
networkRequest.suspend()
}
/**
Cancel the request.
*/
open override func cancel() {
networkRequest.cancel()
}
//MARK: Handlers
/**
Set a response closure to be called once the request has finished.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: The same instance of CLDUploadRequest.
*/
@discardableResult
open override func responseRaw(_ completionHandler: @escaping (_ response: Any?, _ error: NSError?) -> ()) -> CLDUploadRequest {
networkRequest.response(completionHandler)
return self
}
/**
Set a response closure to be called once the request has finished.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: The same instance of CLDUploadRequest.
*/
@discardableResult
open override func response(_ completionHandler: @escaping (_ result: CLDUploadResult?, _ error: NSError?) -> ()) -> CLDUploadRequest {
responseRaw { (response, error) in
if let res = response as? [String : AnyObject] {
completionHandler(CLDUploadResult(json: res), nil)
} else {
completionHandler(nil, error ?? CLDError.generalError())
}
}
return self
}
/**
Set a progress closure that is called periodically during the data transfer.
- parameter progressBlock: The closure that is called periodically during the data transfer.
- returns: The same instance of CLDUploadRequest.
*/
@discardableResult
open override func progress(_ progress: @escaping ((Progress) -> Void)) -> CLDUploadRequest {
networkRequest.progress(progress)
return self
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Uploader/Requests/CLDUploadRequest.swift
================================================
//
// CLDUploadRequest.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
A `CLDUploadRequest` object is returned when creating a data transfer request to Cloudinary, e.g. uploading a file.
It allows the options to add a progress closure that is called periodically during the transfer
and a response closure to be called once the transfer has finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@objcMembers open class CLDUploadRequest : NSObject {
internal override init() {
}
/**
Resume the request.
*/
open func resume(){}
/**
Suspend the request.
*/
open func suspend(){}
/**
Cancel the request.
*/
open func cancel(){}
//MARK: Handlers
/**
Set a response closure to be called once the request has finished.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: The same instance of CLDUploadRequest.
*/
@discardableResult
open func responseRaw(_ completionHandler: @escaping (_ response: Any?, _ error: NSError?) -> ()) -> CLDUploadRequest{
return self
}
/**
Set a response closure to be called once the request has finished.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: The same instance of CLDUploadRequest.
*/
@discardableResult
open func response(_ completionHandler: @escaping (_ result: CLDUploadResult?, _ error: NSError?) -> ()) -> CLDUploadRequest{
return self
}
/**
Set a progress closure that is called periodically during the data transfer.
- parameter progress: The closure that is called periodically during the data transfer.
- returns: The same instance of CLDUploadRequest.
*/
@discardableResult
open func progress(_ progress: @escaping ((Progress) -> Void)) -> CLDUploadRequest{
return self
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Uploader/Requests/CLDUploadRequestWrapper.swift
================================================
//
// CLDUploadRequestWrapper.swift
//
// Copyright (c) 2017 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
A `CLDUploadRequestWrapper` object is a wrapper for instances of `CLDUploadRequest`. This is returned as a promise from
several `CLDUploader` functions, in case the actual concrete request cannot yet be created. This is also allows for multiple
concrete requests to be represented as one request. This class is used for preprocessing requests as well as uploda large requests.
*/
internal class CLDUploadRequestWrapper: CLDUploadRequest {
private var state = RequestState.started
fileprivate var requestsCount: Int!
fileprivate var totalLength: Int64!
fileprivate var requestsProgress = [CLDUploadRequest: Int64]()
fileprivate var totalProgress: Progress?
fileprivate var progressHandler: ((Progress) -> Void)?
fileprivate var requests = [CLDUploadRequest]()
fileprivate var result: CLDUploadResult?
fileprivate var error: NSError?
fileprivate let queue = DispatchQueue(label: "RequestsHandlingQueue", attributes: .concurrent)
fileprivate let closureQueue: OperationQueue = {
let operationQueue = OperationQueue()
operationQueue.name = "com.cloudinary.CLDUploadRequestWrapper"
operationQueue.maxConcurrentOperationCount = 1
operationQueue.isSuspended = true
return operationQueue
}()
/**
Once the count and total length of the request are known this method should be called.
Without this information the progress closures will not be called.
- parameter count: Number of inner requests expected to be added (or already added).
- parameter totalLength: Total length, in bytes, of the uploaded resource (can be spread across several inner request for `uploadLarge`)
*/
internal func setRequestsData(count: Int, totalLength: Int64?) {
self.totalLength = totalLength ?? 0
self.requestsCount = count
self.totalLength = totalLength ?? 0
self.totalProgress = Progress(totalUnitCount: self.totalLength)
}
/**
Add a requst to be part of the wrapping request.
- parameter request: An upload request to add - This is usually a concrete `CLDDefaultUploadRequest` to be part of this wrapper.
*/
internal func addRequest(_ request: CLDUploadRequest) {
queue.sync() {
guard self.state != RequestState.cancelled && self.state != RequestState.error else {
return
}
if (self.state == RequestState.suspended) {
request.suspend()
}
request.response() { result, error in
guard (self.state != RequestState.cancelled && self.state != RequestState.error) else {
return
}
if (error != nil) {
// single part error fails everything
self.state = RequestState.error
self.cancel()
self.requestDone(nil, error)
} else if self.requestsCount == 1 || (result?.done ?? false) {
// last part arrived successfully
self.state = RequestState.success
self.requestDone(result, nil)
}
}
request.progress() { innerProgress in
guard (self.state != RequestState.cancelled && self.state != RequestState.error) else {
return
}
self.requestsProgress[request] = innerProgress.completedUnitCount
if let totalProgress = self.totalProgress {
totalProgress.completedUnitCount = self.requestsProgress.values.reduce(0, +)
self.progressHandler?(totalProgress)
}
}
self.requests.append(request)
}
}
/**
This is used in case the request fails even without any inner upload request (e.g. when the preprocessing fails).
Once the error is set here it will be send once the completion closures are set.
- parameter error: The NSError to set.
*/
internal func setRequestError(_ error: NSError) {
state = RequestState.error
requestDone(nil, error)
}
fileprivate func requestDone(_ result: CLDUploadResult?, _ error: NSError?) {
self.error = error
self.result = result
requestsProgress.removeAll()
requests.removeAll()
closureQueue.isSuspended = false
}
// MARK: - Public
/**
Resume the request.
*/
open override func resume() {
queue.sync() {
state = RequestState.started
for request in requests {
request.resume()
}
}
}
/**
Suspend the request.
*/
open override func suspend() {
queue.sync() {
state = RequestState.suspended
for request in requests {
request.suspend()
}
}
}
/**
Cancel the request.
*/
open override func cancel() {
queue.sync() {
state = RequestState.cancelled
for request in requests {
request.cancel()
}
}
}
//MARK: Handlers
/**
Set a response closure to be called once the request has finished.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: The same instance of CldUploadRequestWrapper.
*/
@discardableResult
open override func response(_ completionHandler: @escaping (_ result: CLDUploadResult?, _ error: NSError?) -> ()) -> Self {
closureQueue.addOperation {
completionHandler(self.result, self.error)
}
return self
}
/**
Set a progress closure that is called periodically during the data transfer.
- parameter progressBlock: The closure that is called periodically during the data transfer.
- returns: The same instance of CLDUploadRequestWrapper.
*/
@discardableResult
open override func progress(_ progress: @escaping ((Progress) -> Void)) -> Self {
self.progressHandler = progress
return self
}
/**
Sets a cleanup handler that is called when the request doesn't need it's resources anymore.
This is called whether the request succeeded or not.
- Parameter handler: The closure to be called once cleanup is necessary.
- returns: The same instance of CLDUploadRequestWrapper.
*/
@discardableResult
internal func cleanupHandler(handler: @escaping (_ success: Bool) -> ()) -> Self {
closureQueue.addOperation {
handler(self.state == RequestState.success)
}
return self
}
}
fileprivate enum RequestState: Int {
case started, suspended, cancelled, error, success
}
================================================
FILE: Cloudinary/Classes/Core/Features/Uploader/Results/CLDUploadResult.swift
================================================
//
// CLDUploadResult.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
@objcMembers open class CLDUploadResult: CLDBaseResult {
// MARK: - Getters
open var type: String? {
return getParam(.urlType) as? String
}
open var eager: [CLDEagerResult]? {
guard let eagerArr = getParam(.eager) as? [[String : AnyObject]] else {
return nil
}
var eager: [CLDEagerResult] = []
for singleEager in eagerArr {
eager.append(CLDEagerResult(json: singleEager))
}
return eager
}
open var publicId: String? {
return getParam(.publicId) as? String
}
open var version: String? {
guard let version = getParam(.version) else {
return nil
}
return String(describing: version)
}
open var url: String? {
return getParam(.url) as? String
}
open var secureUrl: String? {
return getParam(.secureUrl) as? String
}
open var resourceType: String? {
return getParam(.resourceType) as? String
}
open var signature: String? {
return getParam(.signature) as? String
}
open var createdAt: String? {
return getParam(.createdAt) as? String
}
open var length: Double? {
return getParam(.length) as? Double
}
open var tags: [String]? {
return getParam(.tags) as? [String]
}
open var moderation: AnyObject? {
return getParam(.moderation)
}
open var accessControl: ([CLDAccessControlRule])? {
if let rules = getParam(.accessControl) as? [[String: String]] {
let result:[CLDAccessControlRule] = rules.compactMap{fromJson(object: $0)}
if !result.isEmpty {
return result
}
}
return nil
}
open var context: [String:[String:String]]? {
var result: [String:[String:String]]? = nil
if let c = getParam(.context) as? [String:AnyObject] {
result = [:]
for k in c.keys {
result![k] = c[k] as? [String:String]
}
}
return result;
}
open var responsiveBreakpoints: [ResponsiveBreakpointsResult]? {
var result = [ResponsiveBreakpointsResult]()
if let jsonNodes = getParam(UploadResultKey.responsiveBreakpoints) as? [[String: AnyObject]] {
for json in jsonNodes {
result.append(ResponsiveBreakpointsResult(json: json))
}
}
return result
}
// MARK: Image Params
open var width: Int? {
return getParam(.width) as? Int
}
open var height: Int? {
return getParam(.height) as? Int
}
open var format: String? {
return getParam(.format) as? String
}
open var exif: [String : String]? {
return getParam(.exif) as? [String : String]
}
open var metadata: [String : String]? {
return getParam(.metadata) as? [String : String]
}
open var faces: AnyObject? {
return getParam(.faces)
}
open var colors: AnyObject? {
return getParam(.colors)
}
open var phash: String? {
return getParam(.phash) as? String
}
open var deleteToken: String? {
return getParam(.deleteToken) as? String
}
open var originalFilename: String? {
return getParam(.originalFilename) as? String
}
open var playbackUrl: String? {
return getParam(.playbackUrl) as? String
}
open var info: CLDInfo? {
guard let info = getParam(.info) as? [String : AnyObject] else {
return nil
}
return CLDInfo(json: info)
}
@available(*, deprecated, message: "Use qualityAnalysisResult instead.")
open var qualityAnalysis: [String : AnyObject]? {
return getParam(.qualityAnalysis) as? [String : AnyObject]
}
open var qualityAnalysisResult: CLDQualityAnalysis? {
guard let qualityAnalysis = getParam(.qualityAnalysis) as? [String : AnyObject] else {
return nil
}
return CLDQualityAnalysis(json: qualityAnalysis)
}
// MARK: Video Params
open var video: CLDVideo? {
guard let video = getParam(.video) as? [String : AnyObject] else {
return nil
}
return CLDVideo(json: video)
}
open var audio: CLDAudio? {
guard let audio = getParam(.audio) as? [String : AnyObject] else {
return nil
}
return CLDAudio(json: audio)
}
open var frameRte: Double? {
return getParam(.frameRate) as? Double
}
open var bitRate: Int? {
return getParam(.bitRate) as? Int
}
open var duration: Double? {
return getParam(.duration) as? Double
}
open var done: Bool? {
return getParam(.done) as? Bool
}
open var assetFolder: String? {
return getParam(.assetFolder) as? String
}
open var displayName: String? {
return getParam(.displayName) as? String
}
open var accessibilityAnalysis: CLDAccessibilityAnalysisResult? {
guard let accessibilityAnalysis = getParam(.accessibilityAnalysis) as? [String : AnyObject] else {
return nil
}
return CLDAccessibilityAnalysisResult(json: accessibilityAnalysis)
}
open var coordinates: CLDCoordinates? {
guard let coordinates = getParam(CommonResultKeys.coordinates) as? [String : AnyObject] else {
return nil
}
return CLDCoordinates(json: coordinates)
}
// MARK: - Private Helpers
fileprivate func getParam(_ param: UploadResultKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
enum UploadResultKey: CustomStringConvertible {
case signature, done, deleteToken, responsiveBreakpoints
case video, audio, frameRate, bitRate, duration // Video
var description: String {
switch self {
case .signature: return "signature"
case .done: return "done"
case .deleteToken: return "delete_token"
case .responsiveBreakpoints: return "responsive_breakpoints"
case .video: return "video"
case .audio: return "audio"
case .frameRate: return "frame_rate"
case .bitRate: return "bit_rate"
case .duration: return "duration"
}
}
}
}
// MARK: - Video Result
@objcMembers open class CLDVideo: CLDBaseResult {
open var format: String? {
return getParam(.pixFormat) as? String
}
open var codec: String? {
return getParam(.codec) as? String
}
open var level: Int? {
return getParam(.level) as? Int
}
open var bitRate: Int? {
return getParam(.bitRate) as? Int
}
open var metadata: [String: String]? {
return getParam(.videoMetadata) as? [String: String]
}
// MARK: - Private Helpers
fileprivate func getParam(_ param: VideoKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum VideoKey: CustomStringConvertible {
case pixFormat, codec, level, bitRate, videoMetadata
var description: String {
switch self {
case .pixFormat: return "pix_format"
case .codec: return "codec"
case .level: return "level"
case .bitRate: return "bit_rate"
case .videoMetadata: return "metadata"
}
}
}
}
@objcMembers open class CLDEagerResult: CLDBaseResult {
// MARK: - Getters
open var url: String? {
return getParam(.url) as? String
}
open var secureUrl: String? {
return getParam(.secureUrl) as? String
}
}
@objcMembers open class CLDAudio: CLDBaseResult {
open var codec: String? {
return getParam(.codec) as? String
}
open var bitRate: Int? {
return getParam(.bitRate) as? Int
}
open var frequency: Int? {
return getParam(.frequency) as? Int
}
open var channels: Int? {
return getParam(.channels) as? Int
}
open var channelLayout: String? {
return getParam(.channelLayout) as? String
}
// MARK: - Private Helpers
fileprivate func getParam(_ param: AudioKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum AudioKey: CustomStringConvertible {
case codec, bitRate, frequency, channels, channelLayout
var description: String {
switch self {
case .codec: return "codec"
case .bitRate: return "bit_rate"
case .frequency: return "frequency"
case .channels: return "channels"
case .channelLayout: return "channel_layout"
}
}
}
}
@objc open class ResponsiveBreakpointsResult : CLDBaseResult {
// MARK: - Getters
open var breakpoints: [ResponsiveBreakpoint]? {
var result = [ResponsiveBreakpoint]()
if let jsonNodes = getParams(ResponsiveBreakpointResultKey.breakpoints) as? [[String: AnyObject]] {
for json in jsonNodes {
result.append(ResponsiveBreakpoint(json: json))
}
}
return result
}
open var transformation: String? {
return getParams(ResponsiveBreakpointResultKey.transformation) as? String
}
// MARK: - Private Helpers
fileprivate func getParams(_ param: ResponsiveBreakpointResultKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum ResponsiveBreakpointResultKey : CustomStringConvertible {
case breakpoints, transformation
var description: String {
switch self {
case .breakpoints: return "breakpoints"
case .transformation: return "transformation"
}
}
}
}
@objc open class ResponsiveBreakpoint : CLDBaseResult {
open var width : Int? {
return getParam(ResponsiveBreakpointKey.width) as? Int
}
open var height : Int? {
return getParam(ResponsiveBreakpointKey.height) as? Int
}
open var bytes : Int? {
return getParam(ResponsiveBreakpointKey.bytes) as? Int
}
open var url : String? {
return getParam(ResponsiveBreakpointKey.url) as? String
}
open var secureUrl : String? {
return getParam(ResponsiveBreakpointKey.secureUrl) as? String
}
fileprivate func getParam(_ param: ResponsiveBreakpointKey) -> AnyObject? {
return resultJson[String(describing: param)]
}
fileprivate enum ResponsiveBreakpointKey : CustomStringConvertible {
case bytes, width, secureUrl, url, height
var description: String {
switch self {
case .bytes: return "bytes"
case .width: return "width"
case .secureUrl: return "secure_url"
case .url: return "url"
case .height: return "height"
}
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Features/Url/CLDUrl.swift
================================================
//
// CLDUrl.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
The CLDUrl class represents a URL to a remote asset either on your Cloudinary cloud, or from another remote source.
*/
@objcMembers open class CLDUrl: NSObject {
fileprivate struct CLDUrlConsts {
static let CLD_OLD_AKAMAI_CHARED_CDN = "cloudinary-a.akamaihd.net"
static let CLD_SHARED_CDN = "res\(CLD_COM)"
static let CLD_COM = ".cloudinary.com"
}
/**
The Cloudinary public ID of the resource
*/
fileprivate var publicId: String?
/**
The current Cloudinary session configuration.
*/
fileprivate var config: CLDConfiguration!
/**
The media source of the URL. default is upload.
*/
fileprivate var type: String = String(describing: CLDType.upload)
/**
The resource type of the remote asset that the URL points to. default is image.
*/
fileprivate var resourceType: String = String(describing: CLDUrlResourceType.image)
/**
The format of the remote asset that the URL points to.
*/
fileprivate var format: String?
/**
The version of the remote asset that the URL points to.
*/
fileprivate var version: String?
/**
A suffix to the URL that points to the remote asset (private CDN only, image/upload and raw/upload only).
*/
fileprivate var suffix: String?
/**
Indicates whether to use a root path instead of a full path (image/upload only).
*/
fileprivate var useRootPath: Bool = false
/**
Indicates whether to use a shorten URL (image/upload only).
*/
fileprivate var shortenUrl: Bool = false
/**
The transformation to be apllied on the remote asset.
*/
fileprivate var transformation: CLDTransformation?
/**
Indicates whether to add '/v1/' to the URL when the public ID includes folders and a 'version' value was
not defined.
When no version is explicitly specified and the public id contains folders, a default v1 version
is added to the url. This boolean can disable that behaviour. */
fileprivate var forceVersion: Bool = true
// MARK: - Init
fileprivate override init() {
super.init()
}
internal init(configuration: CLDConfiguration) {
config = configuration
super.init()
}
// MARK: - Set Values
/**
Set the media source of the URL.
- parameter publicId: the media source to set.
- returns: the same instance of CLDUrl.
*/
open func setPublicId(_ publicId: String) -> CLDUrl {
self.publicId = publicId
return self
}
/**
Set the media source of the URL.
- parameter type: the media source to set.
- returns: the same instance of CLDUrl.
*/
@objc(setTypeFromType:)
open func setType(_ type: CLDType) -> CLDUrl {
return setType(String(describing: type))
}
/**
Set the media source of the URL.
- parameter type: the media source to set.
- returns: the same instance of CLDUrl.
*/
open func setType(_ type: String) -> CLDUrl {
self.type = type
return self
}
/**
Set the resource type of the asset the URL points to.
- parameter resourceType: the resource type to set.
- returns: the same instance of CLDUrl.
*/
@objc(setResourceTypeFromUrlResourceType:)
open func setResourceType(_ resourceType: CLDUrlResourceType) -> CLDUrl {
return setResourceType(String(describing: resourceType))
}
/**
Set the resource type of the asset the URL points to.
- parameter resourceType: the resource type to set.
- returns: the same instance of CLDUrl.
*/
open func setResourceType(_ resourceType: String) -> CLDUrl {
self.resourceType = resourceType
return self
}
/**
Set the format of the asset the URL points to.
- parameter format: the format to set.
- returns: the same instance of CLDUrl.
*/
open func setFormat(_ format: String) -> CLDUrl {
self.format = format
return self
}
/**
Set the version of the asset the URL points to.
- parameter version: the version to set.
- returns: the same instance of CLDUrl.
*/
open func setVersion(_ version: String) -> CLDUrl {
self.version = version
return self
}
/**
Set the suffix of the URL. (private CDN only, image/upload and raw/upload only).
- parameter suffix: the suffix to set.
- returns: the same instance of CLDUrl.
*/
open func setSuffix(_ suffix: String) -> CLDUrl {
self.suffix = suffix
return self
}
/**
Set whether to use a root path instead of a full path. (image/upload only).
- parameter useRootPath: Indicates whether to use a root path instead of a full path.
- returns: the same instance of CLDUrl.
*/
open func setUseRootPath(_ useRootPath: Bool) -> CLDUrl {
self.useRootPath = useRootPath
return self
}
/**
Set whether to use a shorten URL. (image/upload only).
- parameter shortenUrl: Indicates whether to use a shorten URL.
- returns: the same instance of CLDUrl.
*/
open func setShortenUrl(_ shortenUrl: Bool) -> CLDUrl {
self.shortenUrl = shortenUrl
return self
}
/**
Indicates whether to add '/v1/' to the URL when the public ID includes folders and a 'version' value was
not defined.
When no version is explicitly specified and the public id contains folders, a default v1 version
is added to the url. Set this boolean as false to prevent that behaviour.
- parameter forceVersion: Indicates whether to add the version.
- returns: the same instance of CLDUrl.
*/
open func setForceVersion(_ forceVersion: Bool) -> CLDUrl {
self.forceVersion = forceVersion
return self
}
/**
Set the transformation to be apllied on the remote asset.
- parameter transformation: The transformation to be apllied on the remote asset.
- returns: the same instance of CLDUrl.
*/
@discardableResult
open func setTransformation(_ transformation: CLDTransformation) -> CLDUrl {
self.transformation = transformation
return self
}
// MARK: - Actions
/**
Generate a string URL representation of the CLDUrl.
- parameter signUrl: Indicates whether to generate a signature out of the API secret and add it to the generated URL. Default is false.
- returns: The generated string URL representation.
*/
open func generate(signUrl: Bool = false) -> String? {
if let publicId = self.publicId {
return generate(publicId, signUrl: signUrl)
} else {
return nil
}
}
/**
Generate a string URL representation of the CLDUrl.
- parameter publicId: The remote asset's name (e.g. the public id of an uploaded image).
- parameter signUrl: Indicates whether to generate a signature out of the API secret and add it to the generated URL. Default is false.
- returns: The generated string URL representation.
*/
open func generate(_ publicId: String, signUrl: Bool = false) -> String? {
if signUrl && config.apiSecret == nil {
printLog(.error, text: "Must supply api_secret for signing urls")
return nil
}
var sourceName = publicId
var resourceType = self.resourceType
var type = self.type
var version = self.version ?? String()
var format = self.format
let preloadedComponentsMatch = GenerateUrlRegex.preloadedRegex.matches(in: sourceName, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, sourceName.count))
if preloadedComponentsMatch.count > 0 {
if let preloadedComponents = preloadedComponentsMatch.first {
resourceType = (sourceName as NSString).substring(with: preloadedComponents.rangeAt(1))
type = (sourceName as NSString).substring(with: preloadedComponents.rangeAt(2))
version = (sourceName as NSString).substring(with: preloadedComponents.rangeAt(3))
sourceName = (sourceName as NSString).substring(with: preloadedComponents.rangeAt(4))
}
}
let transformation = self.transformation ?? CLDTransformation()
if let unwrappedFormat = format , !unwrappedFormat.isEmpty && type == String(describing: CLDType.fetch) {
transformation.setFetchFormat(unwrappedFormat)
format = nil
}
guard let transformationStr = transformation.asString() else {
printLog(.error, text: "An invalid transformation was added.")
return nil
}
if forceVersion &&
version.isEmpty &&
sourceName.contains("/") &&
sourceName.range(of: "^v[0-9]+/.*", options: NSString.CompareOptions.regularExpression, range: nil, locale: nil) == nil &&
sourceName.range(of: "^https?:/.*", options: [NSString.CompareOptions.regularExpression, NSString.CompareOptions.caseInsensitive], range: nil, locale: nil) == nil
{
version = "1"
}
if !version.isEmpty {
version = "v\(version)"
}
var toSign: String = String()
if !transformationStr.isEmpty {
toSign.append("\(transformationStr)/")
}
if sourceName.range(of: "^https?:/.*", options: [NSString.CompareOptions.regularExpression, NSString.CompareOptions.caseInsensitive], range: nil, locale: nil) != nil {
if let encBasename = sourceName.cldSmartEncodeUrl() {
toSign.append(encBasename)
sourceName = encBasename
}
}
else {
if let encBasename = sourceName.removingPercentEncoding?.cldSmartEncodeUrl() {
toSign.append(encBasename)
sourceName = encBasename
}
if let suffix = suffix , !suffix.isEmpty {
if suffix.range(of: "[/\\.]", options: NSString.CompareOptions.regularExpression, range: nil, locale: nil) != nil {
printLog(.error, text: "URL Suffix should not include . or /")
return nil
}
sourceName = "\(sourceName)/\(suffix)"
}
if let unwrappedFormat = format {
sourceName = "\(sourceName).\(unwrappedFormat)"
toSign.append(".\(unwrappedFormat)")
}
}
let prefix = finalizePrefix(sourceName)
guard let resourceTypeAndType = finalizeResourceTypeAndType(resourceType, type: type)
else {
return nil
}
var signature = String()
if signUrl {
if let apiSecret = config.apiSecret {
toSign.append(apiSecret)
}
if config.longUrlSignature {
// long url forces sha256 algorithm
let encoded = toSign.sha256_base64()
signature = "s--\(encoded[0...31])--"
}
else {
switch config.signatureAlgorithm {
case .sha1:
let encoded = toSign.sha1_base64()
signature = "s--\(encoded[0...7])--"
case .sha256:
let encoded = toSign.sha256_base64()
signature = "s--\(encoded[0...7])--"
}
}
}
var url = [prefix, resourceTypeAndType, signature, transformationStr, version , sourceName].joined(separator: "/").replacingOccurrences(of: " ", with: "%20")
//Analytics
if let urlObject = URL(string: url) {
if(config.analytics && urlObject.query == nil) {
let analytics = "_a=" + config.analyticsObject.generateAnalyticsSignature()
url = url + "?" + analytics
}
}
let regex = try! NSRegularExpression(pattern: "([^:])\\/+", options: NSRegularExpression.Options.caseInsensitive)
return regex.stringByReplacingMatches(in: url, options: [], range: NSMakeRange(0, url.count), withTemplate: "$1/")
}
fileprivate struct GenerateUrlRegex {
fileprivate static let preloadedRegexString = "^([^/]+)/([^/]+)/v([0-9]+)/([^#]+)(#[0-9a-f]+)?$"
static let preloadedRegex: NSRegularExpression = {
return try! NSRegularExpression(pattern:preloadedRegexString, options: NSRegularExpression.Options.caseInsensitive)
}()
}
// MARK: - Helpers
fileprivate func finalizePrefix(_ basename: String) -> String {
var prefix = String()
if config.secure {
var secureDistribution = String()
if let secureDist = config.secureDistribution , (secureDist != CLDUrlConsts.CLD_OLD_AKAMAI_CHARED_CDN && !secureDist.isEmpty) {
secureDistribution = secureDist
}
else {
secureDistribution = config.privateCdn ? "\(config.cloudName!)-\(CLDUrlConsts.CLD_SHARED_CDN)" : CLDUrlConsts.CLD_SHARED_CDN
}
if config.secureCdnSubdomain {
let sharedDomain = "res-\(basename.toCRC32() % 5 + 1)\(CLDUrlConsts.CLD_COM)"
secureDistribution = secureDistribution.replacingOccurrences(of: CLDUrlConsts.CLD_SHARED_CDN, with: sharedDomain)
}
prefix = "https://\(secureDistribution)"
}
else if let cname = config.cname {
prefix = "http://"
if config.cdnSubdomain {
prefix += "a\(basename.toCRC32() % 5 + 1)."
}
prefix += cname
}
else {
prefix = "http://"
if config.privateCdn {
prefix += "\(config.cloudName!)-"
}
prefix += "res"
if config.cdnSubdomain {
prefix += "-\(basename.toCRC32() % 5 + 1)"
}
prefix += CLDUrlConsts.CLD_COM
}
if !config.privateCdn {
prefix += "/\(config.cloudName!)"
}
return prefix
}
fileprivate func finalizeResourceTypeAndType(_ resourceType: String, type: String) -> String? {
if !config.privateCdn, let urlSuffix = suffix , !urlSuffix.isEmpty {
printLog(.error, text: "URL Suffix only supported in private CDN")
return nil
}
var resourceTypeAndType = "\(resourceType)/\(type)"
if let urlSuffix = suffix , !urlSuffix.isEmpty {
if resourceTypeAndType == "\(String(describing: CLDUrlResourceType.image))/\(String(describing: CLDType.upload))" {
resourceTypeAndType = "images"
}
else if resourceTypeAndType == "\(String(describing: CLDUrlResourceType.image))/\(String(describing: CLDType.private))" {
resourceTypeAndType = "private_images"
}
else if resourceTypeAndType == "\(String(describing: CLDUrlResourceType.raw))/\(String(describing: CLDType.upload))" {
resourceTypeAndType = "files"
}
else {
printLog(.error, text: "URL Suffix only supported for image/upload, image/private and raw/upload")
return nil
}
}
if useRootPath {
if resourceTypeAndType == "\(String(describing: CLDUrlResourceType.image))/\(String(describing: CLDType.upload))" || resourceTypeAndType == "images" {
resourceTypeAndType = String()
}
else {
printLog(.error, text: "Root path only supported for image/upload")
return nil
}
}
if shortenUrl && resourceTypeAndType == "\(String(describing: CLDUrlResourceType.image))/\(String(describing: CLDType.upload))" {
resourceTypeAndType = "iu"
}
return resourceTypeAndType
}
}
================================================
FILE: Cloudinary/Classes/Core/Network/Adapter/CLDNetworkAdapter.swift
================================================
//
// NetworkAdapter.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
// MARK: CLDNetworkAdapter
/**
A protocol defining the way the SDK works with its network layer, allowing the implementation of a custom network layer.
By default the Cloudinary SDK uses CLDNetworkDelegate() as its network adapter,
to use a custom network adapter you must implement the `CLDNetworkAdapter` protocol and send it when creating the CLDCloudinary instance.
*/
@objc public protocol CLDNetworkAdapter {
// MARK: Actions
/**
Create a network request for the given URL, with the specified headers and body parameters.
- parameter url: The URL to make the request to.
- parameter headers: A dictionary of the headers to set to the request.
- parameter parameters: A dictionary of the parameters to set to the request.
- returns: An instance implementing the protocol `CLDNetworkDataRequest`,
allowing the options to add response closure to be called once the request has finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
func cloudinaryRequest(_ url: String, headers: [String : String], parameters: [String : Any]) -> CLDNetworkDataRequest
/**
Create a network upload request for the given URL, with the specified headers, body parameters and data.
- parameter url: The URL to make the request to.
- parameter headers: A dictionary of the headers to set to the request.
- parameter parameters: A dictionary of the parameters to set to the request.
- parameter data: Can receive either the data to upload or an NSURL to either a local or a remote file to upload.
- returns: An instance implementing the protocol `CLDNetworkDataRequest`,
allowing the options to add a progress closure that is called periodically during the upload
and a response closure to be called once the upload is finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
func uploadToCloudinary(_ url: String, headers: [String : String], parameters: [String : Any], data: Any) -> CLDNetworkDataRequest
/**
Download a file from the specified url.
- parameter url: The URL of the file to download.
- returns: An instance implementing the protocol `CLDNetworkDataRequest`,
allowing the option to set a closure returning the fetched image when its available.
The protocol also allows the options to add a progress closure that is called periodically during the download,
as well as cancelling the request.
*/
func downloadFromCloudinary(_ url: String) -> CLDNetworkDataRequest
// MARK: Setters
/**
Set a completion handler provided by the UIApplicationDelegate `application:handleEventsForBackgroundURLSession:completionHandler:` method.
The handler will be called automaticaly once the session finishes its events for background URL session.
default is `nil`.
*/
func setBackgroundCompletionHandler(_ newValue: (() -> ())?)
/**
The maximum number of queued downloads that can execute at the same time.
The default value of this property is NSOperationQueueDefaultMaxConcurrentOperationCount.
*/
func setMaxConcurrentDownloads(_ maxConcurrentDownloads: Int)
// MARK: Getters
/**
Get the completion handler to be called automaticaly once the session finishes its events for background URL session.
default is `nil`.
*/
func getBackgroundCompletionHandler() -> (() -> ())?
}
// MARK: - CLDNetworkRequest
/**
The `CLDNetworkRequest` protocol is returned when creating a network request using one of Cloudinary's API calls.
It allows the options to add a response closure to be called once the request has finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@objc public protocol CLDNetworkRequest {
// MARK: Actions
/**
Resume the request.
*/
func resume()
/**
Suspend the request.
*/
func suspend()
/**
Cancel the request.
*/
func cancel()
}
// MARK: - CLDNetworkDataRequest
/**
The `CLDNetworkDataRequest` protocol is returned when creating a data transfer request to Cloudinary, e.g. uploading a file.
It allows the options to add a progress closure that is called periodically during the transfer
and a response closure to be called once the transfer has finished,
as well as performing actions on the request, such as cancelling, suspending or resuming it.
*/
@objc public protocol CLDNetworkDataRequest: CLDNetworkRequest {
//MARK: Handlers
/**
Set a progress closure that is called periodically during the data transfer.
- parameter progress: The closure that is called periodically during the data transfer.
- returns: The same instance of CLDNetworkDataRequest.
*/
@discardableResult
func progress(_ progress: ((Progress) -> Void)?) -> CLDNetworkDataRequest
/**
Set a response closure to be called once the request has finished.
- parameter completionHandler: The closure to be called once the request has finished, holding either the response object or the error.
- returns: The same instance of CLDNetworkRequest.
*/
@discardableResult
func response(_ completionHandler: ((_ response: Any?, _ error: NSError?) -> ())?) -> CLDNetworkRequest
}
// MARK: - CLDFetchImageRequest
/**
The `CLDFetchImageRequest` protocol is returned when creating a fetch image request.
It allows the option to set a closure returning the fetched image when its available.
The protocol also allows the options to add a progress closure that is called periodically during the download,
as well as cancelling the request.
*/
@objc public protocol CLDFetchImageRequest: CLDNetworkDataRequest {
//MARK: Actions
/**
Set a response closure to be called once the fetch image request has finished.
- parameter completionHandler: The closure to be called once the request has finished, holding either the retrieved UIImage or the error.
- returns: The same instance of CLDFetchImageRequest.
*/
@discardableResult
func responseImage(_ completionHandler: CLDCompletionHandler?) -> CLDFetchImageRequest
}
/**
The `CLDFetchAssetRequest` protocol is returned when creating a fetch asset request.
It allows the option to set a closure returning the fetched asset when its available.
The protocol also allows the options to add a progress closure that is called periodically during the download,
as well as cancelling the request.
*/
@objc public protocol CLDFetchAssetRequest: CLDNetworkDataRequest {
//MARK: Actions
/**
Set a response closure to be called once the fetch asset request has finished.
- parameter completionHandler: The closure to be called once the request has finished, holding either the retrieved Data or the error.
- returns: The same instance of CLDFetchAssetRequest.
*/
@discardableResult
func responseAsset(_ completionHandler: CLDAssetCompletionHandler?) -> CLDFetchAssetRequest
}
================================================
FILE: Cloudinary/Classes/Core/Network/CLDDefaultNetworkAdapter.swift
================================================
//
// CLDDefaultNetworkAdapter.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
internal class CLDDefaultNetworkAdapter: NSObject, CLDNetworkAdapter {
init(configuration: URLSessionConfiguration? = nil) {
if let configuration = configuration {
manager = CLDNSessionManager(configuration: configuration)
} else {
let configuration: URLSessionConfiguration = {
let configuration = URLSessionConfiguration.background(withIdentifier: SessionProperties.networkIdentifier)
configuration.httpAdditionalHeaders = CLDNSessionManager.defaultHTTPHeaders
return configuration
}()
manager = CLDNSessionManager(configuration: configuration)
}
manager.startRequestsImmediately = false
}
fileprivate let manager: CLDNSessionManager
fileprivate let downloadQueue: OperationQueue = {
let operationQueue = OperationQueue()
operationQueue.name = "com.cloudinary.CLDDefaultNetworkAdapter"
return operationQueue
}()
internal static let sharedAdapter = CLDDefaultNetworkAdapter()
private struct SessionProperties {
static let networkIdentifier: String = Bundle.main.bundleIdentifier ?? "" + ".cloudinarySDKbackgroundSession"
static let downloadIdentifier: String = "" + ".cloudinarySDKbackgroundDownloadSession"
}
// MARK: Features
internal func cloudinaryRequest(_ url: String, headers: [String: String], parameters: [String: Any]) -> CLDNetworkDataRequest {
let req: CLDNDataRequest = manager.request(url, method: .post, parameters: parameters, headers: headers)
req.resume()
return CLDNetworkDataRequestImpl(request: req)
}
internal func uploadToCloudinary(_ url: String, headers: [String: String], parameters: [String: Any], data: Any) -> CLDNetworkDataRequest {
var parameters = parameters
let timeout = parameters.removeValue(forKey: "timeout") as? NSNumber
let asyncUploadRequest = CLDAsyncNetworkUploadRequest()
manager.upload(multipartFormData: { (multipartFormData) in
if let data = data as? Data {
multipartFormData.append(data, withName: "file", fileName: "file", mimeType: "application/octet-stream")
} else if let url = data as? URL {
if url.absoluteString.cldIsRemoteUrl() {
if let urlAsData = url.absoluteString.data(using: String.Encoding.utf8) {
multipartFormData.append(urlAsData, withName: "file")
}
} else {
multipartFormData.append(url, withName: "file", fileName: url.lastPathComponent, mimeType: "application/octet-stream")
}
}
for key in parameters.keys {
if let value = parameters[key], value is [String] {
if let valueArr = value as? [String] {
for paramValue in valueArr {
if let valueData = paramValue.data(using: String.Encoding.utf8) {
multipartFormData.append(valueData, withName: key + "[]")
}
}
}
} else if let value = parameters[key] as? String {
if value.isEmpty {
continue
}
if let valueData = value.data(using: String.Encoding.utf8) {
multipartFormData.append(valueData, withName: key)
}
} else if let value = parameters[key] as? Bool {
if let valueData = String(describing: NSNumber(value: value)).data(using: String.Encoding.utf8) {
multipartFormData.append(valueData, withName: key)
}
}
}
}, usingThreshold: UInt64(), to: url, method: .post, headers: headers, timeout: timeout) { (encodingResult) in
switch encodingResult {
case .success(let upload, _, _):
upload.resume()
let uploadRequest = CLDNetworkUploadRequest(request: upload)
asyncUploadRequest.networkDataRequest = uploadRequest
case .failure(let encodingError):
asyncUploadRequest.networkDataRequest = CLDRequestError(error: encodingError)
}
}
return asyncUploadRequest
}
internal func downloadFromCloudinary(_ url: String) -> CLDNetworkDataRequest {
let req = manager.request(url)
downloadQueue.addOperation { () -> () in
req.resume()
}
return CLDNetworkDownloadRequest(request: req)
}
// MARK: - Setters
internal func setBackgroundCompletionHandler(_ newValue: (() -> ())?) {
manager.backgroundCompletionHandler = newValue
}
internal func setMaxConcurrentDownloads(_ maxConcurrentDownloads: Int) {
downloadQueue.maxConcurrentOperationCount = maxConcurrentDownloads
}
// MARK: - Getters
internal func getBackgroundCompletionHandler() -> (() -> ())? {
return manager.backgroundCompletionHandler
}
}
================================================
FILE: Cloudinary/Classes/Core/Network/CLDNetworkCoordinator.swift
================================================
//
// NetworkCoordinator.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import UIKit
internal class CLDNetworkCoordinator: NSObject {
static let DEFAULT_VERSION = "5.2.5"
fileprivate struct CLDNetworkCoordinatorConsts {
static let BASE_CLOUDINARY_URL = "https://api.cloudinary.com"
static let API_KEY = "api_key"
}
fileprivate var config: CLDConfiguration
fileprivate var networkAdapter: CLDNetworkAdapter
fileprivate var extraHeaders: [String: String]?
// MARK: - Init
init(configuration: CLDConfiguration, networkAdapter: CLDNetworkAdapter = CLDDefaultNetworkAdapter.sharedAdapter) {
config = configuration
self.networkAdapter = networkAdapter
}
init(configuration: CLDConfiguration, sessionConfiguration: URLSessionConfiguration) {
config = configuration
self.networkAdapter = CLDDefaultNetworkAdapter(configuration: sessionConfiguration)
}
// MARK: - Actions
internal func callAction(_ action: CLDAPIAction, params: CLDRequestParams) -> CLDNetworkDataRequest {
let url = getUrl(action, resourceType: params.resourceType)
let headers = getHeaders()
params.setTimeout(from: config)
let requestParams = getSignedRequestParams(params)
return networkAdapter.cloudinaryRequest(url, headers: headers, parameters: requestParams)
}
internal func upload(_ data: Any, params: CLDUploadRequestParams, extraHeaders: [String:String]?=[:]) -> CLDNetworkDataRequest {
let url = getUrl(.Upload, resourceType: params.resourceType)
params.setTimeout(from: config)
let requestParams = params.signed ? getSignedRequestParams(params) : params.params
var headers :[String : String] = getHeaders()
headers.cldMerge(self.extraHeaders) //User's configured extra headers
headers.cldMerge(extraHeaders)
return networkAdapter.uploadToCloudinary(url, headers: headers, parameters: requestParams, data: data)
}
internal func download(_ url: String) -> CLDNetworkDataRequest {
return networkAdapter.downloadFromCloudinary(url)
}
// MARK: - Helpers
fileprivate func getSignedRequestParams(_ requestParams: CLDRequestParams) -> [String : Any] {
var params: [String : Any] = requestParams.params
guard let apiKey = requestParams.apiKey ?? config.apiKey else {
printLog(.error, text: "Must supply api key for a signed request")
return params
}
if let signatureObj = requestParams.signature {
params[CLDSignature.SignatureParam.Signature.rawValue] = signatureObj.signature
params[CLDSignature.SignatureParam.Timestamp.rawValue] = cldParamValueAsString(value: signatureObj.timestamp)
}
else if let apiSecret = config.apiSecret {
let timestamp = Int(Date().timeIntervalSince1970)
params[CLDSignature.SignatureParam.Timestamp.rawValue] = cldParamValueAsString(value: timestamp)
let signature = cloudinarySignParamsUsingSecret(getSignParams(from: params), cloudinaryApiSecret: apiSecret, signatureVersion: config.signatureVesion)
params[CLDSignature.SignatureParam.Signature.rawValue] = signature
}
else {
printLog(.error, text: "Must supply api secret for a signed request")
}
params[CLDNetworkCoordinatorConsts.API_KEY] = apiKey
return params
}
fileprivate func getSignParams(from params: [String : Any]) -> [String : Any] {
var signatureParams = params
signatureParams.removeValue(forKey: CLDConfiguration.ConfigParam.Timeout.description)
return signatureParams
}
fileprivate func getUrl(_ action: CLDAPIAction, resourceType: String?) -> String {
var urlComponents: [String] = []
let prefix = config.uploadPrefix ?? CLDNetworkCoordinatorConsts.BASE_CLOUDINARY_URL
urlComponents.append(prefix)
urlComponents.append("v1_1")
urlComponents.append(config.cloudName)
if action != CLDAPIAction.DeleteByToken {
let rescourceType = resourceType ?? String(describing: CLDUrlResourceType.image)
urlComponents.append(rescourceType)
}
urlComponents.append(action.rawValue)
return urlComponents.joined(separator: "/")
}
fileprivate func getHeaders() -> [String : String] {
var headers: [String : String] = [:]
var userAgent: String
if let userPlatform = config.userPlatform {
userAgent = "\(userPlatform.platform)/\(userPlatform.version) CloudinaryiOS/\(CLDNetworkCoordinator.getVersion())"
}
else {
userAgent = "CloudinaryiOS/\(CLDNetworkCoordinator.getVersion())"
}
userAgent += " (\(UIDevice.current.model); \(UIDevice.current.systemName) \(UIDevice.current.systemVersion))"
headers["User-Agent"] = userAgent
headers["X-Requested-With"] = "XMLHttpRequest"
return headers
}
static func getVersion() -> String {
let version = DEFAULT_VERSION
return version
}
internal enum CLDAPIAction: String, CustomStringConvertible {
case Upload = "upload"
case Rename = "rename"
case Destroy = "destroy"
case Tags = "tags"
case Explicit = "explicit"
case Explode = "explode"
case GenerateArchive = "generate_archive"
case GenerateSprite = "sprite"
case Multi = "multi"
case GenerateText = "text"
case DeleteByToken = "delete_by_token"
var description: String {
switch self {
case .Upload: return "upload"
case .Rename: return "rename"
case .Destroy: return "destroy"
case .Tags: return "tags"
case .Explicit: return "explicit"
case .Explode: return "explode"
case .GenerateArchive: return "generate_archive"
case .GenerateSprite: return "sprite"
case .Multi: return "multi"
case .GenerateText: return "text"
case .DeleteByToken: return "delete_by_token"
}
}
}
// MARK: - Public
internal func setBackgroundCompletionHandler(_ newValue: (() -> ())?) {
networkAdapter.setBackgroundCompletionHandler(newValue)
}
internal func setMaxConcurrentDownloads(_ maxConcurrentDownloads: Int) {
networkAdapter.setMaxConcurrentDownloads(maxConcurrentDownloads)
}
internal func setExtraHeaders(_ extraHeaders: [String: String]) {
self.extraHeaders = extraHeaders
}
internal func getExtraHeaders() -> [String: String]? {
return extraHeaders
}
}
class CLDDownloadCoordinator: CLDNetworkCoordinator {
static var enableCache = true
static var urlCache = URLCache.init(memoryCapacity: Defines.defaultMaxMemoryCapacity, diskCapacity: Defines.defaultMaxDiskCapacity, diskPath: "")
init(configuration: CLDConfiguration) {
let downloadConfiguration = URLSessionConfiguration.default
downloadConfiguration.httpAdditionalHeaders = CLDNSessionManager.defaultHTTPHeaders
downloadConfiguration.urlCache = CLDDownloadCoordinator.urlCache
let downloadAdapter = CLDDefaultNetworkAdapter(configuration: downloadConfiguration)
super.init(configuration: configuration, networkAdapter: downloadAdapter)
}
override init(configuration: CLDConfiguration, sessionConfiguration: URLSessionConfiguration) {
super.init(configuration: configuration, sessionConfiguration: sessionConfiguration)
}
override init(configuration: CLDConfiguration, networkAdapter: CLDNetworkAdapter) {
super.init(configuration: configuration, networkAdapter: networkAdapter)
}
}
internal struct Defines {
static let defaultMaxMemoryCapacity = 30 * 1024 * 1024 // 30 MB
static let defaultMaxDiskCapacity = 150 * 1024 * 1024 // 150 MB
}
================================================
FILE: Cloudinary/Classes/Core/Network/NetworkRequest/CLDAsyncNetworkUploadRequest.swift
================================================
//
// CLDAsyncNetworkUploadRequest.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
internal class CLDAsyncNetworkUploadRequest: CLDNetworkDataRequest {
// Once a value is set it should not be overridden
internal var networkDataRequest: CLDNetworkDataRequest? {
didSet {
if networkDataRequest != nil {
closureQueue.isSuspended = false
}
}
}
fileprivate let closureQueue: OperationQueue = {
let operationQueue = OperationQueue()
operationQueue.name = "com.cloudinary.CLDAsyncNetworkUploadRequest"
operationQueue.maxConcurrentOperationCount = 1
operationQueue.isSuspended = true
return operationQueue
}()
// MARK: - CLDNetworkDataRequest
func resume() {
closureQueue.addOperation {
self.networkDataRequest?.resume()
}
}
func suspend() {
closureQueue.addOperation {
self.networkDataRequest?.suspend()
}
}
func cancel() {
closureQueue.addOperation {
self.networkDataRequest?.cancel()
}
}
func response(_ completionHandler: ((_ response: Any?, _ error: NSError?) -> ())?) -> CLDNetworkRequest {
closureQueue.addOperation {
self.networkDataRequest?.response(completionHandler)
}
return self
}
func progress(_ progress: ((Progress) -> Void)?) -> CLDNetworkDataRequest {
closureQueue.addOperation {
self.networkDataRequest?.progress(progress)
}
return self
}
}
================================================
FILE: Cloudinary/Classes/Core/Network/NetworkRequest/CLDErrorRequest.swift
================================================
//
// CLDRequestError.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import Foundation
/// Represents a failure to create the request
internal class CLDRequestError: CLDFetchImageRequest {
var error: Error
// MARK: - Init
init(error: Error) {
self.error = error
}
// MARK: - CLDNetworkRequest
func resume() {
}
func suspend() {
}
func cancel() {
}
// MARK: - CLDNetworkDataRequest
func progress(_ progress: ((Progress) -> Void)?) -> CLDNetworkDataRequest {
return self
}
func response(_ completionHandler: ((_ response: Any?, _ error: NSError?) -> ())?) -> CLDNetworkRequest {
completionHandler?(nil, error as NSError?)
return self
}
// MARK: - CLDFetchImageRequest
func responseImage(_ completionHandler: CLDCompletionHandler?) -> CLDFetchImageRequest {
completionHandler?(nil, error as NSError?)
return self
}
}
================================================
FILE: Cloudinary/Classes/Core/Network/NetworkRequest/CLDGenericNetworkRequest.swift
================================================
//
// CLDGenericNetworkRequest.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
internal class CLDGenericNetworkRequest: NSObject, CLDNetworkRequest {
internal let request: T
// MARK: - Init
internal init(request: T) {
self.request = request
}
//MARK: - State
/**
Resume the current request.
*/
func resume() {
request.resume()
}
/**
Suspend the current request.
*/
func suspend() {
request.suspend()
}
/**
Cancel the current request.
*/
func cancel() {
request.cancel()
}
}
================================================
FILE: Cloudinary/Classes/Core/Network/NetworkRequest/CLDNetworkDataRequestImpl.swift
================================================
//
// CLDDataNetworkRequest.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
internal class CLDNetworkDataRequestImpl: CLDGenericNetworkRequest, CLDNetworkDataRequest {
@discardableResult
public func progress(_ progress: ((Progress) -> Void)?) -> CLDNetworkDataRequest {
if let progress = progress{
request.downloadProgress(closure: progress)
}
return self
}
//MARK: - Handlers
func response(_ completionHandler: ((_ response: Any?, _ error: NSError?) -> ())?) -> CLDNetworkRequest {
request.responseJSON { response in
if let value = response.result.value as? [String : AnyObject] {
if var error = value["error"] as? [String : AnyObject] {
let code = response.response?.statusCode ?? CLDError.CloudinaryErrorCode.generalErrorCode.rawValue
error["statusCode"] = code as AnyObject
let err = CLDError.error(code: code, userInfo: error)
completionHandler?(nil, err)
}
else {
completionHandler?(value as AnyObject?, nil)
}
}
else if let err = response.result.error {
let error = err as NSError
completionHandler?(nil, error)
}
else {
completionHandler?(nil, CLDError.generalError())
}
}
return self
}
}
================================================
FILE: Cloudinary/Classes/Core/Network/NetworkRequest/CLDNetworkDownloadRequest.swift
================================================
//
// CLDNetworkDownloadRequest.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
internal class CLDNetworkDownloadRequest: CLDNetworkDataRequestImpl, CLDFetchImageRequest {
//MARK: - Handlers
func responseImage(_ completionHandler: CLDCompletionHandler?) -> CLDFetchImageRequest {
return responseData { (responseData, error, statusCode) -> () in
if let data = responseData {
if let image = data.cldToUIImageThreadSafe() {
completionHandler?(image, nil)
}
else {
let error = CLDError.error(code: .failedCreatingImageFromData, message: "Failed creating an image from the received data.", userInfo: ["statusCode": statusCode])
completionHandler?(nil, error)
}
}
else if let err = error {
completionHandler?(nil, err)
}
else {
completionHandler?(nil, CLDError.generalError(userInfo: ["statusCode": statusCode]))
}
} as! CLDFetchImageRequest
}
// MARK: - Private
@discardableResult
internal func responseData(_ completionHandler: ((_ responseData: Data?, _ error: NSError?, _ httpCode: Int?) -> ())?) -> CLDNetworkDataRequest {
request.responseData { response in
let statusCode = response.response?.statusCode
if let downloadedData = response.result.value {
if let statusCode = statusCode, self.isAcceptableCode(code: statusCode) {
if CLDDownloadCoordinator.enableCache,
let result = response.response,
let data = response.data,
let request = self.request.request,
CLDDownloadCoordinator.urlCache.cachedResponse(for: request) == nil {
let cachedData = CachedURLResponse(response: result, data: data)
CLDDownloadCoordinator.urlCache.storeCachedResponse(cachedData, for: request)
}
completionHandler?(downloadedData, nil, statusCode)
}
else {
let statusCodeError = CLDError.error(code: .unacceptableStatusCode, message: "request error - unacceptable statusCode - \(statusCode)", userInfo: ["statusCode": statusCode])
completionHandler?(downloadedData, statusCodeError, statusCode)
}
}
else if let err = response.result.error {
let error = err as NSError
completionHandler?(nil, error, statusCode)
}
else {
completionHandler?(nil, CLDError.generalError(userInfo: ["statusCode": statusCode]), statusCode)
}
}
return self
}
func isAcceptableCode(code: Int) -> Bool {
self.request.acceptableStatusCodes.contains(code)
}
}
================================================
FILE: Cloudinary/Classes/Core/Network/NetworkRequest/CLDNetworkUploadRequest.swift
================================================
//
// CLDNetworkUploadRequest.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
internal class CLDNetworkUploadRequest: CLDNetworkDataRequestImpl {
override func progress(_ progress: ((Progress) -> Void)?) -> CLDNetworkDataRequest {
if let progress = progress{
request.uploadProgress(closure: progress)
}
return self
}
}
================================================
FILE: Cloudinary/Classes/Core/Network/PrivacyInfo.xcprivacy
================================================
NSPrivacyAccessedAPITypes
NSPrivacyAccessedAPITypeReasons
0A2A.1
NSPrivacyAccessedAPIType
NSPrivacyAccessedAPICategoryFileTimestamp
NSPrivacyCollectedDataTypes
NSPrivacyCollectedDataTypePurposes
NSPrivacyCollectedDataTypePurposeAppFunctionality
NSPrivacyCollectedDataType
NSPrivacyCollectedDataTypeAudioData
NSPrivacyCollectedDataTypeTracking
NSPrivacyCollectedDataTypeLinked
NSPrivacyCollectedDataTypePurposes
NSPrivacyCollectedDataTypePurposeAppFunctionality
NSPrivacyCollectedDataType
NSPrivacyCollectedDataTypeOtherUserContent
NSPrivacyCollectedDataTypeTracking
NSPrivacyCollectedDataTypeLinked
NSPrivacyCollectedDataTypePurposes
NSPrivacyCollectedDataTypePurposeAnalytics
NSPrivacyCollectedDataTypePurposeAppFunctionality
NSPrivacyCollectedDataTypeTracking
NSPrivacyCollectedDataTypeLinked
NSPrivacyCollectedDataType
NSPrivacyCollectedDataTypePhotosorVideos
NSPrivacyTrackingDomains
NSPrivacyTracking
================================================
FILE: Cloudinary/Classes/Core/Utils/CLDBuildParamsUtils.swift
================================================
//
// CLDBuildParamsUtils.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
// MARK: - Build String Params
internal func buildCoordinatesString(_ coordinates: [CLDCoordinate]) -> String {
return coordinates.map{$0.description}.joined(separator: "|")
}
internal func buildEagerString(_ eager: [CLDTransformation]) -> String {
return eager.map {$0.asString() ?? ""}
.filter {!$0.isEmpty}
.joined(separator: "|")
}
internal func buildContextString(_ context: [String : String]) -> String {
return context.map{"\($0)=\(encodeContextValue($1))"}.joined(separator: "|")
}
internal func encodeContextValue(_ value: String) -> String {
return value.replacingOccurrences(of: "|", with: "\\|").replacingOccurrences(of: "=", with: "\\=")
}
internal func buildHeadersString(_ headers: [String : String]) -> String {
return headers.map{"\($0): \($1)\\n"}.joined(separator: "")
}
================================================
FILE: Cloudinary/Classes/Core/Utils/CLDCryptoUtils.swift
================================================
//
// CLDCryptoUtils.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import Foundation
import CommonCrypto
public func cloudinarySignParamsUsingSecret(
_ paramsToSign: [String: Any],
cloudinaryApiSecret: String,
signatureVersion: Int? = 2
) -> String {
let sortedKeys = paramsToSign.keys.sorted()
var paramsArr: [String] = []
for key in sortedKeys {
guard let value = paramsToSign[key],
let encodedValue = encodedParamValue(value) else {
continue
}
paramsArr.append("\(key)=\(encodedValue)")
}
let toSign = paramsArr.joined(separator: "&")
return toSign.sha1_base8(cloudinaryApiSecret)
}
private func encodedParamValue(_ value: Any) -> String? {
var stringValue: String?
if let array = value as? [String], !array.isEmpty {
stringValue = array.joined(separator: ",")
} else if let str = cldParamValueAsString(value: value) {
stringValue = str
}
guard let result = stringValue else {
return nil
}
return result.replacingOccurrences(of: "&", with: "%26")
}
internal extension String {
func sha1_base8(_ secret: String?) -> String {
let data = self.data(using: String.Encoding.utf8)!
var digest = [UInt8](repeating: 0, count:Int(CC_SHA1_DIGEST_LENGTH))
let ctx = UnsafeMutablePointer.allocate(capacity: 1)
CC_SHA1_Init(ctx)
_ = data.withUnsafeBytes { buffer in
CC_SHA1_Update(ctx, buffer.baseAddress!, CC_LONG(data.count))
}
if let secret = secret {
let secretData = secret.data(using: String.Encoding.utf8)!
_ = secretData.withUnsafeBytes { buffer in
CC_SHA1_Update(ctx, buffer.baseAddress!, CC_LONG(secretData.count))
}
}
CC_SHA1_Final(&digest, ctx)
let hexBytes = digest.map { String(format: "%02hhx", $0) }
return hexBytes.joined()
}
func sha1_base64() -> String {
return sha_base64(type: .sha1)
}
func sha256_base64() -> String {
return sha_base64(type: .sha256)
}
fileprivate func sha_base64(type: shaBase64Type) -> String {
var digest = [UInt8](repeating: 0, count:type.count)
type.hash(string: self, digest: &digest)
let data = Data(bytes: digest, count: MemoryLayout.size * digest.count)
let base64 = data.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
let encoded = base64.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "=", with: "")
return encoded
}
fileprivate enum shaBase64Type {
case sha1
case sha256
var count: Int {
get {
switch self {
case .sha1 : return Int(CC_SHA1_DIGEST_LENGTH)
case .sha256: return Int(CC_SHA256_DIGEST_LENGTH)
}
}
}
func hash(string: String, digest: UnsafeMutablePointer!) {
let cStr = NSString(string: string).utf8String
switch self {
case .sha1 : CC_SHA1(cStr, CC_LONG(strlen(cStr!)), digest)
case .sha256: CC_SHA256(cStr, CC_LONG(strlen(cStr!)), digest)
}
}
}
func toCRC32() -> UInt32 {
return crc32(self)
}
func cld_md5() -> String {
var digest = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
if let data = self.data(using: String.Encoding.utf8) {
_ = data.withUnsafeBytes { buffer in
CC_MD5(buffer.baseAddress!, CC_LONG(buffer.count), &digest)
}
}
var digestHex = ""
for index in 0.. UInt32 {
let crcTable:[UInt32] = [0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d]
guard let data = string.data(using: String.Encoding.utf8)
else {
return 0
}
var crc:UInt32 = 0xffffffff
var buffer = (data as NSData).bytes.bindMemory(to: UInt8.self, capacity: data.count)
for _ in 1...data.count {
crc = (crc >> 8) ^ crcTable[Int((crc ^ UInt32(buffer.pointee)) & 0xff)]
buffer += 1
}
return crc ^ 0xffffffff
}
================================================
FILE: Cloudinary/Classes/Core/Utils/CLDDictionaryUtils.swift
================================================
//
// CLDDictionaryUtils.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
internal extension Dictionary {
mutating func cldMerge(_ otherDictionary: Dictionary?) {
guard let otherDictionary = otherDictionary else {
return
}
for (key,value) in otherDictionary {
self.updateValue(value, forKey:key)
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Utils/CLDError.swift
================================================
//
// CLDError.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
internal struct CLDError {
fileprivate static let domain = "com.cloudinary.error"
enum CloudinaryErrorCode: Int {
case generalErrorCode = -7000
case failedCreatingImageFromData = -7001
case failedDownloadingImage = -7002
case failedRetrievingFileInfo = -7003
case preprocessingError = -7004
case failedDownloadingAsset = -7005
case unacceptableStatusCode = -7006
}
static func generalError(userInfo: [AnyHashable: Any?]? = nil) -> NSError {
return error(code: .generalErrorCode, message: "Something went wrong.", userInfo: userInfo)
}
static func error(domain: String = CLDError.domain, code: CloudinaryErrorCode, message: String) -> NSError {
let userInfo = [NSLocalizedFailureReasonErrorKey: message]
return error(domain: domain, code: code.rawValue, userInfo: userInfo)
}
static func error(code: CloudinaryErrorCode, message: String, userInfo: [AnyHashable: Any?]?) -> NSError {
var _userInfo = userInfo
_userInfo?[NSLocalizedFailureReasonErrorKey] = message
return error(code: code.rawValue, userInfo: userInfo)
}
static func error(domain: String? = CLDError.domain, code: Int, userInfo: [AnyHashable: Any?]?) -> NSError {
var info = [String: Any]()
var _domain = CLDError.domain
if let domain = domain {
_domain = domain
}
if let userInfo = userInfo {
userInfo.forEach {key, value in
if let value = value {
info[String(describing: key)] = value
}
}
}
return NSError(domain: _domain, code: code, userInfo: info)
}
}
================================================
FILE: Cloudinary/Classes/Core/Utils/CLDFileUtils.swift
================================================
//
//
// CLDFileUtils.swift
//
// Copyright (c) 2017 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
internal class CLDFileUtils {
internal static func getFileSize(url: URL)->Int64?{
let attr = try? FileManager.default.attributesOfItem(atPath: url.path)
return attr?[FileAttributeKey.size] as? Int64
}
internal static func removeFile(file: URL) {
try? FileManager.default.removeItem(at: file)
}
internal static func removeFiles(files: [CLDPartDescriptor]) {
for file in files {
removeFile (file: file.url)
}
}
internal static func splitFile(url: URL, chunkSize: Int) -> (URL?, [CLDPartDescriptor])?{
let maxBufferSize = 16 * 1024
var outputStream:OutputStream?
var success = false
var parts = [CLDPartDescriptor]()
guard let inputStream = InputStream(url: url) else {
return nil
}
defer {
inputStream.close()
outputStream?.close()
// clean up files if something failed mid-process
if (!success) {
removeFiles(files: parts)
}
}
inputStream.open()
let bufferSize = min(chunkSize, maxBufferSize)
var buffer = [UInt8](repeating: 0, count: bufferSize)
var currentChunkBytes = 0
var chunkIndex = 0
var baseUrlResult = URL(fileURLWithPath: "")
var targetUrl = URL(fileURLWithPath:"")
var totalRead:Int64 = 0
let randomBaseFolder = NSUUID().uuidString
while inputStream.hasBytesAvailable {
let read = inputStream.read(&buffer, maxLength: calcReadSize(currentChunkBytes, chunkSize, bufferSize))
if (read == 0) {
continue
}
if (outputStream == nil){
(baseUrlResult, targetUrl) = getTempFileUrl(fileName: url.lastPathComponent, baseFolder: randomBaseFolder)
outputStream = OutputStream(url: targetUrl, append: false)
guard (outputStream != nil) else {
return nil
}
outputStream?.open()
}
currentChunkBytes += read
totalRead += Int64(read)
outputStream!.write(&buffer, maxLength: read)
if (currentChunkBytes >= chunkSize) {
// wrap up current chunk:
outputStream?.close()
outputStream = nil
parts.append(CLDPartDescriptor(url: targetUrl, offset: totalRead - Int64(currentChunkBytes), length: currentChunkBytes))
currentChunkBytes = 0
chunkIndex += 1
}
}
if (outputStream != nil) {
// wrap up last chunk's last chunk:
parts.append(CLDPartDescriptor(url: targetUrl, offset: totalRead - Int64(currentChunkBytes), length: currentChunkBytes))
}
success = true
return (baseUrlResult, parts)
}
fileprivate static func calcReadSize(_ currentChunkBytes: Int, _ chunkSize: Int, _ bufferSize: Int) -> Int {
let chunkSpaceLeft = chunkSize - currentChunkBytes
let maxLength = min(bufferSize, chunkSpaceLeft)
return maxLength
}
internal static func getTempFileUrl(fileName: String, baseFolder: String) -> (URL, URL) {
let randomId = NSUUID().uuidString
let baseUrl = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(baseFolder)
let tempDirURL = baseUrl.appendingPathComponent(randomId)
try! FileManager.default.createDirectory(at: tempDirURL, withIntermediateDirectories: true, attributes: nil)
return (baseUrl, tempDirURL.appendingPathComponent(fileName))
}
}
public struct CLDPartDescriptor {
public let url: URL
public let offset: Int64
public let length: Int
}
================================================
FILE: Cloudinary/Classes/Core/Utils/CLDImageGenerator/CLDImageGenerator.swift
================================================
//
// CLDImageGenerator.swift
//
// Copyright (c) 2020 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import UIKit
// MARK: - CLDImageDrawingInstructions
protocol CLDImageDrawingInstructions {
var targetSize: CGSize { get }
func draw(in context: CGContext)
}
// MARK: - CLDImageGenerator
class CLDImageGenerator: NSObject {
class CLDGeneratableImage {
let instructions: CLDImageDrawingInstructions
init(_ drawingInstructions: CLDImageDrawingInstructions) {
self.instructions = drawingInstructions
}
/// Draws an image according to instructions
final func draw() -> UIImage? {
UIGraphicsBeginImageContextWithOptions(instructions.targetSize, false, UIScreen.main.scale)
guard let context = UIGraphicsGetCurrentContext() else { return nil }
context.saveGState()
instructions.draw(in: context)
context.restoreGState()
guard let image = UIGraphicsGetImageFromCurrentImageContext() else { return nil }
UIGraphicsEndImageContext()
return image
}
}
/// Generates an image based on an instructions object
///
/// - Parameter instructions: Drawing instructions used to generate an image
/// - Parameter direction : The direction of the generated image
static func generateImage(from instructions: CLDImageDrawingInstructions, for direction: CLDImageGenerator.Direction = .up) -> UIImage? {
guard let workImage = CLDGeneratableImage(instructions).draw() else { return nil }
return UIImage(cgImage: workImage.cgImage!, scale: workImage.scale, orientation: direction.imageOrientation)
}
}
// MARK: - CLDImageGenerator
extension CLDImageGenerator {
enum Direction: Int {
case left
case right
case up
case down
}
}
// MARK: - CLDImageGenerator.Direction
extension CLDImageGenerator.Direction {
var imageOrientation: UIImage.Orientation {
switch self {
case .up : return .up
case .down : return .down
case .left : return .left
case .right: return .right
}
}
}
================================================
FILE: Cloudinary/Classes/Core/Utils/CLDImageUtils.swift
================================================
//
// CLDImageUtils.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import UIKit
private let lock = NSLock()
internal extension Data {
func cldToUIImageThreadSafe() -> UIImage? {
lock.lock()
let image = UIImage(data: self)
lock.unlock()
return image
}
}
================================================
FILE: Cloudinary/Classes/Core/Utils/CLDJsonUtils.swift
================================================
//
// CLDJsonUtils.swift
//
// Copyright (c) 2018 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
internal func fromJson(object: [String: String]) -> T? {
if let jsonData = try? JSONSerialization.data(withJSONObject: object),
let object = try? getJsonDecoder().decode(T.self, from: jsonData) {
return object
}
return nil
}
internal func asJsonArray(arr: [T]) -> String {
return "[\(arr.map {asJson(object: $0)}.joined(separator: ","))]"
}
internal func asJson(object: T) -> String {
return String(data: try! getJsonEncoder().encode(object), encoding: String.Encoding.utf8)!
}
fileprivate func getJsonDecoder() -> JSONDecoder {
let jsonDecoder = JSONDecoder()
jsonDecoder.dateDecodingStrategy = JSONDecoder.DateDecodingStrategy.formatted(getDateFormatter())
return jsonDecoder
}
fileprivate func getJsonEncoder() -> JSONEncoder {
let jsonEncoder = JSONEncoder()
jsonEncoder.dateEncodingStrategy = .formatted(getDateFormatter())
return jsonEncoder
}
fileprivate func getDateFormatter() -> DateFormatter {
let iso8601Formatter = DateFormatter()
iso8601Formatter.calendar = Calendar(identifier: .iso8601)
iso8601Formatter.locale = Locale(identifier: "en_US_POSIX")
iso8601Formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssXXXX"
iso8601Formatter.timeZone = TimeZone(secondsFromGMT: 0)
return iso8601Formatter
}
================================================
FILE: Cloudinary/Classes/Core/Utils/CLDLogManager.swift
================================================
//
// CLDLogManager.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
private let prefix = "[Cloudinary]"
@objc public enum CLDLogLevel: Int {
case trace, debug, info, warning, error, none
}
private enum LevelText: String {
case Trace, Debug, Info, Warning, Error, None
}
private func levelTextForLevel(_ logLevel: CLDLogLevel) -> LevelText {
switch(logLevel) {
case .trace: return .Trace
case .debug: return .Debug
case .info: return .Info
case .warning: return .Warning
case .error: return .Error
case .none: return .None
}
}
internal struct CLDLogManager {
internal static var minimumLogLevel = CLDLogLevel.none
}
internal func printLog(_ logLevel : CLDLogLevel, text: T, _ file: String = #file, _ function: String = #function, _ line: Int = #line){
if CLDLogManager.minimumLogLevel.rawValue <= logLevel.rawValue {
let filename = (file as NSString).lastPathComponent
let levelText = levelTextForLevel(logLevel).rawValue
print("\(prefix):[\(levelText)]: \(filename).\(function)[\(line)]: \(text)")
}
}
================================================
FILE: Cloudinary/Classes/Core/Utils/CLDStringUtils.swift
================================================
//
// CLDStringUtils.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
//internal extension Int {
// func format(f: String) -> String {
// return String(format: "%\(f)d", self)
// }
//}
internal extension Double {
func cldFormat(f: String) -> String {
return String(format: "%\(f)f", self)
}
}
internal extension Float {
func cldFloatFormat() -> String {
return cldFormat(f: ".1")
}
func cldFormat(f: String) -> String {
return String(format: "%\(f)f", self)
}
/**
Returns an string using int format if the float is an integer value or the
full float if it isn't:
23.5 => "23.5"
23.5123 => "23.5123"
23 => "23"
*/
func cldCleanFormat() -> String {
return self.truncatingRemainder(dividingBy: 1) == 0 ? String(format: "%.0f", self) : String(self);
}
}
internal extension String {
func cldBase64Encode() -> String {
return (self.data(using: String.Encoding.utf8)?.base64EncodedString())!
}
func cldBase64UrlEncode() -> String {
return cldBase64Encode().replacingOccurrences(of: "+", with: "-").replacingOccurrences(of: "/", with: "_")
}
func cldSmartEncodeUrl() -> String? {
let customAllowedSet = NSCharacterSet(charactersIn:"!*'\"();@&=+$,?%#[] ").inverted
return addingPercentEncoding(withAllowedCharacters: customAllowedSet)
}
subscript (i: Int) -> Character {
return self[startIndex.cldAdvance(i, for: self)]
}
subscript (r: CountableClosedRange) -> String {
get {
let startIndex = self.index(self.startIndex, offsetBy: r.lowerBound)
let endIndex = self.index(startIndex, offsetBy: r.upperBound - r.lowerBound)
return String(self[startIndex...endIndex])
}
}
func cldStringByAppendingPathComponent(str: String) -> String {
return self + ("/\(str)")
}
func cldAsBool() -> Bool {
if self == "true" {
return true
}
if let intValue = Int(self) {
return NSNumber(value: intValue).boolValue
}
return false
}
func cldAsNSNumber() -> NSNumber? {
if let intValue = Int(self) {
return NSNumber(value: intValue)
}
return nil
}
func cldIsRemoteUrl() -> Bool {
return self.range(of: "^ftp:|^https?:|^s3:|^gs:|^data:([\\w-]+\\/[\\w-]+(\\+[\\w-]+)?)?(;[\\w-]+=[\\w-]+)*;base64,([a-zA-Z0-9\\/+\\n=]+)$", options: [NSString.CompareOptions.regularExpression, NSString.CompareOptions.caseInsensitive], range: nil, locale: nil) != nil
}
func leftPadding(toLength: Int, withPad character: Character) -> String {
let newLength = self.count
if newLength < toLength {
return String(repeatElement(character, count: toLength - newLength)) + self
} else {
return self.substring(from: index(self.startIndex, offsetBy: newLength - toLength))
}
}
func toAnalyticsVersionStr() -> String {
guard let binaryInt = UInt32(self, radix: 2) else {
return ""
}
switch binaryInt {
case 0..<25:
guard let value = UnicodeScalar(UnicodeScalar("A").value + binaryInt) else {
return ""
}
return String(UnicodeScalar(value))
case 26..<51:
guard let value = UnicodeScalar(UnicodeScalar("a").value + binaryInt - 26) else {
return ""
}
return String(UnicodeScalar(value))
default:
guard let value = UnicodeScalar(UnicodeScalar("0").value + binaryInt - 52) else {
return ""
}
return String(UnicodeScalar(value))
}
}
}
internal extension String.Index{
// func successor(in string:String)->String.Index{
// return string.index(after: self)
// }
//
// func predecessor(in string:String)->String.Index{
// return string.index(before: self)
// }
func cldAdvance(_ offset:Int, `for` string:String)->String.Index{
return string.index(self, offsetBy: offset)
}
}
internal func cldParamValueAsString(value: Any) -> String? {
if let valueStr = value as? String {
if valueStr.isEmpty {
return nil
}
return valueStr
}
else if let valueNum = value as? NSNumber {
return String(describing: valueNum)
}
else {
printLog(.error, text: "The parameter value must ba a String or a Number")
return nil
}
}
extension String {
internal func removePrefix(_ prefix: String) -> String {
guard self.hasPrefix(prefix) else { return self }
return String(self.dropFirst(prefix.count))
}
internal func firstIndex(of value: String) -> Int {
guard let range: Range = range(of: value) else { return NSNotFound }
return distance(from: startIndex, to: range.lowerBound)
}
}
================================================
FILE: Cloudinary/Classes/ios/Extensions/CLDTransformation+Ios.swift
================================================
//
// CLDTransformation+Ios.swift
// Cloudinary
//
// Created on 23/02/2020.
//
import Foundation
import UIKit
extension CLDTransformation
{
/**
Deliver the image in the correct device pixel ratio, according to the used device.
- returns: The same instance of CLDTransformation.
*/
@discardableResult
open func setDprAuto() -> Self {
let scale = Float(UIScreen.main.scale)
return setDpr(scale)
}
}
================================================
FILE: Cloudinary/Classes/ios/Extensions/ExtensionCLDDownloader.swift
================================================
//
// ExtensionCLDDownloader.swift
//
// Copyright (c) 2021 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
extension CLDDownloader
{
// MARK: - Actions
/**
Asynchronously fetches a remote image from the specified URL.
The image is retrieved from the cache if it exists, otherwise its downloaded and cached.
//TODO: REMOVE!!!
- parameter url: The image URL to download.
- parameter progress: The closure that is called periodically during the data transfer.
- parameter completionHandler: The closure to be called once the request has finished, holding either the retrieved UIImage or the error.
- returns: A `CLDFetchImageRequest` instance to be used to get the fetched image from, or to get the download progress or cancel the task.
*/
@discardableResult
open func fetchImage(_ url: String, _ progress: ((Progress) -> Void)? = nil, completionHandler: CLDCompletionHandler? = nil) -> CLDFetchImageRequest {
let request = CLDFetchImageRequestImpl(url: url, downloadCoordinator: downloadCoordinator)
request.responseImage(completionHandler)
request.progress(progress)
request.fetchImage()
return request
}
/**
Asynchronously fetches a remote asset from the specified URL.
The asset is retrieved from the cache if it exists, otherwise its downloaded and cached.
- parameter url: The asset URL to download.
- parameter progress: The closure that is called periodically during the data transfer.
- parameter completionHandler: The closure to be called once the request has finished, holding either the retrieved Data or an error.
- returns: A `CLDFetchAssetRequest` instance to be used to get the fetched image from, or to get the download progress or cancel the task.
*/
@discardableResult
open func fetchAsset(_ url: String, _ progress: ((Progress) -> Void)? = nil, completionHandler: CLDAssetCompletionHandler? = nil) -> CLDFetchAssetRequest {
let request = CLDFetchAssetRequestImpl(url: url, downloadCoordinator: downloadCoordinator)
request.responseAsset(completionHandler)
request.progress(progress)
request.fetchAsset()
return request
}
}
================================================
FILE: Cloudinary/Classes/ios/Extensions/UIButton+Cloudinary.swift
================================================
//
// UIButton+Cloudinary.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import UIKit
public extension UIButton {
/**
Download an image asynchronously from the specified URL and set it to the UIButton's image.
The image is retrieved from the cache if it exists, otherwise its downloaded and cached.
- parameter url: The image URL to download.
- parameter state: The UIButton's UIControlState state that uses the specified image.
- parameter placeholder: A placeholder image to be set as the image untill the asynchronus download request finishes.
*/
@objc func cldSetImage(_ url: String, forState state: UIControl.State, cloudinary: CLDCloudinary, placeholder: UIImage? = nil) {
fetchImageForUIElement(url, placeholder: placeholder, cloudinary: cloudinary) { [weak self] (image: UIImage) in
self?.setImage(image, for: state)
}
}
/**
Download an image asynchronously from the specified URL and set it to the UIButton's image.
The image is retrieved from the cache if it exists, otherwise its downloaded and cached.
- parameter publicId: The remote asset's name (e.g. the public id of an uploaded image).
- parameter cloudinary: An instance of CLDCloudinary.
- parameter state: The UIButton's UIControlState state that uses the specified image.
- parameter signUrl: A boolean parameter indicating whether or not to generate a signiture out of the API secret and add it to the generated URL. Default is false.
- parameter transformation: An instance of CLDTransformation.
- parameter placeholder: A placeholder image to be set as the background image untill the asynchronus download request finishes.
*/
@objc func cldSetImage(publicId: String, cloudinary: CLDCloudinary, forState state: UIControl.State, signUrl: Bool = false, transformation: CLDTransformation? = nil, placeholder: UIImage? = nil) {
let urlGen = cloudinary.createUrl()
if let transformation = transformation {
urlGen.setTransformation(transformation)
}
guard let url = urlGen.generate(publicId, signUrl: signUrl) else {
if let placeholder = placeholder {
DispatchQueue.main.async { [weak self] in
self?.setImage(placeholder, for: state)
}
}
return
}
fetchImageForUIElement(url, placeholder: placeholder, cloudinary: cloudinary) { [weak self] (image: UIImage) in
self?.setImage(image, for: state)
}
}
/**
Download an image asynchronously from the specified URL and set it to the UIButton's background image.
The image is retrieved from the cache if it exists, otherwise its downloaded and cached.
- parameter url: The image URL to download.
- parameter state: The UIButton's UIControlState state that uses the specified image.
- parameter placeholder: A placeholder image to be set as the background image untill the asynchronus download request finishes.
*/
@objc func cld_setBackgroundImage(_ url: String, forState state: UIControl.State, cloudinary: CLDCloudinary, placeholder: UIImage? = nil) {
let setImageOnMainQueue = { [weak self] (image: UIImage) in
DispatchQueue.main.async {
self?.setBackgroundImage(image, for: state)
}
}
fetchImageForUIElement(url, placeholder: placeholder, cloudinary: cloudinary, fetchedImageHandler: setImageOnMainQueue)
}
/**
Download an image asynchronously from the specified URL and set it to the UIButton's image.
The image is retrieved from the cache if it exists, otherwise its downloaded and cached.
- parameter publicId: The remote asset's name (e.g. the public id of an uploaded image).
- parameter cloudinary: An instance of CLDCloudinary.
- parameter state: The UIButton's UIControlState state that uses the specified image.
- parameter signUrl: A boolean parameter indicating whether or not to generate a signiture out of the API secret and add it to the generated URL. Default is false.
- parameter transformation: An instance of CLDTransformation.
- parameter placeholder: A placeholder image to be set as the background image untill the asynchronus download request finishes.
*/
@objc func cld_setBackgroundImage(publicId: String, cloudinary: CLDCloudinary, forState state: UIControl.State, signUrl: Bool = false, transformation: CLDTransformation? = nil, placeholder: UIImage? = nil) {
let urlGen = cloudinary.createUrl()
if let transformation = transformation {
urlGen.setTransformation(transformation)
}
guard let url = urlGen.generate(publicId, signUrl: signUrl) else {
if let placeholder = placeholder {
DispatchQueue.main.async { [weak self] in
self?.setBackgroundImage(placeholder, for: state)
}
}
return
}
fetchImageForUIElement(url, placeholder: placeholder, cloudinary: cloudinary) { [weak self] (image: UIImage) in
self?.setBackgroundImage(image, for: state)
}
}
}
================================================
FILE: Cloudinary/Classes/ios/Extensions/UIImageView+Cloudinary.swift
================================================
//
// UIImageView+Cloudinary.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import UIKit
public extension UIImageView {
/**
Download an image asynchronously from the specified URL and set it to the UIImageView's image.
The image is retrieved from the cache if it exists, otherwise its downloaded and cached.
- parameter url: The image URL to download.
- parameter cloudinary: An instance of CLDCloudinary.
- parameter placeholder: A placeholder image to be set as the background image untill the asynchronus download request finishes.
*/
@objc func cldSetImage(_ url: String, cloudinary: CLDCloudinary, placeholder: UIImage? = nil) {
fetchImageForUIElement(url, placeholder: placeholder, cloudinary: cloudinary) { [weak self] (image: UIImage) in
self?.image = image
}
}
/**
Download an image asynchronously from the specified URL and set it to the UIImageView's image.
The image is retrieved from the cache if it exists, otherwise its downloaded and cached.
- parameter publicId: The remote asset's name (e.g. the public id of an uploaded image).
- parameter cloudinary: An instance of CLDCloudinary.
- parameter signUrl: A boolean parameter indicating whether or not to generate a signature out of the API secret and add it to the generated URL. Default is false.
- parameter resourceType The resource type of the image to download (can be useful to display video frames for thumbnails).
- parameter transformation: An instance of CLDTransformation.
- parameter placeholder: A placeholder image to be set as the background image until the asynchronus download request finishes.
*/
@objc func cldSetImage(publicId: String, cloudinary: CLDCloudinary, signUrl: Bool = false, resourceType:CLDUrlResourceType = CLDUrlResourceType.image, transformation: CLDTransformation? = nil, placeholder: UIImage? = nil) {
var cloudinary = cloudinary
cloudinary.config.analyticsObject.setFeatureFlag(flag: "E")
let urlGen = cloudinary.createUrl()
if let transformation = transformation {
urlGen.setTransformation(transformation)
}
guard let url = urlGen.setResourceType(resourceType).generate(publicId, signUrl: signUrl) else {
if let placeholder = placeholder {
DispatchQueue.main.async { [weak self] in
self?.image = placeholder
}
}
return
}
fetchImageForUIElement(url, placeholder: placeholder, cloudinary: cloudinary) { [weak self] (image: UIImage) in
self?.image = image
}
}
/**
Static var used to generate a unique address for the associated object
*/
private struct AssociatedKeys {
static var cldCurrentUrl = "cldCurrentUrl"
}
/**
Add an associated object to UIImageView so we can track the current url 'attached' to the image view.
This is important in case the view is used in an collection adapter where views are recycled, to verify
that when the async download finishes the associated url hasn't changed
*/
internal var cldCurrentUrl:String? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.cldCurrentUrl) as? String
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.cldCurrentUrl, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
================================================
FILE: Cloudinary/Classes/ios/Extensions/UIView+Cloudinary.swift
================================================
//
// UIView+Cloudinary.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import UIKit
internal extension UIView {
func fetchImageForUIElement(_ url: String, placeholder: UIImage?, cloudinary: CLDCloudinary, fetchedImageHandler: @escaping ((_ fetchedImage: UIImage) -> ())) {
if let placeholder = placeholder {
fetchedImageHandler(placeholder)
}
DispatchQueue.main.async {
self.setInProgressUrl(url)
}
cloudinary.createDownloader().fetchImage(url, completionHandler: { [weak self] (responseImage, error) in
if let img = responseImage {
DispatchQueue.main.async {
if let view = self, view.isUrlStillRelevant(url) {
fetchedImageHandler(img)
}
}
}
})
}
// set a url as the current request url for this view, if possible
func setInProgressUrl(_ url: String?){
// The associated propery `cldCurrentUrl` is only available on UIImageViews.
if let imageView = self as? UIImageView {
imageView.cldCurrentUrl = url
}
}
// check whether the url is in sync with the last request url on this view, if possible
func isUrlStillRelevant(_ url: String) -> Bool {
// The associated property `cldCurrentUrl` is only available on UIImageViews,
// we do not store the url for other UIViews
if let imageView = self as? UIImageView, let lastRequestUrl = imageView.cldCurrentUrl {
return url == lastRequestUrl
}
return true
}
}
================================================
FILE: Cloudinary/Classes/ios/NetworkRequest/CLDFetchAssetRequestImpl.swift
================================================
//
// CLDFetchAssetRequestImpl.swift
//
// Copyright (c) 2021 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
internal class CLDFetchAssetRequestImpl: CLDFetchAssetRequest {
fileprivate let url : String
fileprivate let downloadCoordinator: CLDDownloadCoordinator
fileprivate let closureQueue: OperationQueue
fileprivate var data : Data?
fileprivate var error: NSError?
// Requests
fileprivate var dataDownloadRequest: CLDNetworkDownloadRequest?
fileprivate var progress : ((Progress) -> Void)?
init(url: String, downloadCoordinator: CLDDownloadCoordinator) {
self.url = url
self.downloadCoordinator = downloadCoordinator
closureQueue = {
let operationQueue = OperationQueue()
operationQueue.name = "com.cloudinary.CLDFetchAssetRequest"
operationQueue.maxConcurrentOperationCount = 1
operationQueue.isSuspended = true
return operationQueue
}()
}
// MARK: - Actions
func fetchAsset() {
DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async {
self.downloadData()
}
}
// MARK: Private
fileprivate func downloadData() {
dataDownloadRequest = downloadCoordinator.download(url) as? CLDNetworkDownloadRequest
dataDownloadRequest?.progress(progress)
dataDownloadRequest?.responseData { [weak self] (responseData, responseError, statusCode) -> () in
if let data = responseData,
let err = responseError {
self?.data = data
self?.error = err
}
else if let data = responseData {
self?.data = data
}
else if let err = responseError {
self?.error = err
}
else {
let error = CLDError.error(code: .failedDownloadingAsset, message: "Failed attempting to download asset.", userInfo: ["statusCode": statusCode])
self?.error = error
}
self?.closureQueue.isSuspended = false
}
}
// MARK: - CLDFetchDataRequest
@discardableResult
@objc func responseAsset(_ completionHandler: CLDAssetCompletionHandler?) -> CLDFetchAssetRequest {
closureQueue.addOperation {
if let data = self.data,
let error = self.error {
completionHandler?(data, error)
}
else if let data = self.data {
completionHandler?(data, nil)
}
else if let error = self.error {
completionHandler?(nil, error)
}
else {
completionHandler?(nil, CLDError.generalError())
}
}
return self
}
@discardableResult
@objc func progress(_ progress: ((Progress) -> Void)?) -> CLDNetworkDataRequest {
if let downloadRequest = self.dataDownloadRequest {
downloadRequest.progress(progress)
}
else {
self.progress = progress
}
return self
}
@objc func resume() {
dataDownloadRequest?.resume()
}
@objc func suspend() {
dataDownloadRequest?.suspend()
}
@objc func cancel() {
dataDownloadRequest?.cancel()
}
@objc func response(_ completionHandler: ((_ response: Any?, _ error: NSError?) -> ())?) -> CLDNetworkRequest {
responseAsset(completionHandler)
return self
}
}
================================================
FILE: Cloudinary/Classes/ios/NetworkRequest/CLDFetchImageRequestImpl.swift
================================================
//
// CLDFetchImageRequestImpl.swift
//
// Copyright (c) 2016 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import UIKit
internal class CLDFetchImageRequestImpl: CLDFetchImageRequest {
fileprivate let url: String
fileprivate let downloadCoordinator: CLDDownloadCoordinator
fileprivate let closureQueue: OperationQueue
fileprivate var image: UIImage?
fileprivate var error: NSError?
// Requests
fileprivate var imageDownloadRequest: CLDNetworkDownloadRequest?
fileprivate var progress: ((Progress) -> Void)?
init(url: String, downloadCoordinator: CLDDownloadCoordinator) {
self.url = url
self.downloadCoordinator = downloadCoordinator
closureQueue = {
let operationQueue = OperationQueue()
operationQueue.name = "com.cloudinary.CLDFetchImageRequest"
operationQueue.maxConcurrentOperationCount = 1
operationQueue.isSuspended = true
return operationQueue
}()
}
// MARK: - Actions
func fetchImage() {
DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async {
self.downloadImageAndCacheIt()
}
}
// MARK: Private
fileprivate func downloadImageAndCacheIt() {
imageDownloadRequest = downloadCoordinator.download(url) as? CLDNetworkDownloadRequest
imageDownloadRequest?.progress(progress)
imageDownloadRequest?.responseData { [weak self] (responseData, responseError, httpCode) -> () in
if let data = responseData, let
image = data.cldToUIImageThreadSafe(),
let url = self?.url {
self?.image = image
} else if let err = responseError {
self?.error = err
}
else {
let error = CLDError.error(code: .failedDownloadingImage, message: "Failed attempting to download image.", userInfo: ["statusCode": httpCode])
self?.error = error
}
self?.closureQueue.isSuspended = false
}
}
// MARK: - CLDFetchImageRequest
@discardableResult
@objc func responseImage(_ completionHandler: CLDCompletionHandler?) -> CLDFetchImageRequest {
closureQueue.addOperation {
if let image = self.image {
completionHandler?(image, nil)
}
else if let error = self.error {
completionHandler?(nil, error)
}
else {
completionHandler?(nil, CLDError.generalError())
}
}
return self
}
@discardableResult
@objc func progress(_ progress: ((Progress) -> Void)?) -> CLDNetworkDataRequest {
if let downloadRequest = self.imageDownloadRequest {
downloadRequest.progress(progress)
}
else {
self.progress = progress
}
return self
}
@objc func resume() {
imageDownloadRequest?.resume()
}
@objc func suspend() {
imageDownloadRequest?.suspend()
}
@objc func cancel() {
imageDownloadRequest?.cancel()
}
@objc func response(_ completionHandler: ((_ response: Any?, _ error: NSError?) -> ())?) -> CLDNetworkRequest {
responseImage(completionHandler)
return self
}
}
================================================
FILE: Cloudinary/Classes/ios/UIViews/CLDResponsiveViewHelper.swift
================================================
//
// CLDResponsiveViewHelper.swift
//
// Copyright (c) 2018 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import UIKit
@objcMembers open class CLDResponsiveViewHelper : NSObject{
fileprivate var requestedWidth = 0
fileprivate var requestedHeight = 0
fileprivate var viewSizeKnown = false
fileprivate var publicId: String!
fileprivate var cloudinary: CLDCloudinary!
fileprivate var signUrl: Bool!
fileprivate var resourceType:CLDUrlResourceType!
fileprivate var responsiveParams : CLDResponsiveParams?
fileprivate var baseTransformation: String?
fileprivate var placeholder: UIImage?
public func cldSetImage(view: UIImageView, publicId: String, cloudinary: CLDCloudinary, signUrl: Bool = false, resourceType: CLDUrlResourceType = CLDUrlResourceType.image,
responsiveParams: CLDResponsiveParams, transformation: CLDTransformation? = nil, placeholder: UIImage? = nil) {
self.publicId = publicId
self.signUrl = signUrl
self.cloudinary = cloudinary
self.resourceType = resourceType
self.responsiveParams = responsiveParams
self.baseTransformation = transformation?.asString()
self.placeholder = placeholder
if (viewSizeKnown){
doResponsive(view)
}
}
open func onViewSizeKnown(view: UIImageView) {
viewSizeKnown = true
// if no one called cldSetImage we have nothing to do here
if let params = responsiveParams {
// We fetch an image in two cases: 1) This is the first time, or 2) The view got larger and the deverloper requested to reload the image on resize.
if (requestedWidth == 0 && requestedHeight == 0) ||
(params.shouldReloadOnSizeChange && didGetLarger(view)) {
doResponsive(view)
}
}
}
fileprivate func didGetLarger(_ view: UIImageView) -> Bool {
return getRoundedContentHeight(view) > requestedHeight || getRoundedContentWidth(view) > requestedWidth
}
fileprivate func doResponsive(_ view: UIImageView){
// Only fetch an image of a responsive transformation was generated successfully:
if let transformation = self.chainResponsiveTransformation(view) {
view.cldSetImage(publicId: publicId, cloudinary: cloudinary, signUrl: signUrl, resourceType: resourceType, transformation: transformation, placeholder: placeholder)
}
}
fileprivate func chainResponsiveTransformation(_ view: UIImageView) -> CLDTransformation? {
guard view.bounds.width > 0 && view.bounds.height > 0 else {
// nothing to do
return nil
}
let params = responsiveParams!
let responsiveTransformation = CLDTransformation()
if let baseTransformation = baseTransformation {
responsiveTransformation.setRawTransformation(baseTransformation).chain()
}
if (params.autoWidth) {
requestedWidth = getRoundedContentWidth(view)
responsiveTransformation.setWidth(requestedWidth)
} else {
requestedWidth = 0
}
if (params.autoHeight) {
requestedHeight = getRoundedContentHeight(view)
responsiveTransformation.setHeight(requestedHeight)
} else {
requestedHeight = 0
}
if let crop = params.cropMode {
responsiveTransformation.setCrop(crop)
}
if let gravity = params.gravity {
responsiveTransformation.setGravity(gravity)
}
return responsiveTransformation.setDpr(Float(UIScreen.main.scale))
}
fileprivate func getRoundedContentHeight(_ view: UIImageView) -> Int {
return trimAndRoundUp(Int(round(view.frame.height - view.layoutMargins.top - view.layoutMargins.bottom)))
}
fileprivate func getRoundedContentWidth(_ view: UIImageView) -> Int {
return trimAndRoundUp(Int(round(view.frame.width - view.layoutMargins.left - view.layoutMargins.right)))
}
fileprivate func trimAndRoundUp(_ dimension: Int) -> Int {
let value = ((dimension - 1) / responsiveParams!.stepSizePoints + 1) * responsiveParams!.stepSizePoints;
return max(responsiveParams!.minDimensionPoints, min(value, responsiveParams!.maxDimensionPoints));
}
}
================================================
FILE: Cloudinary/Classes/ios/UIViews/CLDUIImageView.swift
================================================
//
// CLDUIImageView.swift
//
// Copyright (c) 2018 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import UIKit
@objcMembers open class CLDUIImageView: UIImageView {
// Delegate most of the logic to a helper instance. To use responsive downloads in situations
// where one cannot use CLDUIImageView a CLDResponsiveViewHelper can be used in any other custom UIVIew
// as long as these two methods are used.
internal let responsiveHelper: CLDResponsiveViewHelper = CLDResponsiveViewHelper()
/**
Download an image asynchronously from the specified URL and set it to the UIImageView's image.
The image is retrieved from the cache if it exists, otherwise its downloaded and cached. Note: this must be used on the main thread.
- parameter publicId: The remote asset's name (e.g. the public id of an uploaded image).
- parameter cloudinary: An instance of CLDCloudinary.
- parameter signUrl: A boolean parameter indicating whether or not to generate a signature out of the API secret and add it to the generated URL. Default is false.
- parameter resourceType The resource type of the image to download (can be useful to display video frames for thumbnails).
- parameter responsiveParams An instance of CLDResponsiveParams to configure fetching a pre-scaled image to fit in the UIImageView.
- parameter transformation: An instance of CLDTransformation.
- parameter placeholder: A placeholder image to be set as the background image until the asynchronus download request finishes.
*/
@objc public func cldSetImage(publicId: String, cloudinary: CLDCloudinary, signUrl: Bool = false, resourceType: CLDUrlResourceType = CLDUrlResourceType.image,
responsiveParams: CLDResponsiveParams, transformation: CLDTransformation? = nil, placeholder: UIImage? = nil) {
responsiveHelper.cldSetImage (view: self, publicId: publicId, cloudinary: cloudinary, signUrl: signUrl, resourceType: resourceType, responsiveParams: responsiveParams, transformation : transformation, placeholder: placeholder)
}
override open func layoutSubviews() {
super.layoutSubviews()
if (bounds.width > 0 && bounds.height > 0) {
// notify the delegate that the view now knows it's own size
responsiveHelper.onViewSizeKnown(view: self)
}
}
}
================================================
FILE: Cloudinary/Classes/ios/Uploader/CLDImagePreprocessChain.swift
================================================
//
//
// CLDImagePreprocessChain.swift
//
// Copyright (c) 2017 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import UIKit
/**
The CLDImagePreprocessChain is used to run preprocessing on images before uploading.
It support processing, validations and encoders, all fully customizable.
*/
public class CLDImagePreprocessChain: CLDPreprocessChain {
public override init() {
}
internal override func decodeResource(_ resourceData: Any) throws -> UIImage? {
if let url = resourceData as? URL {
if let resourceData = try? Data(contentsOf: url) {
return UIImage(data: resourceData)
}
} else if let data = resourceData as? Data {
return UIImage(data: data)
}
return nil
}
internal override func verifyEncoder() throws {
if (encoder == nil) {
setEncoder(CLDPreprocessHelpers.defaultImageEncoder)
}
}
}
================================================
FILE: Cloudinary/Classes/ios/Uploader/CLDPreprocessHelpers.swift
================================================
//
//
// CLDPreprocessHelpers.swift
//
// Copyright (c) 2017 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import UIKit
/**
This class contains ready-to-use encoders and processing steps to be used with CLDPreprocessChain
*/
public class CLDPreprocessHelpers {
static let defaultImageEncoder: CLDResourceEncoder = CLDPreprocessHelpers.customImageEncoder(format: EncodingFormat.PNG, quality: 100)
/**
Get a CLDPreprocessStep closure to send to CLDPreprocessChain. Scales down any image larger than width/height
params while retaining the original aspect ratio. If the original image is already within bounds it will be
returned unchanged (i.e. a smaller image won't be enlarged).
- parameter width: Maximum allowed width
- parameter height: Maximum allowed height
- returns: A closure to use in a preprocessing chain.
*/
public static func limit(width: CGFloat, height: CGFloat) -> CLDPreprocessStep {
return { image in
if (image.size.width > width || image.size.height > height) {
return resizeImage(image: image, requiredSize: CGSize(width: width, height: height))
}
return image
}
}
/**
Get a CLDPreprocessStep closure to send to CLDPreprocessChain. Returns the image cropped to the requested rectangle. This step will validate that the chosen rectangle is within the image's dimensions, otherwise an exception is thrown and the CLDUploadRequest fails.
- parameter cropRect: The requested crop rectangle.
- returns: A closure to use in a preprocessing chain.
*/
public static func crop(cropRect: CGRect) -> CLDPreprocessStep {
return { image in
let imageRect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
guard imageRect.contains(cropRect) else {
throw CLDError.error(code: CLDError.CloudinaryErrorCode.preprocessingError, message: "Crop dimensions out of bounds")
}
if let croppedImage = image.cld_crop(cropRect) {
return croppedImage
}
else {
throw CLDError.error(code: CLDError.CloudinaryErrorCode.preprocessingError, message: "Image cropping failed")
}
}
}
/**
Get a CLDPreprocessStep closure to send to CLDPreprocessChain. Rotates any image to the requested degree.
- parameter degrees: The requested rotation degree
- returns: A closure to use in a preprocessing chain.
*/
public static func rotate(degrees: Float) -> CLDPreprocessStep {
return { image in
if let rotatedImage = image.cld_rotate(degrees) {
return rotatedImage
}
return image
}
}
/**
Get a CLDResourceEncoder to use in CLDPreprocessChain. This encoder saves the image with the
chosen format and quality.
- parameter format: Image format to encode the image
- parameter quality: Quality to save the image (ignored if the chosen
format doesn't support quality)
- returns: A closure to use as encoder in a preprocessing chain
*/
public static func customImageEncoder(format: EncodingFormat, quality: CGFloat) -> CLDResourceEncoder {
return { image in
if let data = encodeAs(image: image, format: format, quality: quality) {
let (_, url) = CLDFileUtils.getTempFileUrl(fileName: NSUUID().uuidString, baseFolder:"imageEncoder")
try? data.write(to: url)
return url
}
return nil
}
}
/**
Get a CLDPreprocessStep to send to CLDPreprocessChain. This step will validate that a given image's
dimensions are within the chosen bounds, otherwise an exception is throw and the CLDUploadRequest fails.
- parameter minWidth: Minimum width allowed.
- parameter maxWidth: Maximum width allowed.
- parameter minHeight: Minimum height allowed.
- parameter maxHeight: Maximum height allowed.
- returns: A closure to use in a preprocessing chain.
*/
public static func dimensionsValidator(minWidth: Int, maxWidth: Int, minHeight: Int, maxHeight: Int) -> CLDPreprocessStep {
return { image in
if let width = image.cgImage?.width, let height = image.cgImage?.height {
if (width > maxWidth || width < minWidth ||
height > maxHeight || height < minHeight) {
throw CLDError.error(code: CLDError.CloudinaryErrorCode.preprocessingError, message: "Image dimensions invalid")
}
}
return image
}
}
internal static func encodeAs(image: UIImage, format: EncodingFormat, quality: CGFloat) -> Data? {
switch format {
case EncodingFormat.JPEG:
return image.jpegData(compressionQuality: quality)
case EncodingFormat.PNG:
return image.pngData()
}
}
internal static func resizeImage(image: UIImage, requiredSize: CGSize) -> UIImage {
let widthRatio = requiredSize.width / image.size.width
let heightRatio = requiredSize.height / image.size.height
let newSize: CGSize
if (heightRatio > widthRatio) {
newSize = CGSize(width: requiredSize.width, height: round(widthRatio * image.size.height))
} else {
newSize = CGSize(width: round(heightRatio * image.size.width), height: requiredSize.height)
}
var newImage: UIImage
UIGraphicsBeginImageContextWithOptions(CGSize(width: newSize.width, height: newSize.height), false, 1.0)
image.draw(in: CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height))
newImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return newImage
}
}
internal extension UIImage
{
func cld_crop(_ rect: CGRect) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(rect.size, false, self.scale)
self.draw(at: CGPoint(x: -rect.origin.x, y: -rect.origin.y))
let croppedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return croppedImage
}
fileprivate func cld_radians(from degrees: Double) -> CGFloat {
return CGFloat(degrees * Double.pi / 180.0);
}
func cld_rotate(_ degree: Float) -> UIImage? {
var newSize = CGRect(origin: CGPoint.zero, size: self.size).applying(CGAffineTransform(rotationAngle: cld_radians(from: Double(degree)))).size
// Trim off the extremely small float value to prevent core graphics from rounding it up
newSize.width = floor(newSize.width)
newSize.height = floor(newSize.height)
UIGraphicsBeginImageContextWithOptions(newSize, false, self.scale)
let context = UIGraphicsGetCurrentContext()!
// Move origin to middle
context.translateBy(x: newSize.width/2, y: newSize.height/2)
// Rotate around middle
context.rotate(by: cld_radians(from: Double(degree)))
// Draw the image at its center
draw(in: CGRect(x: -self.size.width/2, y: -self.size.height/2, width: self.size.width, height: self.size.height))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
}
public enum EncodingFormat {
case JPEG
case PNG
}
================================================
FILE: Cloudinary/Classes/ios/Uploader/CLDVideoPreprocessChain.swift
================================================
//
// CLDVideoPreprocessChain.swift
//
// Copyright (c) 2017 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import AVKit
public typealias CLDVideoPreprocessStep = (CLDVideoTranscode) throws -> CLDVideoTranscode
public class CLDVideoPreprocessChain: CLDPreprocessChain {
private var outputURL: URL?
public override init() {
super.init()
}
internal override func decodeResource(_ resourceData: Any) throws -> CLDVideoTranscode? {
if let url = resourceData as? URL {
return CLDVideoTranscode(sourceURL: url)
} else if let data = resourceData as? Data {
return try handleLargeVideoData(data)
} else {
throw CLDError.error(code: CLDError.CloudinaryErrorCode.preprocessingError, message: "Resource type should be URL or Data only!")
}
}
private func handleLargeVideoData(_ data: Data) throws -> CLDVideoTranscode? {
var tempDirectory: URL!
if #available(iOS 10.0, *) {
tempDirectory = FileManager.default.temporaryDirectory
} else {
tempDirectory = URL(fileURLWithPath: NSTemporaryDirectory())
}
let tempURL = tempDirectory.appendingPathComponent(UUID().uuidString).appendingPathExtension("mp4")
do {
try data.write(to: tempURL)
return CLDVideoTranscode(sourceURL: tempURL)
} catch {
throw CLDError.error(code: CLDError.CloudinaryErrorCode.preprocessingError, message: "Failed to write data to temporary file: \(error.localizedDescription)")
}
}
public func setOutputFormat(format: AVFileType) -> Self {
addStep { videoTranscode in
try videoTranscode.setOutputFormat(format: format)
return videoTranscode
}
return self
}
public func setOutputDimensions(dimensions: CGSize) -> Self {
addStep { videoTranscode in
videoTranscode.setOutputDimensions(dimensions: dimensions)
return videoTranscode
}
return self
}
public func setCompressionPreset(preset: String) -> Self {
addStep { videoTranscode in
videoTranscode.setCompressionPreset(preset: preset)
return videoTranscode
}
return self
}
internal override func execute(resourceData: Any) throws -> URL {
try verifyEncoder()
guard let videoTranscode = try decodeResource(resourceData) else {
throw CLDError.error(code: CLDError.CloudinaryErrorCode.preprocessingError, message: "Error decoding resource")
}
let dispatchGroup = DispatchGroup()
var resultURL: URL?
var resultError: Error?
dispatchGroup.enter()
DispatchQueue.global(qos: .background).async {
do {
var processedTranscode = videoTranscode
for preprocess in self.chain {
processedTranscode = try preprocess(processedTranscode)
}
processedTranscode.transcode { success, error in
if success, let outputURL = processedTranscode.outputURL {
resultURL = outputURL
} else {
resultError = error ?? CLDError.error(code: CLDError.CloudinaryErrorCode.preprocessingError, message: "Error transcoding video")
}
dispatchGroup.leave()
}
} catch {
resultError = error
dispatchGroup.leave()
}
}
dispatchGroup.wait()
if let finalOutputURL = resultURL {
return finalOutputURL
} else {
throw resultError ?? CLDError.error(code: CLDError.CloudinaryErrorCode.preprocessingError, message: "Unknown error")
}
}
internal override func verifyEncoder() throws {
if encoder == nil {
encoder = { _ in self.outputURL }
}
}
}
================================================
FILE: Cloudinary/Classes/ios/Uploader/CLDVideoPreprocessHelpers.swift
================================================
import Foundation
import AVKit
public class CLDVideoPreprocessHelpers {
private var steps: [(CLDVideoTranscode) throws -> CLDVideoTranscode] = []
public func addStep(_ step: @escaping (CLDVideoTranscode) throws -> CLDVideoTranscode) {
steps.append(step)
}
public static func setOutputFormat(format: AVFileType) -> CLDVideoPreprocessStep {
return { videoTranscode in
do {
try videoTranscode.setOutputFormat(format: format)
} catch {
throw error
}
return videoTranscode
}
}
public static func setOutputDimensions(dimensions: CGSize) -> CLDVideoPreprocessStep {
return { videoTranscode in
videoTranscode.setOutputDimensions(dimensions: dimensions)
return videoTranscode
}
}
public static func setCompressionPreset(preset: String) -> CLDVideoPreprocessStep {
return { videoTranscode in
videoTranscode.setCompressionPreset(preset: preset)
return videoTranscode
}
}
public static func dimensionsValidator(minWidth: CGFloat, maxWidth: CGFloat, minHeight: CGFloat, maxHeight: CGFloat) -> CLDVideoPreprocessStep {
return { videoTranscode in
guard let dimensions = videoTranscode.outputDimensions else {
throw NSError(domain: "CLDVideoPreprocessHelpers", code: -1, userInfo: [NSLocalizedDescriptionKey: "Dimensions not set"])
}
if dimensions.width < minWidth || dimensions.width > maxWidth || dimensions.height < minHeight || dimensions.height > maxHeight {
throw VideoPreprocessError.dimensionsOutOfRange
}
return videoTranscode
}
}
}
enum VideoPreprocessError: Error {
case dimensionsOutOfRange
}
================================================
FILE: Cloudinary/Classes/ios/Uploader/CLDVideoTranscode.swift
================================================
import Foundation
import AVFoundation
public class CLDVideoTranscode {
let sourceURL: URL
var outputURL: URL?
var outputFormat: AVFileType = .mov
var outputDimensions: CGSize?
var compressionPreset: String = AVAssetExportPresetPassthrough // Default to passthrough
init(sourceURL: URL) {
self.sourceURL = sourceURL
}
func setOutputFormat(format: AVFileType) throws {
guard FileManager.default.fileExists(atPath: sourceURL.path) else {
throw NSError(domain: "CLDVideoPreprocessHelpers", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid source URL"])
}
let asset = AVAsset(url: sourceURL)
guard asset.tracks(withMediaType: .video).first != nil else {
throw NSError(domain: "CLDVideoPreprocessHelpers", code: -1, userInfo: [NSLocalizedDescriptionKey: "No video track found"])
}
self.outputFormat = format
}
func setOutputDimensions(dimensions: CGSize) {
self.outputDimensions = dimensions
}
func setCompressionPreset(preset: String) {
self.compressionPreset = preset
}
var hasVideoTrack: Bool {
let asset = AVAsset(url: sourceURL)
return asset.tracks(withMediaType: .video).first != nil
}
func transcode(completion: @escaping (Bool, Error?) -> Void) {
let asset = AVAsset(url: sourceURL)
let outputURL = generateOutputURL()
do {
// Create asset reader
let assetReader = try AVAssetReader(asset: asset)
guard let videoTrack = asset.tracks(withMediaType: .video).first else {
throw NSError(domain: "CLDVideoTranscode", code: -1, userInfo: [NSLocalizedDescriptionKey: "No video track found"])
}
// Configure output settings for AVAssetReader
let assetReaderOutputSettings = [
kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32ARGB
]
let assetReaderOutput = AVAssetReaderTrackOutput(track: videoTrack, outputSettings: assetReaderOutputSettings)
// Add the output to the reader
assetReader.add(assetReaderOutput)
// Create asset writer
let assetWriter = try AVAssetWriter(outputURL: outputURL, fileType: outputFormat)
let videoSettings: [String: Any] = [
AVVideoWidthKey: outputDimensions?.width ?? videoTrack.naturalSize.width,
AVVideoHeightKey: outputDimensions?.height ?? videoTrack.naturalSize.height
]
let assetWriterInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoSettings)
assetWriterInput.expectsMediaDataInRealTime = true
assetWriter.add(assetWriterInput)
// Start reading and writing
assetReader.startReading()
assetWriter.startWriting()
assetWriter.startSession(atSourceTime: .zero)
assetWriterInput.requestMediaDataWhenReady(on: DispatchQueue(label: "assetWriterQueue")) {
while assetWriterInput.isReadyForMoreMediaData {
guard assetReader.status == .reading else {
let error = assetReader.error ?? NSError(domain: "CLDVideoTranscode", code: -1, userInfo: [NSLocalizedDescriptionKey: "Asset reader status is not reading"])
completion(false, error)
return
}
if let sampleBuffer = assetReaderOutput.copyNextSampleBuffer() {
assetWriterInput.append(sampleBuffer)
} else {
assetWriterInput.markAsFinished()
assetWriter.finishWriting {
defer {
if self.sourceURL.isFileURL {
try? FileManager.default.removeItem(at: self.sourceURL)
}
}
if assetWriter.status == .completed {
self.outputURL = assetWriter.outputURL
completion(true, nil)
} else {
completion(false, assetWriter.error)
}
}
break
}
}
}
} catch {
completion(false, error)
}
}
private func generateOutputURL() -> URL {
return URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString).appendingPathExtension(outputFormat.fileExtension)
}
}
private extension AVFileType {
var fileExtension: String {
switch self {
case .mov:
return "mov"
case .mp4:
return "mp4"
case .m4v:
return "m4v"
default:
return "mov"
}
}
}
================================================
FILE: Cloudinary/Classes/ios/Video/Analytics/VideoAnalytics.swift
================================================
//
// VideoAnalytics.swift
// Cloudinary
//
// Created by Adi Mizrahi on 14/12/2023.
//
import Foundation
public typealias EventDetails = [String: Any]
typealias CustomerData = [String: Any]
typealias VideoData = [String: Any]
typealias ProvidedData = [String: Any]
public enum TrackingType: String {
case manual = "manual"
case auto = "auto"
}
public enum PlayerKeyPath: String {
case status = "status"
case timeControlStatus = "timeControlStatus"
case duration = "duration"
}
public enum AnalyticsType {
case auto
case manual
case disabled
}
struct VideoPlayer {
var type: String
var version: String
}
public class VideoEvent {
public var trackingType: TrackingType
public var eventName: String
public var eventTime: Int
public var eventDetails: EventDetails
init(trackingType: TrackingType, eventName: String, eventDetails: EventDetails? = nil) {
self.trackingType = trackingType
self.eventName = eventName
self.eventTime = Int(Int64((Date().timeIntervalSince1970 * 1000.0).rounded()))
self.eventDetails = eventDetails ?? [String: Any]()
self.eventDetails[VideoEventJSONKeys.videoPlayer.rawValue] = createVideoPlayerObject()
}
func createVideoPlayerObject() -> [String: Any] {
return [VideoEventJSONKeys.type.rawValue: "ios_player", VideoEventJSONKeys.version.rawValue: CLDNetworkCoordinator.DEFAULT_VERSION]
}
func createCustomerData(trackingData: [String: String]?, providedData: [String: Any]?) -> [String: Any] {
var videoData = VideoData()
videoData[VideoEventJSONKeys.cloudName.rawValue] = trackingData?[VideoEventJSONKeys.cloudName.rawValue] ?? ""
videoData[VideoEventJSONKeys.publicId.rawValue] = trackingData?[VideoEventJSONKeys.publicId.rawValue] ?? ""
var result: [String: Any] = [VideoEventJSONKeys.videoData.rawValue: videoData]
if let providedData = providedData, !providedData.isEmpty {
var providedDataObject = ProvidedData()
providedData.forEach { (key, value) in
providedDataObject[key] = value
}
result[VideoEventJSONKeys.providedData.rawValue] = providedDataObject
}
return result
}
func toDictonary() -> [String: Any] {
var detailsDictionary: [String: Any] = [:]
for (key, value) in eventDetails {
detailsDictionary[key] = value
}
return [
VideoEventJSONKeys.eventName.rawValue: eventName,
VideoEventJSONKeys.eventName.rawValue: eventTime,
VideoEventJSONKeys.eventDetails.rawValue: detailsDictionary
]
}
}
public class VideoViewStartEvent: VideoEvent {
public init(trackingType: TrackingType = .auto, videoUrl: String, trackingData: [String: String]?,providedData: [String: Any]? = nil) {
var eventDetails = EventDetails()
eventDetails[VideoEventJSONKeys.trackingType.rawValue] = trackingType.rawValue
eventDetails[VideoEventJSONKeys.videoUrl.rawValue] = videoUrl
let defaultEventName = EventNames.viewStart.rawValue
super.init(trackingType: trackingType, eventName: defaultEventName, eventDetails: eventDetails)
super.eventDetails[VideoEventJSONKeys.customerData.rawValue] = createCustomerData(trackingData: trackingData, providedData: providedData)
}
}
public class VideoLoadMetadata: VideoEvent {
public init(trackingType: TrackingType = .auto, duration: Int, providedData: [String: Any]? = nil) {
var eventDetails = EventDetails()
eventDetails[VideoEventJSONKeys.trackingType.rawValue] = trackingType.rawValue
eventDetails[VideoEventJSONKeys.videoDuration.rawValue] = duration
let defaultEventName = EventNames.loadMetadata.rawValue
super.init(trackingType: trackingType, eventName: defaultEventName, eventDetails: eventDetails)
}
}
public class VideoViewEnd: VideoEvent {
public init(trackingType: TrackingType = .auto, providedData: [String: Any]? = nil) {
var eventDetails = EventDetails()
eventDetails[VideoEventJSONKeys.trackingType.rawValue] = trackingType.rawValue
let defaultEventName = EventNames.viewEnd.rawValue
super.init(trackingType: trackingType, eventName: defaultEventName, eventDetails: eventDetails)
}
}
public class VideoPlayEvent: VideoEvent {
public init(trackingType: TrackingType = .auto, providedData: [String: Any]? = nil) {
let defaultEventName = EventNames.play.rawValue
super.init(trackingType: trackingType, eventName: defaultEventName)
}
}
public class VideoPauseEvent: VideoEvent {
public init(trackingType: TrackingType = .auto, providedData: [String: Any]? = nil) {
let defaultEventName = EventNames.pause.rawValue
super.init(trackingType: trackingType, eventName: defaultEventName)
}
}
public enum VideoEventJSONKeys: String {
case userId = "userId"
case trackingType = "trackingType"
case viewId = "viewId"
case events = "events"
case eventName = "eventName"
case eventTime = "eventTime"
case eventDetails = "eventDetails"
case videoPlayer = "videoPlayer"
case videoUrl = "videoUrl"
case videoDuration = "videoDuration"
case videoPublicId = "videoPublicId"
case transformation = "transfromation"
case videoExtension = "videoExtension"
case customerData = "customerData"
case videoData = "videoData"
case providedData = "providedData"
case cloudName = "cloudName"
case publicId = "publicId"
case type = "type"
case version = "version"
}
public enum EventNames: String {
case viewStart = "viewStart"
case viewEnd = "viewEnd"
case loadMetadata = "loadMetadata"
case play = "play"
case pause = "pause"
}
================================================
FILE: Cloudinary/Classes/ios/Video/Analytics/VideoEventsManager.swift
================================================
//
// VideoEventsManager.swift
// Cloudinary
//
// Created by Adi Mizrahi on 14/12/2023.
//
import Foundation
@objcMembers public class VideoEventsManager {
let CLD_ANALYTICS_ENDPOINT_PRODUCTION_URL: String = "https://video-analytics-api.cloudinary.com/v1/video-analytics"
public var CLD_ANALYTICS_ENDPOINT_DEVELOPMENT_URL = ""
var viewId: String
var userId: String!
var trackingType: TrackingType = .auto
var cloudName: String?
var publicId: String?
public init() {
viewId = UUID().uuidString.lowercased().replacingOccurrences(of: "-", with: "")
userId = getUserId()
}
func getUserId() -> String {
if let userId = UserDefaults.standard.string(forKey: "CLDVideoPlayerUserId") {
return userId
} else {
var newUserId = UUID().uuidString
newUserId = newUserId.lowercased().replacingOccurrences(of: "-", with: "")
UserDefaults.standard.set(newUserId, forKey: "CLDVideoPlayerUserId")
return newUserId
}
}
public func sendViewStartEvent(videoUrl: String, providedData: [String: Any]? = nil) {
let event = VideoViewStartEvent(trackingType: trackingType, videoUrl: videoUrl, trackingData: ["cloudName": cloudName ?? "", "publicId": publicId ?? ""], providedData: providedData)
addEventToQueue(event: event)
}
public func sendViewEndEvent(providedData: [String: Any]? = nil) {
let event = VideoViewEnd(trackingType: trackingType, providedData: providedData)
addEventToQueue(event: event)
}
public func sendLoadMetadataEvent(duration: Int, providedData: [String: Any]? = nil) {
let event = VideoLoadMetadata(trackingType: trackingType, duration: duration, providedData: providedData)
addEventToQueue(event: event)
}
public func sendPlayEvent(providedData: [String: Any]? = nil) {
let event = VideoPlayEvent(trackingType: trackingType, providedData: providedData)
addEventToQueue(event: event)
}
public func sendPauseEvent(providedData: [String: Any]? = nil) {
let event = VideoPauseEvent(trackingType: trackingType, providedData: providedData)
addEventToQueue(event: event)
}
public var eventQueue = [VideoEvent]()
private var timer: Timer?
func addEventToQueue(event: VideoEvent) {
eventQueue.append(event)
}
@objc public func sendEvents() {
guard !eventQueue.isEmpty else {
return
}
let eventsToSend = Array(eventQueue.prefix(eventQueue.count))
sendEventToEndpoint(childEvents: eventsToSend)
eventQueue.removeFirst(eventsToSend.count)
}
private func buildFormDataPart(boundary: String, name: String, value: String? = nil) -> Data {
var body = Data()
body.append(contentsOf: "--\(boundary)\r\n".utf8)
body.append(contentsOf: "Content-Disposition: form-data; name=\"\(name)\"\r\n\r\n".utf8)
if let value = value {
body.append(contentsOf: "\(value)\r\n".utf8)
}
return body
}
private func buildEventsData(childEvents: [VideoEvent]) -> Data? {
var body = Data()
var eventData: [[String: Any]] = []
for childEvent in childEvents {
eventData.append([
VideoEventJSONKeys.eventName.rawValue: childEvent.eventName,
VideoEventJSONKeys.eventTime.rawValue: childEvent.eventTime,
VideoEventJSONKeys.eventDetails.rawValue: childEvent.eventDetails])
}
do {
let jsonData = try JSONSerialization.data(withJSONObject: eventData, options: [])
body.append(jsonData)
body.append(contentsOf: "\r\n".utf8)
return body
} catch {
print("Error creating JSON data: \(error)")
return nil
}
}
private func sendEventToEndpoint(childEvents: [VideoEvent]) {
var request = URLRequest(url: URL(string: CLD_ANALYTICS_ENDPOINT_PRODUCTION_URL)!)
request.httpMethod = "POST"
let boundary = "Boundary-\(UUID().uuidString)"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
request.setValue("ios_video_player_analytics_test", forHTTPHeaderField: "User-Agent")
var body = Data()
// Add events data
body.append(buildFormDataPart(boundary: boundary, name: "userId", value: userId))
body.append(buildFormDataPart(boundary: boundary, name: "viewId", value: viewId))
body.append(buildFormDataPart(boundary: boundary, name: "events"))
body.append(buildEventsData(childEvents: childEvents)!)
body.append(contentsOf: "--\(boundary)--\r\n".utf8)
request.httpBody = body
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("Error sending event: \(error)")
} else if let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) {
print("Event sent successfully")
} else {
print("Failed to send event. Response: \(response.debugDescription)")
}
}.resume()
}
}
================================================
FILE: Cloudinary/Classes/ios/Video/CLDVideoPlayer.swift
================================================
//
// CLDVideoPlayer.swift
//
// Copyright (c) 2018 Cloudinary (http://cloudinary.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import AVKit
@available(iOS 10.0, *)
@objcMembers open class CLDVideoPlayer: AVPlayer {
var automaticStreamingProfile: Bool = false
var analytics: Bool = true
var isIntialized: Bool = false
var loadMetadataSent: Bool = false
var publicId: String?
var transformation: CLDTransformation?
var eventsManager: VideoEventsManager = VideoEventsManager()
var providedData: [String: Any]?
override init() {
super.init()
setAnalyticsObservers()
}
/**
Download a video asynchronously from the specified URL and set it to the AVPlayer video.
- parameter publicId: The remote asset's name (e.g. the public id of an uploaded image).
- parameter cloudinary: An instance of CLDCloudinary.
- parameter transformation: An instance of CLDTransformation.
- parameter automaticStreamingProfile: A bool to indicate the use of automatic streaming profile default: false
*/
public init(publicId: String, cloudinary: CLDCloudinary, transformation: CLDTransformation? = nil, automaticStreamingProfile: Bool? = false) {
var cloudinary = cloudinary
cloudinary.config.analyticsObject.setFeatureFlag(flag: "F")
var transformation = transformation
var cldUrl = cloudinary.createUrl()
if automaticStreamingProfile ?? false && transformation == nil {
cldUrl = cldUrl.setFormat("m3u8")
transformation = CLDTransformation().setStreamingProfile("auto")
}
guard let urlString = cldUrl.setResourceType(.video)
.setTransformation(transformation ?? CLDTransformation())
.generate(publicId), let url = URL(string: urlString) else {
print("Error - could not generate URL for CLDVideoPlayer")
super.init()
return
}
super.init(url: url)
}
/**
Initializes a CLDVideoPlayer instance, using a given AVPlayerItem.
- parameter item: The player item to put into AVPlayer
*/
public override init(playerItem item: AVPlayerItem?) {
super.init(playerItem: item)
}
/**
Initializes a CLDVideoPlayer instance, using a given URL.
- parameter url: The URL to put into AVPlayer
*/
public override init(url URL: URL) {
super.init(url: URL)
}
/**
Initializes a CLDVideoPlayer instance, using a given URL.
- parameter url: The string to put into AVPlayer
*/
public init(url string: String) {
guard let url = URL(string: string) else {
print("Error - could not generate URL for CLDVideoPlayer")
super.init()
return
}
super.init(url: url)
}
public func setAnalytics(_ analyticsType: AnalyticsType, cloudName: String?, publicId: String?) {
switch analyticsType {
case .auto:
eventsManager.trackingType = .auto
eventsManager.cloudName = cloudName ?? ""
eventsManager.publicId = publicId ?? self.publicId
break
case .manual:
eventsManager.trackingType = .manual
eventsManager.cloudName = cloudName ?? ""
eventsManager.publicId = publicId ?? self.publicId
break
case .disabled:
analytics = false
}
}
public func flushEvents() {
guard analytics else { return }
eventsManager.sendEvents()
}
public func flushEventsAndCloseSession() {
guard analytics else { return }
eventsManager.sendViewEndEvent(providedData: providedData)
flushEvents()
}
deinit {
guard analytics else { return }
removeObserver(self, forKeyPath: PlayerKeyPath.status.rawValue)
removeObserver(self, forKeyPath: PlayerKeyPath.timeControlStatus.rawValue)
flushEventsAndCloseSession()
}
func setAnalyticsObservers() {
guard analytics else {
return
}
addObserver(self, forKeyPath: PlayerKeyPath.status.rawValue, options: [.new], context: nil)
addObserver(self, forKeyPath: PlayerKeyPath.timeControlStatus.rawValue, options: [.new], context: nil)
}
open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard let path = keyPath, let changes = change else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
switch path {
case PlayerKeyPath.status.rawValue:
if let newStatusNumber = changes[.newKey] as? NSNumber, let newStatus = AVPlayer.Status(rawValue: newStatusNumber.intValue) {
handleStatusChanged(newStatus)
}
case PlayerKeyPath.timeControlStatus.rawValue:
if let newStatusNumber = changes[.newKey] as? NSNumber, let newStatus = AVPlayer.TimeControlStatus(rawValue: newStatusNumber.intValue) {
handleTimeControlStatusChanged(newStatus)
}
default:
super.observeValue(forKeyPath: path, of: object, change: changes, context: context)
}
}
}
@available(iOS 10.0, *)
extension CLDVideoPlayer {
func handleStatusChanged(_ status: AVPlayer.Status) {
switch status {
case .readyToPlay:
if let assetURL = self.currentItem?.asset as? AVURLAsset {
let mediaURL = assetURL.url
eventsManager.sendViewStartEvent(videoUrl: mediaURL.absoluteString, providedData: providedData)
isIntialized = true
loadDurationAsynchronously()
}
break
case .failed:
// Playback failed
break
case .unknown:
// Unknown status
break
@unknown default:
break
}
}
private func loadDurationAsynchronously() {
guard let currentItem = self.currentItem else { return }
let asset = currentItem.asset
let durationKey = "duration"
// Load the duration key asynchronously
asset.loadValuesAsynchronously(forKeys: [durationKey]) { [weak self] in
DispatchQueue.main.async {
guard let self = self else { return }
var error: NSError?
let status = asset.statusOfValue(forKey: durationKey, error: &error)
switch status {
case .loaded:
let duration = asset.duration
var durationInSeconds = 0
if duration.isValid && duration.isNumeric && !duration.isIndefinite {
let durationSeconds = CMTimeGetSeconds(duration)
if durationSeconds.isFinite && !durationSeconds.isNaN {
durationInSeconds = Int(durationSeconds)
}
}
if !self.loadMetadataSent {
self.loadMetadataSent = true
self.eventsManager.sendLoadMetadataEvent(duration: durationInSeconds)
}
case .failed, .cancelled:
print("Failed to load duration: \(error?.localizedDescription ?? "Unknown error")")
case .loading:
// Still loading, could retry or wait
break
case .unknown:
// Unknown status
break
@unknown default:
break
}
}
}
}
func handleTimeControlStatusChanged(_ status: AVPlayer.TimeControlStatus) {
switch status {
case .playing:
eventsManager.sendPlayEvent(providedData: providedData)
case .paused:
// Player paused
if isIntialized {
if self.timeControlStatus == .paused {
eventsManager.sendPauseEvent(providedData: providedData)
}
}
case .waitingToPlayAtSpecifiedRate:
break
@unknown default:
break
}
}
public func setProvidedData(data: [String: Any]) {
providedData = data
}
}
================================================
FILE: Cloudinary.podspec
================================================
#
# Be sure to run `pod lib lint Cloudinary.podspec' to ensure this is a
# valid spec before submitting.
#
# Any lines starting with a # are optional, but their use is encouraged
# To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html
#
Pod::Spec.new do |s|
s.name = 'Cloudinary'
s.version = '5.2.5'
s.summary = "Cloudinary is a cloud service that offers a solution to a web application's entire image management pipeline."
s.description = <<-DESC
Easily upload images to the cloud. Automatically perform smart image resizing, cropping and conversion without installing any complex software.
Integrate Facebook or Twitter profile image extraction in a snap, in any dimension and style to match your website’s graphics requirements.
Images are seamlessly delivered through a fast CDN, and much much more.
Cloudinary offers comprehensive APIs and administration capabilities and is easy to integrate with any web application, existing or new.
Cloudinary provides URL and HTTP based APIs that can be easily integrated with any Web development framework.
DESC
s.homepage = 'http://cloudinary.com'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { "Cloudinary" => "info@cloudinary.com" }
s.source = { :git => "https://github.com/cloudinary/cloudinary_ios.git", :tag => s.version.to_s }
s.swift_version = '5.0'
s.ios.deployment_target = '9.0'
s.frameworks = 'UIKit', 'Foundation'
s.default_subspec = 'ios'
s.subspec 'ios' do |spec|
spec.platform = :ios
spec.source_files = 'Cloudinary/Classes/**/*.{swift,h}'
spec.resource_bundles = { 'Cloudinary' => ['Cloudinary/Classes/Core/Network/PrivacyInfo.xcprivacy'] }
end
end
================================================
FILE: Cloudinary.xcodeproj/Cloudinary_Info.plist
================================================
CFBundleDevelopmentRegion
en
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
$(PRODUCT_NAME)
CFBundlePackageType
FMWK
CFBundleShortVersionString
1.0
CFBundleSignature
????
CFBundleVersion
$(CURRENT_PROJECT_VERSION)
NSPrincipalClass
================================================
FILE: Cloudinary.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
B690BF5E2BC55992007117AC /* HTTPStatusCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B690BF5D2BC55992007117AC /* HTTPStatusCode.swift */; };
B694AAEC2B308AF100075041 /* CLDAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = B694AAEB2B308AF100075041 /* CLDAnalytics.swift */; };
B694AAEF2B308B8400075041 /* VideoAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = B694AAEE2B308B8400075041 /* VideoAnalytics.swift */; };
B694AAF12B308B9D00075041 /* VideoEventsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B694AAF02B308B9D00075041 /* VideoEventsManager.swift */; };
B6B4C49A2C560FEE00C9B604 /* CLDVideoPreprocessChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B4C4992C560FEE00C9B604 /* CLDVideoPreprocessChain.swift */; };
B6B4C49C2C56100600C9B604 /* CLDVideoTranscode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B4C49B2C56100600C9B604 /* CLDVideoTranscode.swift */; };
B6B4C4A12C5615CF00C9B604 /* CLDVideoPreprocessHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B4C4A02C5615CF00C9B604 /* CLDVideoPreprocessHelpers.swift */; };
B6C2D4862A724C4200AA0039 /* CLDVideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C2D4852A724C4200AA0039 /* CLDVideoPlayer.swift */; };
OBJ_257 /* CLDNConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_10 /* CLDNConvertible.swift */; };
OBJ_258 /* CLDNError.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* CLDNError.swift */; };
OBJ_259 /* CLDNMultipartFormData.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* CLDNMultipartFormData.swift */; };
OBJ_260 /* CLDNParameterEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* CLDNParameterEncoding.swift */; };
OBJ_261 /* CLDNRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_14 /* CLDNRequest.swift */; };
OBJ_262 /* CLDNResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_15 /* CLDNResponse.swift */; };
OBJ_263 /* CLDNResponseSerialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_16 /* CLDNResponseSerialization.swift */; };
OBJ_264 /* CLDNResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_17 /* CLDNResult.swift */; };
OBJ_265 /* CLDNSessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_18 /* CLDNSessionDelegate.swift */; };
OBJ_266 /* CLDNSessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_19 /* CLDNSessionManager.swift */; };
OBJ_267 /* CLDNTaskDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_20 /* CLDNTaskDelegate.swift */; };
OBJ_268 /* CLDNTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_21 /* CLDNTimeline.swift */; };
OBJ_269 /* CLDNValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_22 /* CLDNValidation.swift */; };
OBJ_270 /* DispatchQueue+Cloudinary.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_23 /* DispatchQueue+Cloudinary.swift */; };
OBJ_271 /* CLDCloudinary.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_24 /* CLDCloudinary.swift */; };
OBJ_272 /* CLDCompatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_25 /* CLDCompatibility.swift */; };
OBJ_273 /* CLDConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_26 /* CLDConfiguration.swift */; };
OBJ_274 /* CLDEagerTransformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_27 /* CLDEagerTransformation.swift */; };
OBJ_275 /* CLDResponsiveParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_28 /* CLDResponsiveParams.swift */; };
OBJ_304 /* CLDDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_73 /* CLDDownloader.swift */; };
OBJ_305 /* CLDBaseNetworkObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_75 /* CLDBaseNetworkObject.swift */; };
OBJ_306 /* CLDConditionExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_76 /* CLDConditionExpression.swift */; };
OBJ_307 /* CLDDefinitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_77 /* CLDDefinitions.swift */; };
OBJ_308 /* CLDExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_78 /* CLDExpression.swift */; };
OBJ_309 /* CLDOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_79 /* CLDOperators.swift */; };
OBJ_310 /* CLDTransformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_80 /* CLDTransformation.swift */; };
OBJ_311 /* CLDVariable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_81 /* CLDVariable.swift */; };
OBJ_312 /* CLDFetchLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_83 /* CLDFetchLayer.swift */; };
OBJ_313 /* CLDLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_84 /* CLDLayer.swift */; };
OBJ_314 /* CLDSubtitlesLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_85 /* CLDSubtitlesLayer.swift */; };
OBJ_315 /* CLDTextLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_86 /* CLDTextLayer.swift */; };
OBJ_316 /* CLDRequestParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_88 /* CLDRequestParams.swift */; };
OBJ_317 /* CLDRequestParamsHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_90 /* CLDRequestParamsHelpers.swift */; };
OBJ_318 /* CLDRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_92 /* CLDRequest.swift */; };
OBJ_319 /* CLDBaseResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_94 /* CLDBaseResult.swift */; };
OBJ_320 /* CLDAccessibilityAnalysisResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_97 /* CLDAccessibilityAnalysisResult.swift */; };
OBJ_321 /* CLDColorblindAccessibilityAnalysisResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_98 /* CLDColorblindAccessibilityAnalysisResult.swift */; };
OBJ_322 /* CLDBoundingBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_99 /* CLDBoundingBox.swift */; };
OBJ_323 /* CLDDetection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_100 /* CLDDetection.swift */; };
OBJ_324 /* CLDFace.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_101 /* CLDFace.swift */; };
OBJ_325 /* CLDInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_102 /* CLDInfo.swift */; };
OBJ_326 /* CLDAdvOcrResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_104 /* CLDAdvOcrResult.swift */; };
OBJ_327 /* CLDOcrBlockResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_105 /* CLDOcrBlockResult.swift */; };
OBJ_328 /* CLDOcrBoundindBlockResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_106 /* CLDOcrBoundindBlockResult.swift */; };
OBJ_329 /* CLDOcrDataResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_107 /* CLDOcrDataResult.swift */; };
OBJ_330 /* CLDOcrDetectedLanguagesResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_108 /* CLDOcrDetectedLanguagesResult.swift */; };
OBJ_331 /* CLDOcrFullTextAnnotationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_109 /* CLDOcrFullTextAnnotationResult.swift */; };
OBJ_332 /* CLDOcrPageResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_110 /* CLDOcrPageResult.swift */; };
OBJ_333 /* CLDOcrParagraphResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_111 /* CLDOcrParagraphResult.swift */; };
OBJ_334 /* CLDOcrPropertyResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_112 /* CLDOcrPropertyResult.swift */; };
OBJ_335 /* CLDOcrResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_113 /* CLDOcrResult.swift */; };
OBJ_336 /* CLDOcrSymbolResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_114 /* CLDOcrSymbolResult.swift */; };
OBJ_337 /* CLDOcrTextAnnotationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_115 /* CLDOcrTextAnnotationResult.swift */; };
OBJ_338 /* CLDOcrWordResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_116 /* CLDOcrWordResult.swift */; };
OBJ_339 /* CLDQualityAnalysis.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_118 /* CLDQualityAnalysis.swift */; };
OBJ_340 /* CLDRekognitionFace.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_119 /* CLDRekognitionFace.swift */; };
OBJ_341 /* CommonResultKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_120 /* CommonResultKeys.swift */; };
OBJ_342 /* CLDManagementApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_122 /* CLDManagementApi.swift */; };
OBJ_343 /* CLDDeleteRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_124 /* CLDDeleteRequest.swift */; };
OBJ_344 /* CLDExplicitRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_125 /* CLDExplicitRequest.swift */; };
OBJ_345 /* CLDExplodeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_126 /* CLDExplodeRequest.swift */; };
OBJ_346 /* CLDMultiRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_127 /* CLDMultiRequest.swift */; };
OBJ_347 /* CLDRenameRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_128 /* CLDRenameRequest.swift */; };
OBJ_348 /* CLDSpriteRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_129 /* CLDSpriteRequest.swift */; };
OBJ_349 /* CLDTagRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_130 /* CLDTagRequest.swift */; };
OBJ_350 /* CLDTextRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_131 /* CLDTextRequest.swift */; };
OBJ_351 /* CLDDeleteByTokenRequestParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_133 /* CLDDeleteByTokenRequestParams.swift */; };
OBJ_352 /* CLDDestroyRequestParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_134 /* CLDDestroyRequestParams.swift */; };
OBJ_353 /* CLDExplicitRequestParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_135 /* CLDExplicitRequestParams.swift */; };
OBJ_354 /* CLDExplodeRequestParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_136 /* CLDExplodeRequestParams.swift */; };
OBJ_355 /* CLDMultiRequestParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_137 /* CLDMultiRequestParams.swift */; };
OBJ_356 /* CLDRenameRequestParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_138 /* CLDRenameRequestParams.swift */; };
OBJ_357 /* CLDSpriteRequestParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_139 /* CLDSpriteRequestParams.swift */; };
OBJ_358 /* CLDTagsRequestParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_140 /* CLDTagsRequestParams.swift */; };
OBJ_359 /* CLDTextRequestParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_141 /* CLDTextRequestParams.swift */; };
OBJ_360 /* CLDDeleteResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_143 /* CLDDeleteResult.swift */; };
OBJ_361 /* CLDExplicitResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_144 /* CLDExplicitResult.swift */; };
OBJ_362 /* CLDExplodeResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_145 /* CLDExplodeResult.swift */; };
OBJ_363 /* CLDMultiResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_146 /* CLDMultiResult.swift */; };
OBJ_364 /* CLDRenameResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_147 /* CLDRenameResult.swift */; };
OBJ_365 /* CLDSpriteResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_148 /* CLDSpriteResult.swift */; };
OBJ_366 /* CLDTagResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_149 /* CLDTagResult.swift */; };
OBJ_367 /* CLDTextResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_150 /* CLDTextResult.swift */; };
OBJ_368 /* CLDUploaderWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_152 /* CLDUploaderWidget.swift */; };
OBJ_369 /* CLDCropOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_154 /* CLDCropOverlayView.swift */; };
OBJ_370 /* CLDCropScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_155 /* CLDCropScrollView.swift */; };
OBJ_371 /* CLDCropScrollViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_156 /* CLDCropScrollViewController.swift */; };
OBJ_372 /* CLDCropView.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_157 /* CLDCropView.swift */; };
OBJ_373 /* CLDCropViewCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_158 /* CLDCropViewCalculator.swift */; };
OBJ_374 /* CLDCropViewUIManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_159 /* CLDCropViewUIManager.swift */; };
OBJ_375 /* CLDWidgetConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_161 /* CLDWidgetConfiguration.swift */; };
OBJ_376 /* CLDWidgetAssetContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_163 /* CLDWidgetAssetContainer.swift */; };
OBJ_377 /* CLDWidgetPreviewCollectionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_164 /* CLDWidgetPreviewCollectionCell.swift */; };
OBJ_378 /* BackIconInstructions.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_166 /* BackIconInstructions.swift */; };
OBJ_379 /* CropIconInstructions.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_167 /* CropIconInstructions.swift */; };
OBJ_380 /* CropRotateIconInstructions.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_168 /* CropRotateIconInstructions.swift */; };
OBJ_381 /* DoneIconInstructions.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_169 /* DoneIconInstructions.swift */; };
OBJ_382 /* RatioLockedIconInstructions.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_170 /* RatioLockedIconInstructions.swift */; };
OBJ_383 /* RatioOpenedIconInstructions.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_171 /* RatioOpenedIconInstructions.swift */; };
OBJ_384 /* RotateIconInstructions.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_172 /* RotateIconInstructions.swift */; };
OBJ_385 /* CLDDisplayLinkObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_174 /* CLDDisplayLinkObserver.swift */; };
OBJ_386 /* CLDVideoControlsState.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_175 /* CLDVideoControlsState.swift */; };
OBJ_387 /* CLDVideoControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_176 /* CLDVideoControlsView.swift */; };
OBJ_388 /* CLDVideoHiddenAndPausedState.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_177 /* CLDVideoHiddenAndPausedState.swift */; };
OBJ_389 /* CLDVideoHiddenAndPlayingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_178 /* CLDVideoHiddenAndPlayingState.swift */; };
OBJ_390 /* CLDVideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_179 /* CLDVideoPlayerView.swift */; };
OBJ_391 /* CLDVideoShownAndPausedState.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_180 /* CLDVideoShownAndPausedState.swift */; };
OBJ_392 /* CLDVideoShownAndPlayingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_181 /* CLDVideoShownAndPlayingState.swift */; };
OBJ_393 /* CLDVideoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_182 /* CLDVideoView.swift */; };
OBJ_394 /* CLDWidgetEditViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_184 /* CLDWidgetEditViewController.swift */; };
OBJ_395 /* CLDWidgetPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_185 /* CLDWidgetPreviewViewController.swift */; };
OBJ_396 /* CLDWidgetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_186 /* CLDWidgetViewController.swift */; };
OBJ_397 /* CLDUploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_188 /* CLDUploader.swift */; };
OBJ_398 /* CLDPreprocessChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_190 /* CLDPreprocessChain.swift */; };
OBJ_399 /* CLDUploadRequestParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_192 /* CLDUploadRequestParams.swift */; };
OBJ_400 /* CLDDefaultUploadRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_194 /* CLDDefaultUploadRequest.swift */; };
OBJ_401 /* CLDUploadRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_195 /* CLDUploadRequest.swift */; };
OBJ_402 /* CLDUploadRequestWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_196 /* CLDUploadRequestWrapper.swift */; };
OBJ_403 /* CLDUploadResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_198 /* CLDUploadResult.swift */; };
OBJ_404 /* CLDUrl.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_200 /* CLDUrl.swift */; };
OBJ_405 /* CLDNetworkAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_203 /* CLDNetworkAdapter.swift */; };
OBJ_406 /* CLDDefaultNetworkAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_204 /* CLDDefaultNetworkAdapter.swift */; };
OBJ_407 /* CLDNetworkCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_205 /* CLDNetworkCoordinator.swift */; };
OBJ_408 /* CLDAsyncNetworkUploadRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_207 /* CLDAsyncNetworkUploadRequest.swift */; };
OBJ_409 /* CLDErrorRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_208 /* CLDErrorRequest.swift */; };
OBJ_410 /* CLDGenericNetworkRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_209 /* CLDGenericNetworkRequest.swift */; };
OBJ_411 /* CLDNetworkDataRequestImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_210 /* CLDNetworkDataRequestImpl.swift */; };
OBJ_412 /* CLDNetworkDownloadRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_211 /* CLDNetworkDownloadRequest.swift */; };
OBJ_413 /* CLDNetworkUploadRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_212 /* CLDNetworkUploadRequest.swift */; };
OBJ_414 /* CLDBuildParamsUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_214 /* CLDBuildParamsUtils.swift */; };
OBJ_415 /* CLDCryptoUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_215 /* CLDCryptoUtils.swift */; };
OBJ_416 /* CLDDictionaryUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_216 /* CLDDictionaryUtils.swift */; };
OBJ_417 /* CLDError.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_217 /* CLDError.swift */; };
OBJ_418 /* CLDFileUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_218 /* CLDFileUtils.swift */; };
OBJ_420 /* CLDImageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_221 /* CLDImageGenerator.swift */; };
OBJ_421 /* CLDImageUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_222 /* CLDImageUtils.swift */; };
OBJ_422 /* CLDJsonUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_223 /* CLDJsonUtils.swift */; };
OBJ_423 /* CLDLogManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_224 /* CLDLogManager.swift */; };
OBJ_424 /* CLDStringUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_225 /* CLDStringUtils.swift */; };
OBJ_425 /* CLDTransformation+Ios.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_228 /* CLDTransformation+Ios.swift */; };
OBJ_426 /* ExtensionCLDDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_229 /* ExtensionCLDDownloader.swift */; };
OBJ_427 /* UIButton+Cloudinary.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_230 /* UIButton+Cloudinary.swift */; };
OBJ_428 /* UIImageView+Cloudinary.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_231 /* UIImageView+Cloudinary.swift */; };
OBJ_429 /* UIView+Cloudinary.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_232 /* UIView+Cloudinary.swift */; };
OBJ_430 /* CLDFetchAssetRequestImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_234 /* CLDFetchAssetRequestImpl.swift */; };
OBJ_431 /* CLDFetchImageRequestImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_235 /* CLDFetchImageRequestImpl.swift */; };
OBJ_432 /* CLDResponsiveViewHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_237 /* CLDResponsiveViewHelper.swift */; };
OBJ_433 /* CLDUIImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_238 /* CLDUIImageView.swift */; };
OBJ_434 /* CLDImagePreprocessChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_240 /* CLDImagePreprocessChain.swift */; };
OBJ_435 /* CLDPreprocessHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_241 /* CLDPreprocessHelpers.swift */; };
OBJ_442 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
B690BF5D2BC55992007117AC /* HTTPStatusCode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = HTTPStatusCode.swift; path = CacheSystem/Enum/HTTPStatusCode.swift; sourceTree = ""; };
B694AAEB2B308AF100075041 /* CLDAnalytics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CLDAnalytics.swift; sourceTree = ""; };
B694AAEE2B308B8400075041 /* VideoAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoAnalytics.swift; sourceTree = ""; };
B694AAF02B308B9D00075041 /* VideoEventsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoEventsManager.swift; sourceTree = ""; };
B6B4C4992C560FEE00C9B604 /* CLDVideoPreprocessChain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDVideoPreprocessChain.swift; sourceTree = ""; };
B6B4C49B2C56100600C9B604 /* CLDVideoTranscode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDVideoTranscode.swift; sourceTree = ""; };
B6B4C4A02C5615CF00C9B604 /* CLDVideoPreprocessHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDVideoPreprocessHelpers.swift; sourceTree = ""; };
B6C2D4852A724C4200AA0039 /* CLDVideoPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDVideoPlayer.swift; sourceTree = ""; };
"Cloudinary::Cloudinary::Product" /* Cloudinary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Cloudinary.framework; sourceTree = BUILT_PRODUCTS_DIR; };
OBJ_10 /* CLDNConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDNConvertible.swift; sourceTree = ""; };
OBJ_100 /* CLDDetection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDDetection.swift; sourceTree = ""; };
OBJ_101 /* CLDFace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDFace.swift; sourceTree = ""; };
OBJ_102 /* CLDInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDInfo.swift; sourceTree = ""; };
OBJ_104 /* CLDAdvOcrResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDAdvOcrResult.swift; sourceTree = ""; };
OBJ_105 /* CLDOcrBlockResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDOcrBlockResult.swift; sourceTree = ""; };
OBJ_106 /* CLDOcrBoundindBlockResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDOcrBoundindBlockResult.swift; sourceTree = ""; };
OBJ_107 /* CLDOcrDataResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDOcrDataResult.swift; sourceTree = ""; };
OBJ_108 /* CLDOcrDetectedLanguagesResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDOcrDetectedLanguagesResult.swift; sourceTree = ""; };
OBJ_109 /* CLDOcrFullTextAnnotationResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDOcrFullTextAnnotationResult.swift; sourceTree = ""; };
OBJ_11 /* CLDNError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDNError.swift; sourceTree = ""; };
OBJ_110 /* CLDOcrPageResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDOcrPageResult.swift; sourceTree = ""; };
OBJ_111 /* CLDOcrParagraphResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDOcrParagraphResult.swift; sourceTree = ""; };
OBJ_112 /* CLDOcrPropertyResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDOcrPropertyResult.swift; sourceTree = ""; };
OBJ_113 /* CLDOcrResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDOcrResult.swift; sourceTree = ""; };
OBJ_114 /* CLDOcrSymbolResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDOcrSymbolResult.swift; sourceTree = ""; };
OBJ_115 /* CLDOcrTextAnnotationResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDOcrTextAnnotationResult.swift; sourceTree = ""; };
OBJ_116 /* CLDOcrWordResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDOcrWordResult.swift; sourceTree = ""; };
OBJ_118 /* CLDQualityAnalysis.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDQualityAnalysis.swift; sourceTree = ""; };
OBJ_119 /* CLDRekognitionFace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDRekognitionFace.swift; sourceTree = ""; };
OBJ_12 /* CLDNMultipartFormData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDNMultipartFormData.swift; sourceTree = ""; };
OBJ_120 /* CommonResultKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonResultKeys.swift; sourceTree = ""; };
OBJ_122 /* CLDManagementApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDManagementApi.swift; sourceTree = ""; };
OBJ_124 /* CLDDeleteRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDDeleteRequest.swift; sourceTree = ""; };
OBJ_125 /* CLDExplicitRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDExplicitRequest.swift; sourceTree = ""; };
OBJ_126 /* CLDExplodeRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDExplodeRequest.swift; sourceTree = ""; };
OBJ_127 /* CLDMultiRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDMultiRequest.swift; sourceTree = ""; };
OBJ_128 /* CLDRenameRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDRenameRequest.swift; sourceTree = ""; };
OBJ_129 /* CLDSpriteRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDSpriteRequest.swift; sourceTree = ""; };
OBJ_13 /* CLDNParameterEncoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDNParameterEncoding.swift; sourceTree = ""; };
OBJ_130 /* CLDTagRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDTagRequest.swift; sourceTree = ""; };
OBJ_131 /* CLDTextRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDTextRequest.swift; sourceTree = ""; };
OBJ_133 /* CLDDeleteByTokenRequestParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDDeleteByTokenRequestParams.swift; sourceTree = ""; };
OBJ_134 /* CLDDestroyRequestParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDDestroyRequestParams.swift; sourceTree = ""; };
OBJ_135 /* CLDExplicitRequestParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDExplicitRequestParams.swift; sourceTree = ""; };
OBJ_136 /* CLDExplodeRequestParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDExplodeRequestParams.swift; sourceTree = ""; };
OBJ_137 /* CLDMultiRequestParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDMultiRequestParams.swift; sourceTree = ""; };
OBJ_138 /* CLDRenameRequestParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDRenameRequestParams.swift; sourceTree = ""; };
OBJ_139 /* CLDSpriteRequestParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDSpriteRequestParams.swift; sourceTree = ""; };
OBJ_14 /* CLDNRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDNRequest.swift; sourceTree = ""; };
OBJ_140 /* CLDTagsRequestParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDTagsRequestParams.swift; sourceTree = ""; };
OBJ_141 /* CLDTextRequestParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDTextRequestParams.swift; sourceTree = ""; };
OBJ_143 /* CLDDeleteResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDDeleteResult.swift; sourceTree = ""; };
OBJ_144 /* CLDExplicitResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDExplicitResult.swift; sourceTree = ""; };
OBJ_145 /* CLDExplodeResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDExplodeResult.swift; sourceTree = ""; };
OBJ_146 /* CLDMultiResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDMultiResult.swift; sourceTree = ""; };
OBJ_147 /* CLDRenameResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDRenameResult.swift; sourceTree = ""; };
OBJ_148 /* CLDSpriteResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDSpriteResult.swift; sourceTree = ""; };
OBJ_149 /* CLDTagResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDTagResult.swift; sourceTree = ""; };
OBJ_15 /* CLDNResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDNResponse.swift; sourceTree = ""; };
OBJ_150 /* CLDTextResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDTextResult.swift; sourceTree = ""; };
OBJ_152 /* CLDUploaderWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDUploaderWidget.swift; sourceTree = ""; };
OBJ_154 /* CLDCropOverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDCropOverlayView.swift; sourceTree = ""; };
OBJ_155 /* CLDCropScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDCropScrollView.swift; sourceTree = ""; };
OBJ_156 /* CLDCropScrollViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDCropScrollViewController.swift; sourceTree = ""; };
OBJ_157 /* CLDCropView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDCropView.swift; sourceTree = ""; };
OBJ_158 /* CLDCropViewCalculator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDCropViewCalculator.swift; sourceTree = ""; };
OBJ_159 /* CLDCropViewUIManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDCropViewUIManager.swift; sourceTree = ""; };
OBJ_16 /* CLDNResponseSerialization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDNResponseSerialization.swift; sourceTree = ""; };
OBJ_161 /* CLDWidgetConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDWidgetConfiguration.swift; sourceTree = ""; };
OBJ_163 /* CLDWidgetAssetContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDWidgetAssetContainer.swift; sourceTree = ""; };
OBJ_164 /* CLDWidgetPreviewCollectionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDWidgetPreviewCollectionCell.swift; sourceTree = ""; };
OBJ_166 /* BackIconInstructions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackIconInstructions.swift; sourceTree = ""; };
OBJ_167 /* CropIconInstructions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CropIconInstructions.swift; sourceTree = ""; };
OBJ_168 /* CropRotateIconInstructions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CropRotateIconInstructions.swift; sourceTree = ""; };
OBJ_169 /* DoneIconInstructions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoneIconInstructions.swift; sourceTree = ""; };
OBJ_17 /* CLDNResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDNResult.swift; sourceTree = ""; };
OBJ_170 /* RatioLockedIconInstructions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RatioLockedIconInstructions.swift; sourceTree = ""; };
OBJ_171 /* RatioOpenedIconInstructions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RatioOpenedIconInstructions.swift; sourceTree = ""; };
OBJ_172 /* RotateIconInstructions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RotateIconInstructions.swift; sourceTree = ""; };
OBJ_174 /* CLDDisplayLinkObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDDisplayLinkObserver.swift; sourceTree = ""; };
OBJ_175 /* CLDVideoControlsState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDVideoControlsState.swift; sourceTree = ""; };
OBJ_176 /* CLDVideoControlsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDVideoControlsView.swift; sourceTree = ""; };
OBJ_177 /* CLDVideoHiddenAndPausedState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDVideoHiddenAndPausedState.swift; sourceTree = ""; };
OBJ_178 /* CLDVideoHiddenAndPlayingState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDVideoHiddenAndPlayingState.swift; sourceTree = ""; };
OBJ_179 /* CLDVideoPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDVideoPlayerView.swift; sourceTree = ""; };
OBJ_18 /* CLDNSessionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDNSessionDelegate.swift; sourceTree = ""; };
OBJ_180 /* CLDVideoShownAndPausedState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDVideoShownAndPausedState.swift; sourceTree = ""; };
OBJ_181 /* CLDVideoShownAndPlayingState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDVideoShownAndPlayingState.swift; sourceTree = ""; };
OBJ_182 /* CLDVideoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDVideoView.swift; sourceTree = ""; };
OBJ_184 /* CLDWidgetEditViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDWidgetEditViewController.swift; sourceTree = ""; };
OBJ_185 /* CLDWidgetPreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDWidgetPreviewViewController.swift; sourceTree = ""; };
OBJ_186 /* CLDWidgetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDWidgetViewController.swift; sourceTree = ""; };
OBJ_188 /* CLDUploader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDUploader.swift; sourceTree = ""; };
OBJ_19 /* CLDNSessionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDNSessionManager.swift; sourceTree = ""; };
OBJ_190 /* CLDPreprocessChain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDPreprocessChain.swift; sourceTree = ""; };
OBJ_192 /* CLDUploadRequestParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDUploadRequestParams.swift; sourceTree = ""; };
OBJ_194 /* CLDDefaultUploadRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDDefaultUploadRequest.swift; sourceTree = ""; };
OBJ_195 /* CLDUploadRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDUploadRequest.swift; sourceTree = ""; };
OBJ_196 /* CLDUploadRequestWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDUploadRequestWrapper.swift; sourceTree = ""; };
OBJ_198 /* CLDUploadResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDUploadResult.swift; sourceTree = ""; };
OBJ_20 /* CLDNTaskDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDNTaskDelegate.swift; sourceTree = ""; };
OBJ_200 /* CLDUrl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDUrl.swift; sourceTree = ""; };
OBJ_203 /* CLDNetworkAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDNetworkAdapter.swift; sourceTree = ""; };
OBJ_204 /* CLDDefaultNetworkAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDDefaultNetworkAdapter.swift; sourceTree = ""; };
OBJ_205 /* CLDNetworkCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDNetworkCoordinator.swift; sourceTree = ""; };
OBJ_207 /* CLDAsyncNetworkUploadRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDAsyncNetworkUploadRequest.swift; sourceTree = ""; };
OBJ_208 /* CLDErrorRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDErrorRequest.swift; sourceTree = ""; };
OBJ_209 /* CLDGenericNetworkRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDGenericNetworkRequest.swift; sourceTree = ""; };
OBJ_21 /* CLDNTimeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDNTimeline.swift; sourceTree = ""; };
OBJ_210 /* CLDNetworkDataRequestImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDNetworkDataRequestImpl.swift; sourceTree = ""; };
OBJ_211 /* CLDNetworkDownloadRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDNetworkDownloadRequest.swift; sourceTree = ""; };
OBJ_212 /* CLDNetworkUploadRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDNetworkUploadRequest.swift; sourceTree = ""; };
OBJ_214 /* CLDBuildParamsUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDBuildParamsUtils.swift; sourceTree = ""; };
OBJ_215 /* CLDCryptoUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDCryptoUtils.swift; sourceTree = ""; };
OBJ_216 /* CLDDictionaryUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDDictionaryUtils.swift; sourceTree = ""; };
OBJ_217 /* CLDError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDError.swift; sourceTree = ""; };
OBJ_218 /* CLDFileUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDFileUtils.swift; sourceTree = ""; };
OBJ_22 /* CLDNValidation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDNValidation.swift; sourceTree = ""; };
OBJ_221 /* CLDImageGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDImageGenerator.swift; sourceTree = ""; };
OBJ_222 /* CLDImageUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDImageUtils.swift; sourceTree = ""; };
OBJ_223 /* CLDJsonUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDJsonUtils.swift; sourceTree = ""; };
OBJ_224 /* CLDLogManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDLogManager.swift; sourceTree = ""; };
OBJ_225 /* CLDStringUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDStringUtils.swift; sourceTree = ""; };
OBJ_228 /* CLDTransformation+Ios.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CLDTransformation+Ios.swift"; sourceTree = ""; };
OBJ_229 /* ExtensionCLDDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionCLDDownloader.swift; sourceTree = ""; };
OBJ_23 /* DispatchQueue+Cloudinary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DispatchQueue+Cloudinary.swift"; sourceTree = ""; };
OBJ_230 /* UIButton+Cloudinary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIButton+Cloudinary.swift"; sourceTree = ""; };
OBJ_231 /* UIImageView+Cloudinary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImageView+Cloudinary.swift"; sourceTree = ""; };
OBJ_232 /* UIView+Cloudinary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Cloudinary.swift"; sourceTree = ""; };
OBJ_234 /* CLDFetchAssetRequestImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDFetchAssetRequestImpl.swift; sourceTree = ""; };
OBJ_235 /* CLDFetchImageRequestImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDFetchImageRequestImpl.swift; sourceTree = ""; };
OBJ_237 /* CLDResponsiveViewHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDResponsiveViewHelper.swift; sourceTree = ""; };
OBJ_238 /* CLDUIImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDUIImageView.swift; sourceTree = ""; };
OBJ_24 /* CLDCloudinary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDCloudinary.swift; sourceTree = ""; };
OBJ_240 /* CLDImagePreprocessChain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDImagePreprocessChain.swift; sourceTree = ""; };
OBJ_241 /* CLDPreprocessHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDPreprocessHelpers.swift; sourceTree = ""; };
OBJ_245 /* tools */ = {isa = PBXFileReference; lastKnownFileType = folder; path = tools; sourceTree = SOURCE_ROOT; };
OBJ_246 /* Example */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Example; sourceTree = SOURCE_ROOT; };
OBJ_247 /* Cloudinary */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Cloudinary; sourceTree = SOURCE_ROOT; };
OBJ_248 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; };
OBJ_249 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; };
OBJ_25 /* CLDCompatibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDCompatibility.swift; sourceTree = ""; };
OBJ_250 /* Cloudinary.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = Cloudinary.podspec; sourceTree = ""; };
OBJ_251 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
OBJ_26 /* CLDConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDConfiguration.swift; sourceTree = ""; };
OBJ_27 /* CLDEagerTransformation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDEagerTransformation.swift; sourceTree = ""; };
OBJ_28 /* CLDResponsiveParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLDResponsiveParams.swift; sourceTree = ""; };
OBJ_6 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = Package.swift; sourceTree = "