From 50eaa5df0d969b78933db89102d06f94c98dfb59 Mon Sep 17 00:00:00 2001 From: Svend Date: Thu, 13 Aug 2020 21:17:59 +0800 Subject: [PATCH 1/3] =?UTF-8?q?Amazon=20S3=20=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构 Amazon S3。现支持第三方兼容 S3 协议服务 --- uPic.xcodeproj/project.pbxproj | 107 ++++++++------- .../xcshareddata/swiftpm/Package.resolved | 70 ++++++++++ .../hosts/host_icon_s3.imageset/Contents.json | 56 ++++++++ .../host_icon_s3.imageset/amazon-dark.png | Bin 0 -> 2795 bytes .../host_icon_s3.imageset/amazon-dark@2x.png | Bin 0 -> 6210 bytes .../host_icon_s3.imageset/amazon-dark@3x.png | Bin 0 -> 10063 bytes .../host_icon_s3.imageset/amazon-light.png | Bin 0 -> 3460 bytes .../host_icon_s3.imageset/amazon-light@2x.png | Bin 0 -> 7453 bytes .../host_icon_s3.imageset/amazon-light@3x.png | Bin 0 -> 11733 bytes .../Models/Amazon_S3/AmazonS3HostConfig.swift | 72 ---------- uPic/Models/Amazon_S3/AmazonS3Uploader.swift | 128 ------------------ uPic/Models/Amazon_S3/AmazonS3Util.swift | 62 --------- uPic/Models/BaseUploader.swift | 20 +-- uPic/Models/HostConfig.swift | 13 +- uPic/Models/HostType.swift | 7 +- uPic/Models/Minio/MinioUploader.swift | 112 --------------- uPic/Models/Minio/MinioUtil.swift | 22 --- .../S3HostConfig.swift} | 27 ++-- .../S3Region.swift} | 37 ++--- uPic/Models/S3/S3Uploader.swift | 100 ++++++++++++++ uPic/Models/S3/S3Util.swift | 27 ++++ .../ConfigView/ConfigView.swift | 8 +- .../ConfigView/Views/MinioConfigView.swift | 110 --------------- ...nS3ConfigView.swift => S3ConfigView.swift} | 94 +++++++++++-- uPic/en.lproj/Localizable.strings | 3 + uPic/zh-Hans.lproj/Localizable.strings | 3 + uPic/zh-Hant.lproj/Localizable.strings | 3 + 27 files changed, 456 insertions(+), 625 deletions(-) create mode 100644 uPic.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 uPic/Assets.xcassets/hosts/host_icon_s3.imageset/Contents.json create mode 100644 uPic/Assets.xcassets/hosts/host_icon_s3.imageset/amazon-dark.png create mode 100644 uPic/Assets.xcassets/hosts/host_icon_s3.imageset/amazon-dark@2x.png create mode 100644 uPic/Assets.xcassets/hosts/host_icon_s3.imageset/amazon-dark@3x.png create mode 100644 uPic/Assets.xcassets/hosts/host_icon_s3.imageset/amazon-light.png create mode 100644 uPic/Assets.xcassets/hosts/host_icon_s3.imageset/amazon-light@2x.png create mode 100644 uPic/Assets.xcassets/hosts/host_icon_s3.imageset/amazon-light@3x.png delete mode 100644 uPic/Models/Amazon_S3/AmazonS3HostConfig.swift delete mode 100644 uPic/Models/Amazon_S3/AmazonS3Uploader.swift delete mode 100644 uPic/Models/Amazon_S3/AmazonS3Util.swift delete mode 100644 uPic/Models/Minio/MinioUploader.swift delete mode 100644 uPic/Models/Minio/MinioUtil.swift rename uPic/Models/{Minio/MinioHostConfig.swift => S3/S3HostConfig.swift} (75%) rename uPic/Models/{Amazon_S3/AmazonS3Region.swift => S3/S3Region.swift} (78%) create mode 100644 uPic/Models/S3/S3Uploader.swift create mode 100644 uPic/Models/S3/S3Util.swift delete mode 100644 uPic/PreferencesWindow/ConfigView/Views/MinioConfigView.swift rename uPic/PreferencesWindow/ConfigView/Views/{AmazonS3ConfigView.swift => S3ConfigView.swift} (58%) diff --git a/uPic.xcodeproj/project.pbxproj b/uPic.xcodeproj/project.pbxproj index a2be7b3..8995b07 100644 --- a/uPic.xcodeproj/project.pbxproj +++ b/uPic.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 52; objects = { /* Begin PBXBuildFile section */ @@ -55,7 +55,6 @@ 1660FCBD22C11C7800372950 /* TencentRegion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1660FCB622C11BA000372950 /* TencentRegion.swift */; }; 1660FCBF22C1211900372950 /* TencentConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1660FCBE22C1211800372950 /* TencentConfigView.swift */; }; 1662AC7122C0AC53003AC924 /* AliyunConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1662AC7022C0AC52003AC924 /* AliyunConfigView.swift */; }; - 1662AC7322C0BBF7003AC924 /* AmazonS3Region.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1662AC7222C0BBF7003AC924 /* AmazonS3Region.swift */; }; 166B4A5322B9CB4D001288ED /* UpYunConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 166B4A5222B9CB4D001288ED /* UpYunConfigView.swift */; }; 166B4A5722B9D118001288ED /* PreferencesNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 166B4A5622B9D118001288ED /* PreferencesNotifier.swift */; }; 1672762322AFF655007299C3 /* Host.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1672762222AFF655007299C3 /* Host.swift */; }; @@ -65,7 +64,6 @@ 167620ED230819C0008F8363 /* ImgurHostConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 167620EC230819A9008F8363 /* ImgurHostConfig.swift */; }; 167620EE23081B0C008F8363 /* ImgurUploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 167620EB2308199E008F8363 /* ImgurUploader.swift */; }; 167620F023081DC0008F8363 /* ImgurConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 167620EF23081DC0008F8363 /* ImgurConfigView.swift */; }; - 167D08A822ED88E7000F3BC0 /* AmazonS3ConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 167D08A622ED88C2000F3BC0 /* AmazonS3ConfigView.swift */; }; 167D08AA22ED8A27000F3BC0 /* AliyunHostConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 167D08A922ED8A27000F3BC0 /* AliyunHostConfig.swift */; }; 167D08AC22ED8A4B000F3BC0 /* AliyunRegion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 167D08AB22ED8A4B000F3BC0 /* AliyunRegion.swift */; }; 167D08AE22ED8A58000F3BC0 /* AliyunUploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 167D08AD22ED8A58000F3BC0 /* AliyunUploader.swift */; }; @@ -93,16 +91,19 @@ 169F073A22AF4549008E8525 /* AboutPreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 169F073622AF4549008E8525 /* AboutPreferencesViewController.swift */; }; 169F073B22AF53DE008E8525 /* Preferences.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 169F073D22AF53DE008E8525 /* Preferences.storyboard */; }; 169F074422AF7A3D008E8525 /* StatusMenuController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 169F074322AF7A3D008E8525 /* StatusMenuController.swift */; }; + 16A1C17124E3E7B40097018A /* S3 in Frameworks */ = {isa = PBXBuildFile; productRef = 16A1C17024E3E7B40097018A /* S3 */; }; + 16A1C17324E3E7B40097018A /* S3Control in Frameworks */ = {isa = PBXBuildFile; productRef = 16A1C17224E3E7B40097018A /* S3Control */; }; 16A6DC5822AA375700813706 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16A6DC5722AA375700813706 /* AppDelegate.swift */; }; + 16AFDB3024E3EA670008E5A7 /* S3Uploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16AFDB2F24E3EA670008E5A7 /* S3Uploader.swift */; }; + 16AFDB3224E5094A0008E5A7 /* S3HostConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16AFDB3124E5094A0008E5A7 /* S3HostConfig.swift */; }; + 16AFDB3424E50DD30008E5A7 /* S3Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16AFDB3324E50DD30008E5A7 /* S3Util.swift */; }; + 16AFDB3624E50F0A0008E5A7 /* S3ConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16AFDB3524E50F0A0008E5A7 /* S3ConfigView.swift */; }; + 16AFDB3824E50F450008E5A7 /* S3Region.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16AFDB3724E50F450008E5A7 /* S3Region.swift */; }; 16B4F0BC23ADCFAC00846BD3 /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 164745EF22B65FE900F9575D /* String+Extension.swift */; }; 16B4F0BE23ADD09300846BD3 /* String+Crypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16B4F0BD23ADCFFF00846BD3 /* String+Crypto.swift */; }; 16BDDDFF22EA96AE0080E467 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 16BDDDFE22EA96AE0080E467 /* Assets.xcassets */; }; 16BDDE0322EAA2920080E467 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 16BDDE0222EAA2920080E467 /* Assets.xcassets */; }; 16C176F523A208A80089B933 /* NSImage+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16C176F423A208A80089B933 /* NSImage+Extension.swift */; }; - 16C31AF5244315790079EB4A /* MinioUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16C31AF4244315790079EB4A /* MinioUtil.swift */; }; - 16C31AF7244315BD0079EB4A /* MinioHostConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16C31AF6244315BD0079EB4A /* MinioHostConfig.swift */; }; - 16C31AF8244316280079EB4A /* MinioUploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16C31AF3244315270079EB4A /* MinioUploader.swift */; }; - 16C31AFA244317140079EB4A /* MinioConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16C31AF9244317140079EB4A /* MinioConfigView.swift */; }; 16CD34F323BC87ED005B52F2 /* SmmsHostConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16CD34F223BC87ED005B52F2 /* SmmsHostConfig.swift */; }; 16CD34F523BC8855005B52F2 /* SmmsVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16CD34F423BC8855005B52F2 /* SmmsVersion.swift */; }; 16CD34F723BC88E2005B52F2 /* SmmsConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16CD34F623BC88E2005B52F2 /* SmmsConfigView.swift */; }; @@ -140,9 +141,6 @@ 4BC11BDE238CFD53001641A6 /* HistoryThumbnailTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC11BDD238CFD53001641A6 /* HistoryThumbnailTimer.swift */; }; 4BEFD811238B738E00BBE64D /* HistoryThumbnailConstant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEFD810238B738E00BBE64D /* HistoryThumbnailConstant.swift */; }; 4BEFD813238BB2F200BBE64D /* HistoryPreviewCustomScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEFD812238BB2F200BBE64D /* HistoryPreviewCustomScrollView.swift */; }; - 68BBB3EF25C92DCAE216A4DF /* AmazonS3Uploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68BBB5C4550545707614BE4D /* AmazonS3Uploader.swift */; }; - 68BBB6E5FFBB050D05107413 /* AmazonS3HostConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68BBB8CF2838A67CA1AD438F /* AmazonS3HostConfig.swift */; }; - 68BBB99780D7F4586458D4F5 /* AmazonS3Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68BBB2F5FAEEFFF55935F022 /* AmazonS3Util.swift */; }; 8E879700A97E9294450D524F /* Pods_uPic.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A116EF79D38D9092D34EFCF5 /* Pods_uPic.framework */; }; 968ECC07240DFCF900B2D78C /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 968ECC09240DFCF900B2D78C /* InfoPlist.strings */; }; /* End PBXBuildFile section */ @@ -233,7 +231,6 @@ 1660FCB922C11BA000372950 /* TencentHostConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TencentHostConfig.swift; sourceTree = ""; }; 1660FCBE22C1211800372950 /* TencentConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TencentConfigView.swift; sourceTree = ""; }; 1662AC7022C0AC52003AC924 /* AliyunConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AliyunConfigView.swift; sourceTree = ""; }; - 1662AC7222C0BBF7003AC924 /* AmazonS3Region.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AmazonS3Region.swift; sourceTree = ""; }; 166B4A5222B9CB4D001288ED /* UpYunConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpYunConfigView.swift; sourceTree = ""; }; 166B4A5622B9D118001288ED /* PreferencesNotifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesNotifier.swift; sourceTree = ""; }; 1672762222AFF655007299C3 /* Host.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Host.swift; sourceTree = ""; }; @@ -243,7 +240,6 @@ 167620EB2308199E008F8363 /* ImgurUploader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImgurUploader.swift; sourceTree = ""; }; 167620EC230819A9008F8363 /* ImgurHostConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImgurHostConfig.swift; sourceTree = ""; }; 167620EF23081DC0008F8363 /* ImgurConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImgurConfigView.swift; sourceTree = ""; }; - 167D08A622ED88C2000F3BC0 /* AmazonS3ConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AmazonS3ConfigView.swift; sourceTree = ""; }; 167D08A922ED8A27000F3BC0 /* AliyunHostConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AliyunHostConfig.swift; sourceTree = ""; }; 167D08AB22ED8A4B000F3BC0 /* AliyunRegion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AliyunRegion.swift; sourceTree = ""; }; 167D08AD22ED8A58000F3BC0 /* AliyunUploader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AliyunUploader.swift; sourceTree = ""; }; @@ -274,14 +270,15 @@ 16A6DC5722AA375700813706 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 16A6DC6022AA375800813706 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 16A6DC6122AA375800813706 /* uPic.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = uPic.entitlements; sourceTree = ""; }; + 16AFDB2F24E3EA670008E5A7 /* S3Uploader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = S3Uploader.swift; sourceTree = ""; }; + 16AFDB3124E5094A0008E5A7 /* S3HostConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = S3HostConfig.swift; sourceTree = ""; }; + 16AFDB3324E50DD30008E5A7 /* S3Util.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = S3Util.swift; sourceTree = ""; }; + 16AFDB3524E50F0A0008E5A7 /* S3ConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = S3ConfigView.swift; sourceTree = ""; }; + 16AFDB3724E50F450008E5A7 /* S3Region.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = S3Region.swift; sourceTree = ""; }; 16B4F0BD23ADCFFF00846BD3 /* String+Crypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Crypto.swift"; sourceTree = ""; }; 16BDDDFE22EA96AE0080E467 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 16BDDE0222EAA2920080E467 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 16C176F423A208A80089B933 /* NSImage+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSImage+Extension.swift"; sourceTree = ""; }; - 16C31AF3244315270079EB4A /* MinioUploader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MinioUploader.swift; sourceTree = ""; }; - 16C31AF4244315790079EB4A /* MinioUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MinioUtil.swift; sourceTree = ""; }; - 16C31AF6244315BD0079EB4A /* MinioHostConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MinioHostConfig.swift; sourceTree = ""; }; - 16C31AF9244317140079EB4A /* MinioConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MinioConfigView.swift; sourceTree = ""; }; 16CD34F223BC87ED005B52F2 /* SmmsHostConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmmsHostConfig.swift; sourceTree = ""; }; 16CD34F423BC8855005B52F2 /* SmmsVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmmsVersion.swift; sourceTree = ""; }; 16CD34F623BC88E2005B52F2 /* SmmsConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmmsConfigView.swift; sourceTree = ""; }; @@ -320,9 +317,6 @@ 4BC11BDD238CFD53001641A6 /* HistoryThumbnailTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryThumbnailTimer.swift; sourceTree = ""; }; 4BEFD810238B738E00BBE64D /* HistoryThumbnailConstant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryThumbnailConstant.swift; sourceTree = ""; }; 4BEFD812238BB2F200BBE64D /* HistoryPreviewCustomScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryPreviewCustomScrollView.swift; sourceTree = ""; }; - 68BBB2F5FAEEFFF55935F022 /* AmazonS3Util.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AmazonS3Util.swift; sourceTree = ""; }; - 68BBB5C4550545707614BE4D /* AmazonS3Uploader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AmazonS3Uploader.swift; sourceTree = ""; }; - 68BBB8CF2838A67CA1AD438F /* AmazonS3HostConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AmazonS3HostConfig.swift; sourceTree = ""; }; 96319C6E24CE7CEB004A6E62 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Main.strings; sourceTree = ""; }; 968ECC08240DFCF900B2D78C /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 968ECC0A240DFCFA00B2D78C /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; @@ -337,6 +331,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 16A1C17124E3E7B40097018A /* S3 in Frameworks */, + 16A1C17324E3E7B40097018A /* S3Control in Frameworks */, 8E879700A97E9294450D524F /* Pods_uPic.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -446,11 +442,10 @@ 1646B80F22C7C485009271DF /* Views */ = { isa = PBXGroup; children = ( - 16C31AF9244317140079EB4A /* MinioConfigView.swift */, + 16AFDB3524E50F0A0008E5A7 /* S3ConfigView.swift */, 161539142408F0E200CC662F /* LskyProConfigView.swift */, 16D20B5B238392C9006D8D01 /* BaiduConfigView.swift */, 167620EF23081DC0008F8363 /* ImgurConfigView.swift */, - 167D08A622ED88C2000F3BC0 /* AmazonS3ConfigView.swift */, 1657019E22C897A400C57EE9 /* WeiboConfigView.swift */, 1662AC7022C0AC52003AC924 /* AliyunConfigView.swift */, 161C3BE422C4870B0092114F /* CustomConfigView.swift */, @@ -510,6 +505,7 @@ 1672762122AFF63A007299C3 /* Models */ = { isa = PBXGroup; children = ( + 16AFDB2E24E3EA340008E5A7 /* S3 */, 1602ED9622ADEFB200AA8638 /* BaseUploader.swift */, 1675EC5222FB38240038DA33 /* BaseUploaderUtil.swift */, 1672762222AFF655007299C3 /* Host.swift */, @@ -517,14 +513,12 @@ 167D7F4A22B4D31300DD0A7A /* HostType.swift */, 16F04EE923BCC49000CCA2FE /* OutputType.swift */, 68BBB87F2C3AEBDF2C914131 /* Aliyun */, - 167D08A522ED8132000F3BC0 /* Amazon_S3 */, 16D20B522383887E006D8D01 /* Baidu */, 161C3BDF22C4830A0092114F /* Custom */, 1646B80222C7931E009271DF /* Gitee */, 167DDBE922C76F3B00B03357 /* Github */, 167620EA2308191A008F8363 /* Imgur */, 1615390E2408E7DE00CC662F /* LskyPro */, - 16C31AF22443150D0079EB4A /* Minio */, 1690E7E522BF113700FC81F8 /* Qiniu */, 167D7F4E22B4DB2500DD0A7A /* Smms */, 1660FCB522C11A9F00372950 /* Tencent */, @@ -558,17 +552,6 @@ path = Imgur; sourceTree = ""; }; - 167D08A522ED8132000F3BC0 /* Amazon_S3 */ = { - isa = PBXGroup; - children = ( - 68BBB8CF2838A67CA1AD438F /* AmazonS3HostConfig.swift */, - 68BBB5C4550545707614BE4D /* AmazonS3Uploader.swift */, - 68BBB2F5FAEEFFF55935F022 /* AmazonS3Util.swift */, - 1662AC7222C0BBF7003AC924 /* AmazonS3Region.swift */, - ); - path = Amazon_S3; - sourceTree = ""; - }; 167D7F4E22B4DB2500DD0A7A /* Smms */ = { isa = PBXGroup; children = ( @@ -660,14 +643,15 @@ path = Views; sourceTree = ""; }; - 16C31AF22443150D0079EB4A /* Minio */ = { + 16AFDB2E24E3EA340008E5A7 /* S3 */ = { isa = PBXGroup; children = ( - 16C31AF3244315270079EB4A /* MinioUploader.swift */, - 16C31AF4244315790079EB4A /* MinioUtil.swift */, - 16C31AF6244315BD0079EB4A /* MinioHostConfig.swift */, + 16AFDB3124E5094A0008E5A7 /* S3HostConfig.swift */, + 16AFDB2F24E3EA670008E5A7 /* S3Uploader.swift */, + 16AFDB3324E50DD30008E5A7 /* S3Util.swift */, + 16AFDB3724E50F450008E5A7 /* S3Region.swift */, ); - path = Minio; + path = S3; sourceTree = ""; }; 16CD34F823BC8966005B52F2 /* Swime */ = { @@ -789,6 +773,10 @@ 16F7467822E994A300480A62 /* PBXTargetDependency */, ); name = uPic; + packageProductDependencies = ( + 16A1C17024E3E7B40097018A /* S3 */, + 16A1C17224E3E7B40097018A /* S3Control */, + ); productName = uPic; productReference = 16EC9A0222ABBBB4001B6C89 /* uPic.app */; productType = "com.apple.product-type.application"; @@ -856,6 +844,9 @@ "zh-Hant", ); mainGroup = 16A6DC4B22AA375700813706; + packageReferences = ( + 16A1C16F24E3E7B40097018A /* XCRemoteSwiftPackageReference "aws-sdk-swift" */, + ); productRefGroup = 16A6DC4B22AA375700813706; projectDirPath = ""; projectRoot = ""; @@ -947,6 +938,7 @@ 167D08AC22ED8A4B000F3BC0 /* AliyunRegion.swift in Sources */, 1685AA3522DEC943008FBF1D /* FlippedView.swift in Sources */, 16068C7822AECB34004D39B7 /* PreferencesWindowController.swift in Sources */, + 16AFDB3024E3EA670008E5A7 /* S3Uploader.swift in Sources */, 16CD34F323BC87ED005B52F2 /* SmmsHostConfig.swift in Sources */, 16D20B58238390F2006D8D01 /* BaiduUploader.swift in Sources */, 1675EC5322FB38240038DA33 /* BaseUploaderUtil.swift in Sources */, @@ -957,6 +949,7 @@ 16F04EE523BCC45F00CCA2FE /* CommandLine.swift in Sources */, 1602ED9922ADF43800AA8638 /* SmmsUploader.swift in Sources */, 164745F022B65FE900F9575D /* String+Extension.swift in Sources */, + 16AFDB3224E5094A0008E5A7 /* S3HostConfig.swift in Sources */, 4B2B3354236BC5AB00C9F2CA /* NSButtonExtension.swift in Sources */, 166B4A5322B9CB4D001288ED /* UpYunConfigView.swift in Sources */, 166B4A5722B9D118001288ED /* PreferencesNotifier.swift in Sources */, @@ -978,7 +971,6 @@ 1618D1A822E99E2100831601 /* UploadNotifier.swift in Sources */, 4B09F11423695F8D000208BD /* HistoryThumbnailContentView.swift in Sources */, 163632EF22B2749000805E7F /* NSTextFieldCell+VerticallyCentered.swift in Sources */, - 167D08A822ED88E7000F3BC0 /* AmazonS3ConfigView.swift in Sources */, 4B2B3356236C3B6400C9F2CA /* NSViewExtension.swift in Sources */, 164745EB22B618D700F9575D /* HostPreferencesViewController.swift in Sources */, 161C3BE322C483560092114F /* CustomHostConfig.swift in Sources */, @@ -1005,7 +997,6 @@ 16995A3622B6026500B1F923 /* UpYunUtil.swift in Sources */, 167DDBED22C76F6600B03357 /* GithubHostConfig.swift in Sources */, 1646B80822C79349009271DF /* GiteeUtil.swift in Sources */, - 16C31AF5244315790079EB4A /* MinioUtil.swift in Sources */, 168A3D1523D1AACA001BF1D9 /* RequestMethods.swift in Sources */, 1660FCBF22C1211900372950 /* TencentConfigView.swift in Sources */, 1646B80A22C79483009271DF /* GiteeConfigView.swift in Sources */, @@ -1017,6 +1008,7 @@ 1657019F22C897A400C57EE9 /* WeiboConfigView.swift in Sources */, 161539132408EC7D00CC662F /* LskyProHostConfig.swift in Sources */, 16F04EE723BCC45F00CCA2FE /* StringExtensions.swift in Sources */, + 16AFDB3624E50F0A0008E5A7 /* S3ConfigView.swift in Sources */, 4BC11BDE238CFD53001641A6 /* HistoryThumbnailTimer.swift in Sources */, 1662AC7122C0AC53003AC924 /* AliyunConfigView.swift in Sources */, 1675516A22ACAA5900D3EB6F /* AppPublic.swift in Sources */, @@ -1031,14 +1023,12 @@ 16068C7522AEC1D1004D39B7 /* PreferencesViewController.swift in Sources */, 161C3BE122C483380092114F /* CustomUploader.swift in Sources */, 169F073A22AF4549008E8525 /* AboutPreferencesViewController.swift in Sources */, - 16C31AF8244316280079EB4A /* MinioUploader.swift in Sources */, 4BEFD811238B738E00BBE64D /* HistoryThumbnailConstant.swift in Sources */, 1690E7E722BF174300FC81F8 /* QiniuHostConfig.swift in Sources */, 1675516222ABF80300D3EB6F /* NSDragingInfoExt.swift in Sources */, - 1662AC7322C0BBF7003AC924 /* AmazonS3Region.swift in Sources */, 16F04EEC23BCC4B200CCA2FE /* FileManagerExtension.swift in Sources */, - 16C31AFA244317140079EB4A /* MinioConfigView.swift in Sources */, 1683573C22DB0D0800985B10 /* CustomConfigSheetController.swift in Sources */, + 16AFDB3824E50F450008E5A7 /* S3Region.swift in Sources */, 16CD34FC23BC8966005B52F2 /* MimeType.swift in Sources */, 16F04EE623BCC45F00CCA2FE /* Option.swift in Sources */, 169F074422AF7A3D008E8525 /* StatusMenuController.swift in Sources */, @@ -1047,20 +1037,17 @@ 16068C7C22AECD9F004D39B7 /* Constants.swift in Sources */, 16ECAA962413B6C200F9236B /* FinderUtil.swift in Sources */, 161C3BE522C4870C0092114F /* CustomConfigView.swift in Sources */, - 68BBB6E5FFBB050D05107413 /* AmazonS3HostConfig.swift in Sources */, - 68BBB3EF25C92DCAE216A4DF /* AmazonS3Uploader.swift in Sources */, + 16AFDB3424E50DD30008E5A7 /* S3Util.swift in Sources */, 1660FCBC22C11C7500372950 /* TencentUtil.swift in Sources */, 1660FCBA22C11C2300372950 /* TencentHostConfig.swift in Sources */, 16248E32230673B6002131BB /* NotificationExt.swift in Sources */, 16F04EE423BCC45F00CCA2FE /* Console.swift in Sources */, - 16C31AF7244315BD0079EB4A /* MinioHostConfig.swift in Sources */, 167DDBEF22C7722200B03357 /* GithubUtil.swift in Sources */, 16F04EEE23BCC4C500CCA2FE /* URLSchemeExt.swift in Sources */, 16D20B5C238392C9006D8D01 /* BaiduConfigView.swift in Sources */, 167620F023081DC0008F8363 /* ImgurConfigView.swift in Sources */, 4BBA45BB235F081C0079F253 /* HistoryThumbnailItem.swift in Sources */, 1685AA3722DEEE6C008FBF1D /* CustomHostUtil.swift in Sources */, - 68BBB99780D7F4586458D4F5 /* AmazonS3Util.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1393,6 +1380,30 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 16A1C16F24E3E7B40097018A /* XCRemoteSwiftPackageReference "aws-sdk-swift" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/swift-aws/aws-sdk-swift.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 4.7.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 16A1C17024E3E7B40097018A /* S3 */ = { + isa = XCSwiftPackageProductDependency; + package = 16A1C16F24E3E7B40097018A /* XCRemoteSwiftPackageReference "aws-sdk-swift" */; + productName = S3; + }; + 16A1C17224E3E7B40097018A /* S3Control */ = { + isa = XCSwiftPackageProductDependency; + package = 16A1C16F24E3E7B40097018A /* XCRemoteSwiftPackageReference "aws-sdk-swift" */; + productName = S3Control; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 16A6DC4C22AA375700813706 /* Project object */; } diff --git a/uPic.xcworkspace/xcshareddata/swiftpm/Package.resolved b/uPic.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..56b4ea2 --- /dev/null +++ b/uPic.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,70 @@ +{ + "object": { + "pins": [ + { + "package": "AWSSDKSwift", + "repositoryURL": "https://github.com/swift-aws/aws-sdk-swift.git", + "state": { + "branch": null, + "revision": "6345f0a548de02eac0ba5dca217e1857d0a77467", + "version": "4.7.0" + } + }, + { + "package": "AWSSDKSwiftCore", + "repositoryURL": "https://github.com/swift-aws/aws-sdk-swift-core.git", + "state": { + "branch": null, + "revision": "2a36809260bcfc38222b85808978c719199612af", + "version": "4.6.0" + } + }, + { + "package": "HypertextApplicationLanguage", + "repositoryURL": "https://github.com/swift-aws/HypertextApplicationLanguage.git", + "state": { + "branch": null, + "revision": "aa2c9141d491682f17b2310aed17b9adfc006256", + "version": "1.1.1" + } + }, + { + "package": "INIParser", + "repositoryURL": "https://github.com/swift-aws/Perfect-INIParser.git", + "state": { + "branch": null, + "revision": "42de0efc7a01105e19b80d533d3d282a98277f6c", + "version": "3.0.3" + } + }, + { + "package": "swift-nio", + "repositoryURL": "https://github.com/apple/swift-nio.git", + "state": { + "branch": null, + "revision": "acf5465b5e7fb9aeda54a34d16fb44c31a399715", + "version": "2.20.2" + } + }, + { + "package": "swift-nio-ssl", + "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", + "state": { + "branch": null, + "revision": "8a137b72a9339f295bc8bb95cd2fafe207f1df0d", + "version": "2.9.0" + } + }, + { + "package": "swift-nio-transport-services", + "repositoryURL": "https://github.com/apple/swift-nio-transport-services.git", + "state": { + "branch": null, + "revision": "d40a5e34e5b35f4f961cb34aeb2e0a02f42a945f", + "version": "1.8.0" + } + } + ] + }, + "version": 1 +} diff --git a/uPic/Assets.xcassets/hosts/host_icon_s3.imageset/Contents.json b/uPic/Assets.xcassets/hosts/host_icon_s3.imageset/Contents.json new file mode 100644 index 0000000..140031d --- /dev/null +++ b/uPic/Assets.xcassets/hosts/host_icon_s3.imageset/Contents.json @@ -0,0 +1,56 @@ +{ + "images" : [ + { + "filename" : "amazon-light.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "amazon-dark.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "amazon-light@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "amazon-dark@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "amazon-light@3x.png", + "idiom" : "universal", + "scale" : "3x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "amazon-dark@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/uPic/Assets.xcassets/hosts/host_icon_s3.imageset/amazon-dark.png b/uPic/Assets.xcassets/hosts/host_icon_s3.imageset/amazon-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..ab2bb4d900ec71d1ee3ade56303bb9f6e4914e56 GIT binary patch literal 2795 zcmVPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91K%fHv1ONa40RR91KmY&$07g+lumAuGRY^oaRCoc^n|-VuMHRqj-+L6Q zz_YCtu|l4Kwt^L*E!Ze(tu@u^A7Zpk@`M} zIF3IGY*#E3S%QYx;!;NxO=oZ|+-F-9^DUMqbt)i)HLJb#NP{h6MT*c=S6hCtj;}Lu zVj&(f7Ybn|!Ou(R$Yi=KW_JU#tzf36LCX48mCcDyFm;!9Q#6r$BqMDJZC2ybZu4-w z5aU+9A-FBU0kPd~<$)Z)_?FcgZyYw0O~2)(HEtHzINUlF3Lz+b+$w1q%adC3gE(@V zwUWkzO45Mf(@Hoq2A1y{uxYmZV=Y4cVZl)xRXm~n*NPQFV5#*cwc_uBe1oMkwG4|L zw@gETKz6w_iB9f9fH_8++cj%yI6Bw7$`XY&;r0j-(WkN-F zuOskdMNd<>AH_ggtOSho>I6U1@+;RU%}A}r@ky>?dbQ~WQP8x)b9^@kw>rw7L3y4s zP>iZjQ%~?tQ2aN#7BUg2#upX-Glid_@C40V2ReK;(WlXfhUHsB-Xuz+HfDu=b*2lh zbrgOrJSn1VYi%fs*O|+rRc4K57U4W-hIz~!oyhcna}Rm{~r<{fnRcR79;gAwTi z{vjh%p{y>mSGgT$M^|td68UyzMFdcsUYS3EU0r1&T^;@@dE6_li7}w3P->G#4F1w7WbJk1?fW^ax@Ga)0;8 zma;N)nIr9UF7sK&`MA#Mj;3zdAB!6*(8#UZenT;^dqOsA!)9jZ#&Hg}lAI9)r@C}KB z$jr@T0KpwJ9+!HL%)MrnS;RB1c8|qI{3G*v^P;%}?Nd^leP)&!HBUe%Z_1eftWf-Y za|A-qLBGQCI;wlkyl5pEpzz4Pf`D!z(+>*m4L}-n;jLqm88JgBenRs-_+~6#j`5#* z+B#=(5z52qN?;ox<=?;qz+mUr3oPK^(?FFX3;y7(UEm_zgu!R3cy?rpSCYzI;WdEK zZOG12@|DrD3CJ1>rVHn0~UjGe?Y#J9SR_% zxY%tV{l9T}mZt4+i`S}Aya@0XZ3uxuuh0b3-T&0;VI2KQ6E?iq9xvC=JU8sAV1mJx zOh8pr_$;!y?cj08y*!jcYscX3wyc7WD2iOQJ%;Rb=zhf$be}v8DM9Csyq_c!p>qt` zQJ2ScTA)t_q&%K7OORS~Jcj$}(Qt`PZ!@c|^J!Sj+Tt|ozX6|Rqw&!`)`h1q5Hr|7 z{DoIjG23y@Dr*6&tow1Si@ex80z^GL=dNPadZ#Q<5iZ@+(N-$qW69>l2K;;;`43QE zqxE`NMlFVUMZz34ecgg*CZ>y4D3hVc|oce5T zQRBZoW@(k7$>=D8h3IPfzcyt>=3NA`DWUt!l;~{peC8sQt#5Y2_aUHnKSX{D5r3K9 z+>s~;e#o3WL(a#^hjJp38AShw<9iX0lZ|8(GZTPS!q9~n`MRY$sv~n3j^A%>5WrnH zf1jnj0oX=_WtsgEOV_F+Glb*9X;|zP24Ed>eSDCyyn(C~o&Aks?K|uwTpE4S{L|`o zy=JnS44ebp1ITRIPI(li&jGEonXdPurb7ai1IGJ-gH<+Rc%WujmBcGuf+0%;CG+L) zfvF*xhWSrn@6>jlNTE3DzLBPO==^06e*wDHdGS2}{0ram_H*b80bCB$Dfb|Jx*IXP z9zf=c5ui@F&9&7vnkWZqMEYA~OHXVhc;q(Ju-kf_=vuFpBZ8a>$nj-a*?~Z%&W|VH zv@(NE`+y3oZM1N2#%GlYNbdW#_HBLk6z@L@uYyjlB2*?*I{{?|>$EeXGJ(DpkoDxj z*<&gR=%6Hkj?Kldyh1JkWChmI;}(5i$u6;c5*2ujB!CXf5#-gtnH7A#tVG&Q0G*Zt z<&8k&`*q#SP+KoTbpDG3`9jUk{~Y4ljUDBWyDTsA)1Eo-{a?Fkn|xv-5$Wmjk| zFb9y;pMco84QQe~hLL8z?V0Fm7{gg7Uo$q*VV@1z_3mCFf;GMsZRjx zj;`F7(k@0t&r!p^>wL>6{&0!r6fw zMij4cBD06#na-aT_9cQG#cV(s|xRkC#*tg{*_w%h>U~4&Ad`KBQq$nMfr-yu2do~@lTjrx&$#WREZCoLLqU}v8 z$S-j~c%{&}1X!VXy?KrKFNcG1a^z{*`8qOBA#)Q4t2c7nY|RTa!%2>Ack^GNF|!zp zONjhT{08Hv>g4dcbvW6YCsbPjD*wyeP9R%> zE#OZA@?WeGk~Km-2K3W5jVo?iQX6v&ypsSKQTdLbdXZ1eV+QPSvzo)v9io^F;!ES- zwnlsn-e=w#4VuhX{*@A-qaYdbCH^;Ywp{ZM5q=-Z{3=)8rgr_;PJru(4?AWumKVTZ zPEv}FuU*yZbZauc8-?|Bcq8*qV+SK0C&1SWzGID;b7%8;%zUu<$ec`arcqA;rUFL+ xduiJVe;4@91UJc(W;18}4{#apfq&4;{{VR(@LU@q5Lo~K002ovPDHLkV1fYZ8bAO5 literal 0 HcmV?d00001 diff --git a/uPic/Assets.xcassets/hosts/host_icon_s3.imageset/amazon-dark@2x.png b/uPic/Assets.xcassets/hosts/host_icon_s3.imageset/amazon-dark@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..faa3955c5c11cc2e7c402d8317f97ccc58379176 GIT binary patch literal 6210 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 literal 0 HcmV?d00001 diff --git a/uPic/Assets.xcassets/hosts/host_icon_s3.imageset/amazon-dark@3x.png b/uPic/Assets.xcassets/hosts/host_icon_s3.imageset/amazon-dark@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..468898367db4755cdb0539bf3c99297e8c155d8a GIT binary patch literal 10063 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 literal 0 HcmV?d00001 diff --git a/uPic/Assets.xcassets/hosts/host_icon_s3.imageset/amazon-light.png b/uPic/Assets.xcassets/hosts/host_icon_s3.imageset/amazon-light.png new file mode 100644 index 0000000000000000000000000000000000000000..64d7b22aefd62dc015fe9add18779ec1fee44b95 GIT binary patch literal 3460 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 String { - switch key { - case "region": - return "Region".localized - case "bucket": - return "Bucket".localized - case "accessKey": - return "Access Key".localized - case "secretKey": - return "Secret Key".localized - case "domain": - return "Domain".localized - case "saveKeyPath": - return "Save Key".localized - case "suffix": - return "URL suffix".localized - default: - return "" - } - } - - override func serialize() -> String { - var dict = Dictionary() - dict["region"] = self.region - dict["bucket"] = self.bucket - dict["accessKey"] = self.accessKey - dict["secretKey"] = self.secretKey - dict["domain"] = self.domain - dict["saveKeyPath"] = self.saveKeyPath - dict["suffix"] = self.suffix - - return JSON(dict).rawString()! - } - - static func deserialize(str: String?) -> AmazonS3HostConfig? { - let config = AmazonS3HostConfig() - guard let str = str else { - return config - } - let data = str.data(using: String.Encoding.utf8) - let json = try! JSON(data: data!) - config.region = json["region"].stringValue - config.bucket = json["bucket"].stringValue - config.accessKey = json["accessKey"].stringValue - config.secretKey = json["secretKey"].stringValue - config.domain = json["domain"].stringValue - config.saveKeyPath = json["saveKeyPath"].string - config.suffix = json["suffix"].stringValue - return config - } -} diff --git a/uPic/Models/Amazon_S3/AmazonS3Uploader.swift b/uPic/Models/Amazon_S3/AmazonS3Uploader.swift deleted file mode 100644 index 5dd5768..0000000 --- a/uPic/Models/Amazon_S3/AmazonS3Uploader.swift +++ /dev/null @@ -1,128 +0,0 @@ -// -// AmazonS3Uploader.swift -// uPic -// -// Created by Svend Jin on 2019/7/28. -// Copyright © 2019 Svend Jin. All rights reserved. -// - -import Cocoa -import Alamofire -import SwiftyXMLParser - -class AmazonS3Uploader: BaseUploader { - - static let shared = AmazonS3Uploader() - static let fileExtensions: [String] = [] - - func _upload(_ fileUrl: URL?, fileData: Data?, host: Host) { - guard let data = host.data else { - super.faild(errorMsg: "There is a problem with the map bed configuration, please check!".localized) - return - } - - super.start() - - let config = data as! AmazonS3HostConfig - - - let bucket = config.bucket - let accessKey = config.accessKey - let secretKey = config.secretKey - let domain = config.domain - let region = AmazonS3Region.formatRegion(config.region) - - let saveKeyPath = config.saveKeyPath - - let url = AmazonS3Util.computeUrl(bucket: bucket, region: region) - - if url.isEmpty { - super.faild(errorMsg: "There is a problem with the map bed configuration, please check!".localized) - return - } - - guard let configuration = BaseUploaderUtil.getSaveConfiguration(fileUrl, fileData, saveKeyPath) else { - super.faild(errorMsg: "Invalid file") - return - } - let retData = configuration["retData"] as? Data - let fileName = configuration["fileName"] as! String - let mimeType = configuration["mimeType"] as! String - let saveKey = configuration["saveKey"] as! String - - let suffix = BaseUploaderUtil._parseVariables(config.suffix, fileName, otherVariables: nil) - - - // MARK: 加密 policy - let iso_date = Date().format(dateFormat: "yyyyMMdd'T'HHmmss'Z'", timeZone: TimeZone(secondsFromGMT: 0)) - let short_date = Date().format(dateFormat: "yyyyMMdd", timeZone: TimeZone(secondsFromGMT: 0)) - - let credential = AmazonS3Util.getCredential(access_key: accessKey, short_date: short_date, region: region) - - var policyDict = Dictionary() - let conditions: [Any] = [ - ["acl": "public-read"], - ["bucket": bucket], - ["starts-with", "$key", ""], - ["x-amz-credential": credential], - ["x-amz-algorithm": AmazonS3Util.ALGORITHM], - ["X-amz-date": iso_date], - ["content-type": mimeType] // 如不手动设置 content-type , aws 默认会将文件的 content-type 设置为 binary/octet-stream 。访问时将会直接下载,而不是预览 - ] - policyDict["conditions"] = conditions - let policy = AmazonS3Util.getPolicy(policyDict: policyDict) - - let signature = AmazonS3Util.computeSignature(secret_key: secretKey, policy: policy, region: region, short_date: short_date) - - - func multipartFormDataGen(multipartFormData: MultipartFormData) { - multipartFormData.append(saveKey.data(using: .utf8)!, withName: "key") - multipartFormData.append("public-read".data(using: .utf8)!, withName: "acl") - multipartFormData.append(credential.data(using: .utf8)!, withName: "X-Amz-Credential") - multipartFormData.append(AmazonS3Util.ALGORITHM.data(using: .utf8)!, withName: "X-Amz-Algorithm") - multipartFormData.append(iso_date.data(using: .utf8)!, withName: "X-Amz-Date") - multipartFormData.append(policy.data(using: .utf8)!, withName: "policy") - multipartFormData.append(signature.data(using: .utf8)!, withName: "X-Amz-Signature") - // 如不手动设置 content-type , aws 默认会将文件的 content-type 设置为 binary/octet-stream 。访问时将会直接下载,而不是预览 - multipartFormData.append(mimeType.data(using: .utf8)!, withName: "content-type") - - if retData != nil { - multipartFormData.append(retData!, withName: "file", fileName: fileName, mimeType: mimeType) - } else if fileUrl != nil { - multipartFormData.append(fileUrl!, withName: "file", fileName: fileName, mimeType: mimeType) - } - } - - - AF.upload(multipartFormData: multipartFormDataGen, to: url).validate().uploadProgress { progress in - super.progress(percent: progress.fractionCompleted) - }.response(completionHandler: { response -> Void in - switch response.result { - case .success(_): - if domain.isEmpty { - super.completed(url: "\(url)/\(saveKey)\(suffix)", retData, fileUrl, fileName) - } else { - super.completed(url: "\(domain)/\(saveKey)\(suffix)", retData, fileUrl, fileName) - } - case .failure(let error): - var errorMessage = error.localizedDescription - if let data = response.data { - let xml = XML.parse(data) - if let errorMsg = xml.Error.Message.text { - errorMessage = errorMsg - } - } - super.faild(errorMsg: errorMessage) - } - }) - - } - - func upload(_ fileUrl: URL, host: Host) { - self._upload(fileUrl, fileData: nil, host: host) - } - - func upload(_ fileData: Data, host: Host) { - self._upload(nil, fileData: fileData, host: host) - } -} diff --git a/uPic/Models/Amazon_S3/AmazonS3Util.swift b/uPic/Models/Amazon_S3/AmazonS3Util.swift deleted file mode 100644 index 6c66dbe..0000000 --- a/uPic/Models/Amazon_S3/AmazonS3Util.swift +++ /dev/null @@ -1,62 +0,0 @@ -// -// AmazonS3Util.swift -// uPic -// -// Created by Svend Jin on 2019/7/28. -// Copyright © 2019 Svend Jin. All rights reserved. -// - -import Foundation -import SwiftyJSON - -public class AmazonS3Util { - private static let expiration = 1800 - private static let schema = "https://" - public static let SEVICE_NAME = "s3" - public static let SCHEME = "AWS4"; - public static let ALGORITHM = "AWS4-HMAC-SHA256"; - public static let TERMINATOR = "aws4_request"; - - static func getCredential(access_key: String, short_date: String, region: String) -> String { - return "\(access_key)/\(short_date)/\(region)/\(SEVICE_NAME)/\(TERMINATOR)" - } - - static func getPolicy(policyDict: Dictionary) -> String { - // MARK: 将 policy 字典转成 JSON 然后转成 Data 再转 Base64 - - var policyDict = policyDict - - if policyDict["expiration"] == nil { - policyDict["expiration"] = Date(timeIntervalSince1970: TimeInterval(Date().timeStamp + expiration)).toISOString() - } - - let policyJSON = JSON(policyDict) - let policyData = try! policyJSON.rawData() - return policyData.toBase64() - } - - - static func computeSignature(secret_key: String, policy: String, region: String, short_date: String) -> String { - - //Signature calculation (AWS Signature Version 4) - //For more info http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html - - let kDate = short_date.calculateHMAC256ByKey(key: "\(SCHEME)\(secret_key)".bytes) - let kRegion = region.calculateHMAC256ByKey(key: kDate) - let kService = SEVICE_NAME.calculateHMAC256ByKey(key: kRegion) - let kSigning = TERMINATOR.calculateHMAC256ByKey(key: kService) - let signature = policy.calculateHMAC256ByKey(key: kSigning) - - return signature.toHexString() - } - - static func computeUrl(bucket: String, region: String) -> String { - let endPoint = AmazonS3Region.endPoint(region) - - if endPoint.isEmpty { - return "" - } - - return "\(schema)\(bucket).\(endPoint)" - } -} diff --git a/uPic/Models/BaseUploader.swift b/uPic/Models/BaseUploader.swift index 44ae476..923f7a8 100644 --- a/uPic/Models/BaseUploader.swift +++ b/uPic/Models/BaseUploader.swift @@ -141,8 +141,8 @@ class BaseUploader { case .weibo: WeiboUploader.shared.upload(url, host: host) break - case .amazon_s3: - AmazonS3Uploader.shared.upload(url, host: host) + case .s3: + S3Uploader.shared.upload(url, host: host) break case .imgur: ImgurUploader.shared.upload(url, host: host) @@ -153,9 +153,6 @@ class BaseUploader { case .lsky_pro: LskyProUploader.shared.upload(url, host: host) break - case .minio: - MinioUploader.shared.upload(url, host: host) - break } } @@ -205,8 +202,8 @@ class BaseUploader { case .weibo: WeiboUploader.shared.upload(data, host: host) break - case .amazon_s3: - AmazonS3Uploader.shared.upload(data, host: host) + case .s3: + S3Uploader.shared.upload(data, host: host) break case .imgur: ImgurUploader.shared.upload(data, host: host) @@ -217,9 +214,6 @@ class BaseUploader { case .lsky_pro: LskyProUploader.shared.upload(data, host: host) break - case .minio: - MinioUploader.shared.upload(data, host: host) - break } } @@ -251,16 +245,14 @@ class BaseUploader { return GiteeUploader.fileExtensions case .weibo: return WeiboUploader.fileExtensions - case .amazon_s3: - return AmazonS3Uploader.fileExtensions + case .s3: + return S3Uploader.fileExtensions case .imgur: return ImgurUploader.fileExtensions case .baidu_bos: return BaiduUploader.fileExtensions case .lsky_pro: return LskyProUploader.fileExtensions - case .minio: - return MinioUploader.fileExtensions } } diff --git a/uPic/Models/HostConfig.swift b/uPic/Models/HostConfig.swift index 5e05aba..37626fb 100644 --- a/uPic/Models/HostConfig.swift +++ b/uPic/Models/HostConfig.swift @@ -75,16 +75,14 @@ class HostConfig: NSObject, Codable { return GiteeHostConfig() case .weibo: return WeiboHostConfig() - case .amazon_s3: - return AmazonS3HostConfig() + case .s3: + return S3HostConfig() case .imgur: return ImgurHostConfig() case .baidu_bos: return BaiduHostConfig() case .lsky_pro: return LskyProHostConfig() - case .minio: - return MinioHostConfig() } } @@ -126,8 +124,8 @@ class HostConfig: NSObject, Codable { case .weibo: config = WeiboHostConfig.deserialize(str: str) break - case .amazon_s3: - config = AmazonS3HostConfig.deserialize(str: str) + case .s3: + config = S3HostConfig.deserialize(str: str) break case .imgur: config = ImgurHostConfig.deserialize(str: str) @@ -138,9 +136,6 @@ class HostConfig: NSObject, Codable { case .lsky_pro: config = LskyProHostConfig.deserialize(str: str) break - case .minio: - config = MinioHostConfig.deserialize(str: str) - break } config?.fixPrefixAndSuffix() diff --git a/uPic/Models/HostType.swift b/uPic/Models/HostType.swift index 158bf34..eee8831 100644 --- a/uPic/Models/HostType.swift +++ b/uPic/Models/HostType.swift @@ -18,11 +18,10 @@ public enum HostType: String, CaseIterable, Codable { case github case gitee case weibo - case amazon_s3 + case s3 case imgur case baidu_bos case lsky_pro - case minio public init(intValue: Int) { switch intValue { @@ -45,15 +44,13 @@ public enum HostType: String, CaseIterable, Codable { case 8: self = .weibo case 9: - self = .amazon_s3 + self = .s3 case 10: self = .imgur case 11: self = .baidu_bos case 12: self = .lsky_pro - case 13: - self = .minio default: self = .smms } diff --git a/uPic/Models/Minio/MinioUploader.swift b/uPic/Models/Minio/MinioUploader.swift deleted file mode 100644 index 30f2d1a..0000000 --- a/uPic/Models/Minio/MinioUploader.swift +++ /dev/null @@ -1,112 +0,0 @@ -// -// MinioUploader.swift -// uPic -// -// Created by Svend Jin on 2020/4/12. -// Copyright © 2019 Svend Jin. All rights reserved. -// - -import Cocoa -import Alamofire -import SwiftyXMLParser - -class MinioUploader: BaseUploader { - - static let shared = MinioUploader() - static let fileExtensions: [String] = [] - - func _upload(_ fileUrl: URL?, fileData: Data?, host: Host) { - guard let data = host.data else { - super.faild(errorMsg: "There is a problem with the map bed configuration, please check!".localized) - return - } - - super.start() - - let config = data as! MinioHostConfig - let endPoint = config.url - let bucket = config.bucket - let accessKey = config.accessKey - let secretKey = config.secretKey -// let domain = config.domain - let region = AmazonS3Region.formatRegion(config.region) - - let saveKeyPath = config.saveKeyPath - - guard let configuration = BaseUploaderUtil.getSaveConfiguration(fileUrl, fileData, saveKeyPath) else { - super.faild(errorMsg: "Invalid file") - return - } - let retData = configuration["retData"] as? Data - let fileName = configuration["fileName"] as! String - let mimeType = configuration["mimeType"] as! String - let saveKey = configuration["saveKey"] as! String - - let url = MinioUtil.computeUrl(endPoint: endPoint, bucket: bucket, saveKey: saveKey) - - if url.isEmpty { - super.faild(errorMsg: "There is a problem with the map bed configuration, please check!".localized) - return - } - - let suffix = BaseUploaderUtil._parseVariables(config.suffix, fileName, otherVariables: nil) - - - // MARK: 加密 policy - let iso_date = Date().format(dateFormat: "yyyyMMdd'T'HHmmss'Z'", timeZone: TimeZone(secondsFromGMT: 0)) - let short_date = Date().format(dateFormat: "yyyyMMdd", timeZone: TimeZone(secondsFromGMT: 0)) - - let credential = AmazonS3Util.getCredential(access_key: accessKey, short_date: short_date, region: region) - - var policyDict = Dictionary() - let conditions: [Any] = [ - ["acl": "public-read"], - ["bucket": bucket], - ["starts-with", "$key", ""], - ["x-amz-credential": credential], - ["x-amz-algorithm": AmazonS3Util.ALGORITHM], - ["X-amz-date": iso_date], - ["content-type": mimeType] // 如不手动设置 content-type , aws 默认会将文件的 content-type 设置为 binary/octet-stream 。访问时将会直接下载,而不是预览 - ] - policyDict["conditions"] = conditions - let policy = AmazonS3Util.getPolicy(policyDict: policyDict) - - let signature = AmazonS3Util.computeSignature(secret_key: secretKey, policy: policy, region: region, short_date: short_date) - - var headers = HTTPHeaders() - headers.add(HTTPHeader.contentType("multipart/form-data")) - headers.add(name: "key", value: saveKey) - headers.add(name: "acl", value: "public-read") - headers.add(name: "X-Amz-Credential", value: credential) - headers.add(name: "X-Amz-Algorithm", value: AmazonS3Util.ALGORITHM) - headers.add(name: "X-Amz-Date", value: iso_date) - headers.add(name: "policy", value: policy) - headers.add(name: "X-Amz-Signature", value: signature) - AF.upload(retData!, to: url, method: .put, headers: headers).validate().uploadProgress { progress in - super.progress(percent: progress.fractionCompleted) - }.response(completionHandler: { response -> Void in - switch response.result { - case .success(_): - super.completed(url: "\(url)\(suffix)", retData, fileUrl, fileName) - case .failure(let error): - var errorMessage = error.localizedDescription - if let data = response.data { - let xml = XML.parse(data) - if let errorMsg = xml.Error.Message.text { - errorMessage = errorMsg - } - } - super.faild(errorMsg: errorMessage) - } - }) - - } - - func upload(_ fileUrl: URL, host: Host) { - self._upload(fileUrl, fileData: nil, host: host) - } - - func upload(_ fileData: Data, host: Host) { - self._upload(nil, fileData: fileData, host: host) - } -} diff --git a/uPic/Models/Minio/MinioUtil.swift b/uPic/Models/Minio/MinioUtil.swift deleted file mode 100644 index b7b286a..0000000 --- a/uPic/Models/Minio/MinioUtil.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// MinioUtil.swift -// uPic -// -// Created by Svend Jin on 2020/4/12. -// Copyright © 2020 Svend Jin. All rights reserved. -// - -import Foundation - -class MinioUtil { - static func computeUrl(endPoint: String, bucket: String, saveKey: String) -> String { - if (endPoint.isEmpty) { - return "" - } - var url = endPoint - if url.hasSuffix("/") { - url.removeLast() - } - return "\(url)/\(bucket)/\(saveKey)" - } -} diff --git a/uPic/Models/Minio/MinioHostConfig.swift b/uPic/Models/S3/S3HostConfig.swift similarity index 75% rename from uPic/Models/Minio/MinioHostConfig.swift rename to uPic/Models/S3/S3HostConfig.swift index 5c5ea11..421ec7b 100644 --- a/uPic/Models/Minio/MinioHostConfig.swift +++ b/uPic/Models/S3/S3HostConfig.swift @@ -1,8 +1,8 @@ // -// MinioHostConfig.swift +// S3HostConfig.swift // uPic // -// Created by Svend Jin on 2020/4/12. +// Created by Svend Jin on 2020/8/13. // Copyright © 2020 Svend Jin. All rights reserved. // @@ -10,22 +10,23 @@ import Foundation import SwiftyJSON @objcMembers -class MinioHostConfig: HostConfig { - dynamic var region: String = "us-east-1" - dynamic var url: String = "" +class S3HostConfig: HostConfig { + dynamic var region: String = "" + dynamic var endpoint: String? dynamic var bucket: String = "" dynamic var accessKey: String = "" dynamic var secretKey: String = "" dynamic var domain: String = "" dynamic var saveKeyPath: String? dynamic var suffix: String = "" + dynamic var customize: Bool = false override func displayName(key: String) -> String { switch key { case "region": return "Region".localized - case "url": - return "Domain".localized + case "endpoint": + return "Endpoint".localized case "bucket": return "Bucket".localized case "accessKey": @@ -38,6 +39,8 @@ class MinioHostConfig: HostConfig { return "Save Key".localized case "suffix": return "URL suffix".localized + case "customize": + return "Customize".localized default: return "" } @@ -46,32 +49,34 @@ class MinioHostConfig: HostConfig { override func serialize() -> String { var dict = Dictionary() dict["region"] = self.region - dict["url"] = self.url + dict["endpoint"] = self.endpoint dict["bucket"] = self.bucket dict["accessKey"] = self.accessKey dict["secretKey"] = self.secretKey dict["domain"] = self.domain dict["saveKeyPath"] = self.saveKeyPath dict["suffix"] = self.suffix + dict["customize"] = self.customize return JSON(dict).rawString()! } - static func deserialize(str: String?) -> MinioHostConfig? { - let config = MinioHostConfig() + static func deserialize(str: String?) -> S3HostConfig? { + let config = S3HostConfig() guard let str = str else { return config } let data = str.data(using: String.Encoding.utf8) let json = try! JSON(data: data!) config.region = json["region"].stringValue - config.url = json["url"].stringValue + config.endpoint = json["endpoint"].string config.bucket = json["bucket"].stringValue config.accessKey = json["accessKey"].stringValue config.secretKey = json["secretKey"].stringValue config.domain = json["domain"].stringValue config.saveKeyPath = json["saveKeyPath"].string config.suffix = json["suffix"].stringValue + config.customize = json["customize"].boolValue return config } } diff --git a/uPic/Models/Amazon_S3/AmazonS3Region.swift b/uPic/Models/S3/S3Region.swift similarity index 78% rename from uPic/Models/Amazon_S3/AmazonS3Region.swift rename to uPic/Models/S3/S3Region.swift index 243921f..f948c18 100644 --- a/uPic/Models/Amazon_S3/AmazonS3Region.swift +++ b/uPic/Models/S3/S3Region.swift @@ -1,37 +1,40 @@ // -// AmazonS3Region.swift +// S3Region.swift // uPic // -// Created by Svend Jin on 2019/7/28. -// Copyright © 2019 Svend Jin. All rights reserved. +// Created by Svend Jin on 2020/8/13. +// Copyright © 2020 Svend Jin. All rights reserved. // import Foundation +import S3 -public class AmazonS3Region { +public class S3Region { /// https://docs.aws.amazon.com/general/latest/gr/rande.html + + /// 与 AWS SDK 中 region 对应 + public static let allRegion = [ - "us-east-2": ["cname": "美国东部(俄亥俄州)", "name": "US East (Ohio)"], "us-east-1": ["cname": "美国东部(弗吉尼亚北部)", "name": "US East (N. Virginia)"], + "us-east-2": ["cname": "美国东部(俄亥俄州)", "name": "US East (Ohio)"], "us-west-1": ["cname": "美国西部(加利福尼亚北部)", "name": "US West (N. California)"], "us-west-2": ["cname": "美国西部(俄勒冈)", "name": "US West (Oregon)"], - "ap-east-1": ["cname": "亚太地区(香港)", "name": "Asia Pacific (Hong Kong)"], "ap-south-1": ["cname": "亚太地区(孟买)", "name": "Asia Pacific (Mumbai)"], - "ap-northeast-3": ["cname": "亚太区域 (大阪当地)", "name": "Asia Pacific (Osaka-Local)"], "ap-northeast-2": ["cname": "亚太区域(首尔)", "name": "Asia Pacific (Seoul)"], "ap-southeast-1": ["cname": "亚太区域(新加坡)", "name": "Asia Pacific (Singapore)"], "ap-southeast-2": ["cname": "亚太区域(悉尼)", "name": "Asia Pacific (Sydney)"], "ap-northeast-1": ["cname": "亚太区域(东京)", "name": "Asia Pacific (Tokyo)"], + "ap-east-1": ["cname": "亚太地区(香港)", "name": "Asia Pacific (Hong Kong)"], "ca-central-1": ["cname": "加拿大 (中部)", "name": "Canada (Central)"], - "cn-north-1": ["cname": "中国(北京)", "name": "China (Beijing)"], - "cn-northwest-1": ["cname": "中国 (宁夏)", "name": "China (Ningxia)"], - "eu-central-1": ["cname": "欧洲(法兰克福)", "name": "Europe (Frankfurt)"], "eu-west-1": ["cname": "欧洲(爱尔兰)", "name": "Europe (Ireland)"], - "eu-west-2": ["cname": "欧洲(伦敦)", "name": "Europe (London)"], "eu-west-3": ["cname": "欧洲 (巴黎)", "name": "Europe (Paris)"], + "eu-west-2": ["cname": "欧洲(伦敦)", "name": "Europe (London)"], + "eu-central-1": ["cname": "欧洲(法兰克福)", "name": "Europe (Frankfurt)"], "eu-north-1": ["cname": "欧洲(斯德哥尔摩)", "name": "Europe (Stockholm)"], + "eu-south-1": ["cname": "欧洲(米兰)", "name": "Europe (Milan)"], + "sa-east-1": ["cname": "南美洲(圣保罗)", "name": "South America (São Paulo)"], "me-south-1": ["cname": "中东(巴林)", "name": "Middle East (Bahrain)"], - "sa-east-1": ["cname": "南美洲(圣保罗)", "name": "South America (São Paulo)"] + "af-south-1": ["cname": "非洲(开普敦)", "name": "Africa (Cape Town)"] ] public static func name(_ key: String) -> String { @@ -51,10 +54,6 @@ public class AmazonS3Region { if key.isEmpty { return "" } - if key == "cn-north-1" || key == "cn-northwest-1" { - // s3-control.cn-north-1.amazonaws.com.cn - return "s3.\(key).amazonaws.com.cn" - } return "s3.\(key).amazonaws.com" } @@ -62,6 +61,10 @@ public class AmazonS3Region { if let region = region, !region.isEmpty { return region } - return AmazonS3Region.allRegion.keys.first! + return S3Region.allRegion.keys.first! + } + + public static func toS3Region(_ key: String) -> AWSSDKSwiftCore.Region? { + return AWSSDKSwiftCore.Region(rawValue: key) } } diff --git a/uPic/Models/S3/S3Uploader.swift b/uPic/Models/S3/S3Uploader.swift new file mode 100644 index 0000000..5a2f379 --- /dev/null +++ b/uPic/Models/S3/S3Uploader.swift @@ -0,0 +1,100 @@ +// +// S3Uploader.swift +// uPic +// +// Created by Svend Jin on 2020/8/12. +// Copyright © 2020 Svend Jin. All rights reserved. +// + +import Foundation +import AWSSDKSwiftCore +import S3 +import NIO +import NIOHTTP1 + + +class S3Uploader: BaseUploader { + + static let shared = S3Uploader() + static let fileExtensions: [String] = [] + + + func _upload(_ fileUrl: URL?, fileData: Data?, host: Host) { + guard let data = host.data else { + super.faild(errorMsg: "There is a problem with the map bed configuration, please check!".localized) + return + } + + super.start() + + let config = data as! S3HostConfig + + let customize = config.customize + let bucket = config.bucket + let endpoint = config.endpoint + let accessKey = config.accessKey + let secretKey = config.secretKey + let domain = config.domain + let region = AWSSDKSwiftCore.Region(rawValue: config.region) + + let saveKeyPath = config.saveKeyPath + + let url = S3Util.computeUrl(bucket: bucket, region: config.region, endpoint: customize ? endpoint : nil) + + if url.isEmpty { + super.faild(errorMsg: "There is a problem with the map bed configuration, please check!".localized) + return + } + + guard let configuration = BaseUploaderUtil.getSaveConfiguration(fileUrl, fileData, saveKeyPath) else { + super.faild(errorMsg: "Invalid file") + return + } + let retData = configuration["retData"] as? Data + let fileName = configuration["fileName"] as! String + let mimeType = configuration["mimeType"] as! String + let saveKey = configuration["saveKey"] as! String + + guard let bodyData = retData else { + super.faild(errorMsg: "Invalid file") + return + } + + let suffix = BaseUploaderUtil._parseVariables(config.suffix, fileName, otherVariables: nil) + + let s3 = S3(accessKeyId: accessKey, secretAccessKey: secretKey, region: region, endpoint: customize ? endpoint : nil) + + let putObjectRequest = S3.PutObjectRequest(acl: .publicRead, body: bodyData, bucket: bucket, contentLength: Int64(bodyData.count), contentType: mimeType, key: saveKey) + let put = s3.putObject(putObjectRequest) + + put.whenComplete { (result: Result) in + switch(result) { + case .success(_): + if domain.isEmpty { + super.completed(url: "\(url)/\(saveKey)\(suffix)", retData, fileUrl, fileName) + } else { + super.completed(url: "\(domain)/\(saveKey)\(suffix)", retData, fileUrl, fileName) + } + break + + case .failure(let e): + if let s3Error = e as? S3ErrorType { + super.faild(errorMsg: s3Error.description) + } else { + super.faild(errorMsg: e.localizedDescription) + } + break + + } + } + + } + + func upload(_ fileUrl: URL, host: Host) { + self._upload(fileUrl, fileData: nil, host: host) + } + + func upload(_ fileData: Data, host: Host) { + self._upload(nil, fileData: fileData, host: host) + } +} diff --git a/uPic/Models/S3/S3Util.swift b/uPic/Models/S3/S3Util.swift new file mode 100644 index 0000000..71f06fe --- /dev/null +++ b/uPic/Models/S3/S3Util.swift @@ -0,0 +1,27 @@ +// +// S3Util.swift +// uPic +// +// Created by Svend Jin on 2020/8/13. +// Copyright © 2020 Svend Jin. All rights reserved. +// + +import Foundation +import SwiftyJSON + +public class S3Util { + private static let schema = "https://" + + static func computeUrl(bucket: String, region: String, endpoint: String?) -> String { + + if let endpoint = endpoint { + if (endpoint.last == "/") { + return "\(endpoint)\(bucket)" + } + return "\(endpoint)/\(bucket)" + } else { + let cEndpoint = "s3.\(region).amazonaws.com" + return "\(schema)\(bucket).\(cEndpoint)" + } + } +} diff --git a/uPic/PreferencesWindow/ConfigView/ConfigView.swift b/uPic/PreferencesWindow/ConfigView/ConfigView.swift index 55e848d..767ed88 100644 --- a/uPic/PreferencesWindow/ConfigView/ConfigView.swift +++ b/uPic/PreferencesWindow/ConfigView/ConfigView.swift @@ -12,6 +12,7 @@ class ConfigView: NSView { var paddingTop: Int { return 30 } + var paddingLeft: Int { return 6 } @@ -79,8 +80,8 @@ class ConfigView: NSView { case .weibo: parentView.addSubview(WeiboConfigView(frame: parentView.frame, host: item)) break - case .amazon_s3: - parentView.addSubview(AmazonS3ConfigView(frame: parentView.frame, host: item)) + case .s3: + parentView.addSubview(S3ConfigView(frame: parentView.frame, host: item)) break case .imgur: parentView.addSubview(ImgurConfigView(frame: parentView.frame, host: item)) @@ -91,9 +92,6 @@ class ConfigView: NSView { case .lsky_pro: parentView.addSubview(LskyProConfigView(frame: parentView.frame, host: item)) break - case .minio: - parentView.addSubview(MinioConfigView(frame: parentView.frame, host: item)) - break // default: // let label = NSTextField(labelWithString: "The file will be uploaded anonymously to".localized + " \(item.name)") // label.frame = NSRect(x: (parentView.frame.width - label.frame.width) / 2, y: parentView.frame.height - 50, width: label.frame.width, height: 20) diff --git a/uPic/PreferencesWindow/ConfigView/Views/MinioConfigView.swift b/uPic/PreferencesWindow/ConfigView/Views/MinioConfigView.swift deleted file mode 100644 index e8b2d14..0000000 --- a/uPic/PreferencesWindow/ConfigView/Views/MinioConfigView.swift +++ /dev/null @@ -1,110 +0,0 @@ -// -// MinioConfigView.swift -// uPic -// -// Created by Svend Jin on 2020/4/12. -// Copyright © 2019 Svend Jin. All rights reserved. -// - -import Cocoa - -class MinioConfigView: ConfigView { - - override func createView() { - super.createView() - - guard let data = self.data as? MinioHostConfig else { - return - } - - let urlLabel = NSTextField(labelWithString: "\(data.displayName(key: "url")):") - urlLabel.frame = NSRect(x: paddingLeft, y: y, width: labelWidth, height: labelHeight) - urlLabel.alignment = .right - urlLabel.lineBreakMode = .byClipping - - let urlField = NSTextField(frame: NSRect(x: textFieldX, y: y, width: textFieldWidth, height: labelHeight)) - urlField.identifier = NSUserInterfaceItemIdentifier(rawValue: "url") - urlField.usesSingleLineMode = true - urlField.lineBreakMode = .byTruncatingTail - urlField.delegate = data - urlField.stringValue = data.url - urlField.placeholderString = "http://127.0.0.1:9000" - self.addSubview(urlLabel) - self.addSubview(urlField) - nextKeyViews.append(urlField) - - // MARK: Bucket - y = y - gapTop - labelHeight - let bucketLabel = NSTextField(labelWithString: "\(data.displayName(key: "bucket")):") - bucketLabel.frame = NSRect(x: paddingLeft, y: y, width: labelWidth, height: labelHeight) - bucketLabel.alignment = .right - bucketLabel.lineBreakMode = .byClipping - - let bucketField = NSTextField(frame: NSRect(x: textFieldX, y: y, width: textFieldWidth, height: labelHeight)) - bucketField.identifier = NSUserInterfaceItemIdentifier(rawValue: "bucket") - bucketField.usesSingleLineMode = true - bucketField.lineBreakMode = .byTruncatingTail - bucketField.delegate = data - bucketField.stringValue = data.bucket - self.addSubview(bucketLabel) - self.addSubview(bucketField) - nextKeyViews.append(bucketField) - - // MARK: AccessKey - y = y - gapTop - labelHeight - - let accessKeyLabel = NSTextField(labelWithString: "\(data.displayName(key: "accessKey")):") - accessKeyLabel.frame = NSRect(x: paddingLeft, y: y, width: labelWidth, height: labelHeight) - accessKeyLabel.alignment = .right - accessKeyLabel.lineBreakMode = .byClipping - - let accessKeyField = NSSecureTextField(frame: NSRect(x: textFieldX, y: y, width: textFieldWidth, height: labelHeight)) - accessKeyField.identifier = NSUserInterfaceItemIdentifier(rawValue: "accessKey") - accessKeyField.usesSingleLineMode = true - accessKeyField.lineBreakMode = .byTruncatingTail - accessKeyField.delegate = data - accessKeyField.stringValue = data.accessKey - self.addSubview(accessKeyLabel) - self.addSubview(accessKeyField) - nextKeyViews.append(accessKeyField) - - - // MARK: secretKey - y = y - gapTop - labelHeight - - let secretKeyLabel = NSTextField(labelWithString: "\(data.displayName(key: "secretKey")):") - secretKeyLabel.frame = NSRect(x: paddingLeft, y: y, width: labelWidth, height: labelHeight) - secretKeyLabel.alignment = .right - secretKeyLabel.lineBreakMode = .byClipping - - let secretKeyField = NSSecureTextField(frame: NSRect(x: textFieldX, y: y, width: textFieldWidth, height: labelHeight)) - secretKeyField.identifier = NSUserInterfaceItemIdentifier(rawValue: "secretKey") - secretKeyField.usesSingleLineMode = true - secretKeyField.lineBreakMode = .byTruncatingTail - secretKeyField.delegate = data - secretKeyField.stringValue = data.secretKey - self.addSubview(secretKeyLabel) - self.addSubview(secretKeyField) - nextKeyViews.append(secretKeyField) - - - // MARK: domain -// y = y - gapTop - labelHeight -// self.createDomainField(data) - - // MARK: saveKeyPath - y = y - gapTop - labelHeight - self.createSaveKeyField(data) - - // MARK: help - y = y - gapTop * 2 - labelHeight - super.createHelpBtn("https://blog.svend.cc/upic/tutorials/amazon_s3") - } - - - @objc func regionChange(_ sender: NSPopUpButton) { - if let menuItem = sender.selectedItem, let identifier = menuItem.identifier?.rawValue { - self.data?.setValue(identifier, forKey: "region") - } - } -} diff --git a/uPic/PreferencesWindow/ConfigView/Views/AmazonS3ConfigView.swift b/uPic/PreferencesWindow/ConfigView/Views/S3ConfigView.swift similarity index 58% rename from uPic/PreferencesWindow/ConfigView/Views/AmazonS3ConfigView.swift rename to uPic/PreferencesWindow/ConfigView/Views/S3ConfigView.swift index f06d8ad..100887e 100644 --- a/uPic/PreferencesWindow/ConfigView/Views/AmazonS3ConfigView.swift +++ b/uPic/PreferencesWindow/ConfigView/Views/S3ConfigView.swift @@ -1,40 +1,73 @@ // -// AmazonS3ConfigView.swift +// S3ConfigView.swift // uPic // -// Created by Svend Jin on 2019/7/28. -// Copyright © 2019 Svend Jin. All rights reserved. +// Created by Svend Jin on 2020/8/13. +// Copyright © 2020 Svend Jin. All rights reserved. // import Cocoa -class AmazonS3ConfigView: ConfigView { +class S3ConfigView: ConfigView { + + override var paddingTop: Int { + return 25 + } + + override var gapTop: Int { + return 6 + } + + private var regionLabel: NSTextField! + private var regionButtonPopUp: NSPopUpButton! + private var endpointLabel: NSTextField! + private var endpointField: NSTextField! override func createView() { super.createView() - guard let data = self.data as? AmazonS3HostConfig else { + guard let data = self.data as? S3HostConfig else { return } + //Customize + let customizeLabel = NSTextField(labelWithString: "\(data.displayName(key: "customize")):") + customizeLabel.frame = NSRect(x: paddingLeft, y: y, width: labelWidth, height: labelHeight) + customizeLabel.alignment = .right + customizeLabel.lineBreakMode = .byClipping + + + let customizeBtn = NSButton(frame: NSRect(x: textFieldX, y: y, width: 50, height: labelHeight)) + customizeBtn.title = "" + customizeBtn.target = self + customizeBtn.action = #selector(customizeChanged(_:)) + customizeBtn.identifier = NSUserInterfaceItemIdentifier(rawValue: "customize") + customizeBtn.setButtonType(.switch) + customizeBtn.allowsMixedState = false + customizeBtn.state = data.customize ? .on : .off + self.addSubview(customizeLabel) + self.addSubview(customizeBtn) + nextKeyViews.append(customizeBtn) + // MARK: Region - let regionLabel = NSTextField(labelWithString: "\(data.displayName(key: "region")):") + y = y - gapTop - labelHeight + regionLabel = NSTextField(labelWithString: "\(data.displayName(key: "region")):") regionLabel.frame = NSRect(x: paddingLeft, y: y, width: labelWidth, height: labelHeight) regionLabel.alignment = .right regionLabel.lineBreakMode = .byClipping - let regionButtonPopUp = NSPopUpButton(frame: NSRect(x: textFieldX, y: y, width: textFieldWidth, height: labelHeight)) + regionButtonPopUp = NSPopUpButton(frame: NSRect(x: textFieldX, y: y, width: textFieldWidth, height: labelHeight)) regionButtonPopUp.target = self regionButtonPopUp.action = #selector(regionChange(_:)) regionButtonPopUp.identifier = NSUserInterfaceItemIdentifier(rawValue: "region") var selectRegion: NSMenuItem? - let sortedKeys = Array(AmazonS3Region.allRegion.keys).sorted() + let sortedKeys = Array(S3Region.allRegion.keys).sorted() for key in sortedKeys { - let title = AmazonS3Region.name(key) - let endPoint = AmazonS3Region.endPoint(key) + let title = S3Region.name(key) + let endPoint = S3Region.endPoint(key) let menuItem = NSMenuItem(title: title, action: nil, keyEquivalent: "") menuItem.identifier = NSUserInterfaceItemIdentifier(rawValue: key) regionButtonPopUp.menu?.addItem(menuItem) @@ -61,6 +94,25 @@ class AmazonS3ConfigView: ConfigView { nextKeyViews.append(regionButtonPopUp) + // MARK: Endpoint +// y = y - gapTop - labelHeight + endpointLabel = NSTextField(labelWithString: "\(data.displayName(key: "endpoint")):") + endpointLabel.frame = NSRect(x: paddingLeft, y: y, width: labelWidth, height: labelHeight) + endpointLabel.alignment = .right + endpointLabel.lineBreakMode = .byClipping + + endpointField = NSTextField(frame: NSRect(x: textFieldX, y: y, width: textFieldWidth, height: labelHeight)) + endpointField.identifier = NSUserInterfaceItemIdentifier(rawValue: "endpoint") + endpointField.usesSingleLineMode = true + endpointField.lineBreakMode = .byTruncatingTail + endpointField.delegate = data + endpointField.stringValue = data.endpoint ?? "" + + self.addSubview(endpointLabel) + self.addSubview(endpointField) + nextKeyViews.append(endpointField) + + // MARK: Bucket y = y - gapTop - labelHeight let bucketLabel = NSTextField(labelWithString: "\(data.displayName(key: "bucket")):") @@ -127,6 +179,8 @@ class AmazonS3ConfigView: ConfigView { // MARK: help y = y - gapTop * 2 - labelHeight super.createHelpBtn("https://blog.svend.cc/upic/tutorials/amazon_s3") + + changeCustomizeModel(data.customize) } @@ -135,4 +189,24 @@ class AmazonS3ConfigView: ConfigView { self.data?.setValue(identifier, forKey: "region") } } + + @objc func customizeChanged(_ sender: NSButton) { + let customize = sender.state == .on + self.data?.setValue(customize, forKey: "customize") + + changeCustomizeModel(customize) + } + + func changeCustomizeModel(_ customize: Bool) { + regionLabel.isHidden = customize + regionButtonPopUp.isHidden = customize + + endpointLabel.isHidden = !customize + endpointField.isHidden = !customize + + if (!customize && !endpointField.stringValue.isEmpty) { + endpointField.stringValue = "" + self.data?.setValue(nil, forKey: "endpoint") + } + } } diff --git a/uPic/en.lproj/Localizable.strings b/uPic/en.lproj/Localizable.strings index e674292..139086f 100644 --- a/uPic/en.lproj/Localizable.strings +++ b/uPic/en.lproj/Localizable.strings @@ -80,6 +80,8 @@ /* host -- start */ "Region" = "Region"; +"Endpoint" = "Endpoint"; +"Customize" = "Customize"; "Bucket" = "Bucket"; "Operator" = "Operator"; "Password" = "Password"; @@ -144,6 +146,7 @@ "host.type.baidu_bos" = "Baidu Cloud BOS"; "host.type.lsky_pro" = "Lsky Pro"; "host.type.minio" = "MinIO"; +"host.type.s3" = "Amazon S3(Support for third-party S3 protocols)"; /* weibo quality*/ "weibo.quality.thumbnail" = "thumbnail"; diff --git a/uPic/zh-Hans.lproj/Localizable.strings b/uPic/zh-Hans.lproj/Localizable.strings index e7da076..a3e4aae 100644 --- a/uPic/zh-Hans.lproj/Localizable.strings +++ b/uPic/zh-Hans.lproj/Localizable.strings @@ -80,6 +80,8 @@ /* host -- start */ "Region" = "区域"; +"Endpoint" = "服务端 URL"; +"Customize" = "自定义"; "Bucket" = "空间名称"; "Operator" = "操作员"; "Password" = "密码"; @@ -144,6 +146,7 @@ "host.type.baidu_bos" = "百度云 BOS"; "host.type.lsky_pro" = "Lsky Pro"; "host.type.minio" = "MinIO"; +"host.type.s3" = "Amazon S3(支持第三方S3协议)"; /* weibo quality*/ "weibo.quality.thumbnail" = "缩略图"; diff --git a/uPic/zh-Hant.lproj/Localizable.strings b/uPic/zh-Hant.lproj/Localizable.strings index b08b757..3eb2b60 100644 --- a/uPic/zh-Hant.lproj/Localizable.strings +++ b/uPic/zh-Hant.lproj/Localizable.strings @@ -81,6 +81,8 @@ /* host -- start */ "Region" = "區域"; +"Endpoint" = "服務端 URL"; +"Customize" = "自定義"; "Bucket" = "存儲桶名稱"; "Operator" = "操作員"; "Password" = "密碼"; @@ -145,6 +147,7 @@ "host.type.baidu_bos" = "百度雲 BOS"; "host.type.lsky_pro" = "Lsky Pro"; "host.type.minio" = "MinIO"; +"host.type.s3" = "Amazon S3(支持第三方S3協議)"; /* weibo quality*/ "weibo.quality.thumbnail" = "縮圖"; From 3fa4e0082f46bce514972dc93e047a69cfe9ddc0 Mon Sep 17 00:00:00 2001 From: Svend Date: Fri, 14 Aug 2020 10:09:54 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E5=9B=BE=E5=BA=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- uPic.xcodeproj/project.pbxproj | 2 +- uPic/Models/Custom/CustomUploader.swift | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/uPic.xcodeproj/project.pbxproj b/uPic.xcodeproj/project.pbxproj index 8995b07..6bb33c9 100644 --- a/uPic.xcodeproj/project.pbxproj +++ b/uPic.xcodeproj/project.pbxproj @@ -505,7 +505,6 @@ 1672762122AFF63A007299C3 /* Models */ = { isa = PBXGroup; children = ( - 16AFDB2E24E3EA340008E5A7 /* S3 */, 1602ED9622ADEFB200AA8638 /* BaseUploader.swift */, 1675EC5222FB38240038DA33 /* BaseUploaderUtil.swift */, 1672762222AFF655007299C3 /* Host.swift */, @@ -520,6 +519,7 @@ 167620EA2308191A008F8363 /* Imgur */, 1615390E2408E7DE00CC662F /* LskyPro */, 1690E7E522BF113700FC81F8 /* Qiniu */, + 16AFDB2E24E3EA340008E5A7 /* S3 */, 167D7F4E22B4DB2500DD0A7A /* Smms */, 1660FCB522C11A9F00372950 /* Tencent */, 16995A1F22B5FC6800B1F923 /* UpYun */, diff --git a/uPic/Models/Custom/CustomUploader.swift b/uPic/Models/Custom/CustomUploader.swift index 1e15b18..d9b6b83 100644 --- a/uPic/Models/Custom/CustomUploader.swift +++ b/uPic/Models/Custom/CustomUploader.swift @@ -160,21 +160,21 @@ class CustomUploader: BaseUploader { }) } - var isApplicationJson = false + var isForm = false for (_, header) in headers.enumerated() { if header.name.lowercased() != "content-type" { continue } - if header.value.lowercased().contains("application/json") { - isApplicationJson = true + if header.value.lowercased().contains("x-www-form-urlencoded") || header.value.lowercased().contains("form-data") { + isForm = true break } } - if isApplicationJson { - _byRequest() - } else { + if isForm { _byUpload() + } else { + _byRequest() } } From dd703402728674dcd77105ede08d5e8700f6ec9c Mon Sep 17 00:00:00 2001 From: Svend Date: Fri, 14 Aug 2020 10:56:37 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E4=BC=98=E5=8C=96=20S3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- uPic.xcodeproj/project.pbxproj | 16 ++++++++-------- uPic/Models/S3/S3Region.swift | 9 ++++++++- uPic/Models/S3/S3Uploader.swift | 11 ++++++++--- uPic/Models/S3/S3Util.swift | 12 ++++++++++-- .../HostPreferencesViewController.swift | 4 ++++ 5 files changed, 38 insertions(+), 14 deletions(-) diff --git a/uPic.xcodeproj/project.pbxproj b/uPic.xcodeproj/project.pbxproj index 6bb33c9..8874c3e 100644 --- a/uPic.xcodeproj/project.pbxproj +++ b/uPic.xcodeproj/project.pbxproj @@ -1247,7 +1247,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 20200728; + CURRENT_PROJECT_VERSION = 20200814; DEVELOPMENT_TEAM = W863J6W8DZ; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = "$(SRCROOT)/uPic/Supporting Files/Info.plist"; @@ -1256,7 +1256,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.12; - MARKETING_VERSION = 0.19.6; + MARKETING_VERSION = 0.20.0; PRODUCT_BUNDLE_IDENTIFIER = com.svend.uPic; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1277,7 +1277,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 20200728; + CURRENT_PROJECT_VERSION = 20200814; DEVELOPMENT_TEAM = W863J6W8DZ; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = "$(SRCROOT)/uPic/Supporting Files/Info.plist"; @@ -1286,7 +1286,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.12; - MARKETING_VERSION = 0.19.6; + MARKETING_VERSION = 0.20.0; PRODUCT_BUNDLE_IDENTIFIER = com.svend.uPic; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1303,7 +1303,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 20200728; + CURRENT_PROJECT_VERSION = 20200814; DEVELOPMENT_TEAM = W863J6W8DZ; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = uPicFinderExtension/Info.plist; @@ -1313,7 +1313,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.12; - MARKETING_VERSION = 0.19.6; + MARKETING_VERSION = 0.20.0; PRODUCT_BUNDLE_IDENTIFIER = com.svend.uPic.uPicFinderExtension; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1330,7 +1330,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 20200728; + CURRENT_PROJECT_VERSION = 20200814; DEVELOPMENT_TEAM = W863J6W8DZ; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = uPicFinderExtension/Info.plist; @@ -1340,7 +1340,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.12; - MARKETING_VERSION = 0.19.6; + MARKETING_VERSION = 0.20.0; PRODUCT_BUNDLE_IDENTIFIER = com.svend.uPic.uPicFinderExtension; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/uPic/Models/S3/S3Region.swift b/uPic/Models/S3/S3Region.swift index f948c18..20c7664 100644 --- a/uPic/Models/S3/S3Region.swift +++ b/uPic/Models/S3/S3Region.swift @@ -24,6 +24,7 @@ public class S3Region { "ap-southeast-1": ["cname": "亚太区域(新加坡)", "name": "Asia Pacific (Singapore)"], "ap-southeast-2": ["cname": "亚太区域(悉尼)", "name": "Asia Pacific (Sydney)"], "ap-northeast-1": ["cname": "亚太区域(东京)", "name": "Asia Pacific (Tokyo)"], + "ap-northeast-3": ["cname": "亚太区域 (大阪当地)", "name": "Asia Pacific (Osaka-Local)"], "ap-east-1": ["cname": "亚太地区(香港)", "name": "Asia Pacific (Hong Kong)"], "ca-central-1": ["cname": "加拿大 (中部)", "name": "Canada (Central)"], "eu-west-1": ["cname": "欧洲(爱尔兰)", "name": "Europe (Ireland)"], @@ -34,7 +35,10 @@ public class S3Region { "eu-south-1": ["cname": "欧洲(米兰)", "name": "Europe (Milan)"], "sa-east-1": ["cname": "南美洲(圣保罗)", "name": "South America (São Paulo)"], "me-south-1": ["cname": "中东(巴林)", "name": "Middle East (Bahrain)"], - "af-south-1": ["cname": "非洲(开普敦)", "name": "Africa (Cape Town)"] + "af-south-1": ["cname": "非洲(开普敦)", "name": "Africa (Cape Town)"], + // 中国 + "cn-north-1": ["cname": "中国(北京)", "name": "China (Beijing)"], + "cn-northwest-1": ["cname": "中国 (宁夏)", "name": "China (Ningxia)"] ] public static func name(_ key: String) -> String { @@ -54,6 +58,9 @@ public class S3Region { if key.isEmpty { return "" } + if key == "cn-north-1" || key == "cn-northwest-1" { + return "s3.\(key).amazonaws.com.cn" + } return "s3.\(key).amazonaws.com" } diff --git a/uPic/Models/S3/S3Uploader.swift b/uPic/Models/S3/S3Uploader.swift index 5a2f379..543a3e2 100644 --- a/uPic/Models/S3/S3Uploader.swift +++ b/uPic/Models/S3/S3Uploader.swift @@ -31,15 +31,20 @@ class S3Uploader: BaseUploader { let customize = config.customize let bucket = config.bucket - let endpoint = config.endpoint let accessKey = config.accessKey let secretKey = config.secretKey let domain = config.domain let region = AWSSDKSwiftCore.Region(rawValue: config.region) + var endpoint = config.endpoint + + if !customize { + endpoint = S3Region.endPoint(config.region) + } + let saveKeyPath = config.saveKeyPath - let url = S3Util.computeUrl(bucket: bucket, region: config.region, endpoint: customize ? endpoint : nil) + let url = S3Util.computeUrl(bucket: bucket, region: config.region, customize: customize, endpoint: endpoint) if url.isEmpty { super.faild(errorMsg: "There is a problem with the map bed configuration, please check!".localized) @@ -62,7 +67,7 @@ class S3Uploader: BaseUploader { let suffix = BaseUploaderUtil._parseVariables(config.suffix, fileName, otherVariables: nil) - let s3 = S3(accessKeyId: accessKey, secretAccessKey: secretKey, region: region, endpoint: customize ? endpoint : nil) + let s3 = S3(accessKeyId: accessKey, secretAccessKey: secretKey, region: region, endpoint: S3Util.computedS3Endpoint(endpoint)) let putObjectRequest = S3.PutObjectRequest(acl: .publicRead, body: bodyData, bucket: bucket, contentLength: Int64(bodyData.count), contentType: mimeType, key: saveKey) let put = s3.putObject(putObjectRequest) diff --git a/uPic/Models/S3/S3Util.swift b/uPic/Models/S3/S3Util.swift index 71f06fe..f7036c3 100644 --- a/uPic/Models/S3/S3Util.swift +++ b/uPic/Models/S3/S3Util.swift @@ -12,9 +12,9 @@ import SwiftyJSON public class S3Util { private static let schema = "https://" - static func computeUrl(bucket: String, region: String, endpoint: String?) -> String { + static func computeUrl(bucket: String, region: String, customize: Bool, endpoint: String?) -> String { - if let endpoint = endpoint { + if customize, let endpoint = endpoint { if (endpoint.last == "/") { return "\(endpoint)\(bucket)" } @@ -24,4 +24,12 @@ public class S3Util { return "\(schema)\(bucket).\(cEndpoint)" } } + + static func computedS3Endpoint(_ endpoint: String?) -> String? { + if var point = endpoint, URL(string: point)?.scheme == nil { + point = schema + point + return point + } + return endpoint + } } diff --git a/uPic/PreferencesWindow/HostPreferencesViewController.swift b/uPic/PreferencesWindow/HostPreferencesViewController.swift index 8173da7..7b5fa78 100644 --- a/uPic/PreferencesWindow/HostPreferencesViewController.swift +++ b/uPic/PreferencesWindow/HostPreferencesViewController.swift @@ -71,6 +71,10 @@ class HostPreferencesViewController: PreferencesViewController { self.refreshButtonStatus() self.initHostItems() self.addObserver() + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { + self.setDefaultSelectedHost() + } } override func viewDidDisappear() {