Compare commits

...

51 Commits

Author SHA1 Message Date
Amir Abbas abf68a6254 Updated to Swift 5.0, Removed Swift 3 support 2019-04-04 12:42:17 +04:30
Amir Abbas Mousavian 9384264d29 Merge pull request #139 from sasaki-945/master
Fixed bug: cannot be transferred file 4KB or less
2018-12-16 10:25:42 +03:30
T.Sasaki 90706f1842 Fixed bug: cannot be transferred file 4KB or less 2018-12-10 14:48:59 +09:00
Amir Abbas Mousavian cb42552573 Merge pull request #136 from sasaki-945/master
Added FTP SSL/TLS session resumption/reuse support
2018-11-20 07:49:24 +03:30
T.Sasaki b9c1729733 Added FTP SSL/TLS session resumption/reuse support
- Get a SessionID of control connection after TLS Handshake, and set the SessionID to data connection
2018-11-20 11:10:05 +09:00
Amir Abbas Mousavian 83eb97a100 Merge pull request #135 from sasaki-945/master
Fixed few bug on FTP
2018-11-19 13:46:37 +03:30
Amir Abbas Mousavian 7c7763d105 Merge pull request #134 from sasaki-945/issue#113
Fixed Issue#113: FTP timeout error occurred when ftpList an empty directory
2018-11-19 13:44:54 +03:30
Amir Abbas Mousavian d1ac332ace Merge pull request #132 from sasaki-945/bug-fix
Fixed a blocked stream handle event during UI control
2018-11-19 13:34:43 +03:30
T.Sasaki 737cd7a7c3 Fixed a bug, cannot be canceled if canceled before login 2018-11-19 18:27:17 +09:00
T.Sasaki d050ceb3bc Added filePath argument on FileProviderFTPError 2018-11-19 18:24:07 +09:00
T.Sasaki 504ca76077 Fixed a multiple error callback on recursiveList 2018-11-19 18:22:12 +09:00
T.Sasaki d462b5d17b Add missed commit - [933ef9b] Fixed a truncated uploaded file 2018-11-19 18:19:05 +09:00
T.Sasaki 004966aa69 Fixed a timeout error occurred when ftpList an empty directory
- Break a readData looping when the end of the stream has been reached
2018-11-19 17:33:46 +09:00
T.Sasaki d97f6c0289 To keep it compatible to Swift 3/4.0 2018-11-19 11:24:10 +09:00
Amir Abbas Mousavian 967a5a05fd Merge pull request #133 from sasaki-945/issue#111
Fixed Issue#111: Connection stays open after FTP upload
2018-11-17 19:09:38 +03:30
Amir Abbas Mousavian 9d5ac1064c Merge pull request #131 from sasaki-945/master
Fixed bug in FTP data-connection and login
2018-11-17 19:08:26 +03:30
T.Sasaki 8a5edc5700 Fixed a timeout error not notified 2018-11-16 19:45:48 +09:00
T.Sasaki 933ef9b5d4 Fixed a truncated uploaded file 2018-11-16 19:42:22 +09:00
T.Sasaki d6d777fd65 Fixed a blocked stream handle event during UI control 2018-11-16 17:18:03 +09:00
T.Sasaki 9cbe0fc661 Fixed bugs in FTPS, ftpUserPass was called twice
- change redundant `ftpUserPass` to `completionHandler`
2018-11-15 10:40:14 +09:00
T.Sasaki 59ba5b7817 Fixed bugs in FTP provider fallbacks from EPSV to PASV
- missing `return`
2018-11-15 10:15:03 +09:00
Amir Abbas Mousavian 3f59178b9e Merge pull request #130 from sasaki-945/master
Fixed Issue #127: Support for connect to self-signed certificate FTPS server
2018-11-14 21:19:33 +03:30
T.Sasaki 92f0970097 Convert a boolean value to enum for connecting to self-signed certificate FTPS server
> define an enum like Alamofire's ServerTrustPolicy
2018-11-14 17:55:55 +09:00
T.Sasaki 1fd724c3dc Added connect to self-signed certificate FTPS server 2018-11-09 18:56:45 +09:00
Amir Abbas Mousavian ab1da39fc2 Merge pull request #126 from bscothern/swift_4.2
Swift 4.2 Support
2018-10-16 09:46:30 +03:30
Braden.Scothern 36c600ebb0 updated FileObject's hash implementation 2018-10-16 00:09:48 -06:00
Braden.Scothern 26a9e2de1e updated podspec to use Swift 4.2 2018-10-16 00:09:38 -06:00
Braden.Scothern 5c33683a8c updated .travis.yml to use xcode10 2018-10-16 00:09:26 -06:00
Braden.Scothern 08d476654e Swift 4.2, fixed warnings, fixed tests 2018-10-15 22:35:30 -06:00
Amir Abbas b597244be4 Fix #112 (crash on progress report) 2018-08-18 21:31:37 +04:30
Amir Abbas 76aee40c0d Undo for all providers, encapsulated error creating 2018-08-05 20:07:31 +04:30
Amir Abbas 38fb3fc89a Fix remote session crash, minor fixes 2018-07-26 13:18:49 +04:30
Amir Abbas e2572d2810 Fix #109, refactoring 2018-07-19 15:37:08 +04:30
Amir Abbas 5e3db16401 FTPdownload to use Stream, Fix FTP upload truncated 2018-07-10 15:46:06 +04:30
Amir Abbas 311fcc5a88 Switching to Stream, Fixed FPStreamTask would remain open 2018-07-08 02:44:27 +04:30
Amir Abbas fb389c1d69 Fix #102 & #103 (FTP write issue), Fix fileSize overflow on 32bit device 2018-07-07 23:31:48 +04:30
Amir Abbas f1f7955b86 Fix #105 FTP downloads corrupt file 2018-07-07 14:29:28 +04:30
Amir Abbas 7b21605eeb Amend finxing #99 2018-06-21 13:17:56 +04:30
Amir Abbas cf6e6f96f5 Probable fix #99 (double delegate call), better linux support 2018-06-18 10:59:01 +04:30
Amir Abbas Mousavian 91f2610a72 Fix PDF thumbnail generating, Faster UIImage scaling
- Safer secure coding support
2018-06-14 15:13:01 +04:30
Amir Abbas Mousavian 50f0d33233 Improved performance of thumbnail generating dramatically
- Fix race condition in LocalFileMonitor
2018-06-12 21:54:22 +04:30
Amir Abbas Mousavian ba9cad8daf Merge branch 'fix-issue-95'
* fix-issue-95:
  Fixing #95 (ftp store completion handler latency)
2018-06-07 02:04:23 +04:30
Amir Abbas Mousavian 057bf1a663 Fix compile error 2018-06-06 17:01:04 +04:30
Amir Abbas Mousavian 753055602f Silence warnings of Swift 4.2 2018-06-06 15:27:40 +04:30
Amir Abbas 7a5cef47b5 Updated readme to mention AMSMB2 project. 2018-05-30 14:02:04 +04:30
Amir Abbas f5c6403769 Fixing #95 (ftp store completion handler latency) 2018-05-30 13:05:27 +04:30
Amir Abbas e2520ff154 Possible fix #94, local symbolic links respect ~ 2018-05-26 13:19:40 +04:30
Amir Abbas 7a3c4a297a Removed AnyObject casts 2018-05-20 01:52:21 +04:30
Amir Abbas b946d514a0 Fixed LocalFileObject init issue, symlink tests 2018-05-12 20:03:47 +04:30
Amir Abbas 5e49202ba6 Fix Travis swift 4.0 build 2018-05-12 09:24:05 +04:30
Amir Abbas 399d755eac Add FileProviderSymbolicLink protocol, resolving symlink return FileObject
- Fixed resolving relative path for LocalFileObject
- Gardening CloudFileProvider
2018-05-12 02:32:37 +04:30
31 changed files with 2329 additions and 1734 deletions
+7 -7
View File
@@ -1,5 +1,5 @@
language: objective-c
osx_image: xcode9
osx_image: xcode10.2
xcode_project: $PROJECTNAME.xcodeproj
env:
global:
@@ -13,14 +13,14 @@ env:
- MACOS_SDK=macosx
- TVOS_SDK=appletvsimulator
matrix:
- DESTINATION="OS=11.0,name=iPad Pro (10.5-inch)" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" CARTHAGEDEPLOY="YES"
- DESTINATION="OS=11.0,name=iPhone 8" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" POD="YES"
- DESTINATION="OS=12.2,name=iPad Pro (10.5-inch)" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" CARTHAGEDEPLOY="YES"
- DESTINATION="OS=12.2,name=iPhone 8" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" POD="YES"
- DESTINATION="arch=x86_64" SCHEME="$MACOS_FRAMEWORK_SCHEME" SDK="$MACOS_SDK"
- DESTINATION="arch=x86_64" SCHEME="FilesProviderTests" SDK="$MACOS_SDK" RUN_TESTS="YES"
- DESTINATION="OS=11.0,name=Apple TV 1080p" SCHEME="$TVOS_FRAMEWORK_SCHEME" SDK="$TVOS_SDK"
- DESTINATION="OS=12.2,name=Apple TV 1080p" SCHEME="$TVOS_FRAMEWORK_SCHEME" SDK="$TVOS_SDK"
before_install:
- gem install xcpretty --no-rdoc --no-ri --no-document --quiet
- gem install cocoapods --no-rdoc --no-ri --no-document --quiet
- gem install xcpretty --no-document --quiet
- gem install cocoapods --no-document --quiet
# - gem install xcpretty-travis-formatter
script:
@@ -76,4 +76,4 @@ deploy:
# repo: amosavian/$PROJECTNAME
repo: amosavian/FileProvider
tags: true
condition: "$CARTHAGEDEPLOY = YES"
condition: "$CARTHAGEDEPLOY = YES"
+2 -2
View File
@@ -16,7 +16,7 @@ Pod::Spec.new do |s|
#
s.name = "FilesProvider"
s.version = "0.25.1"
s.version = "0.26.0"
s.summary = "FileManager replacement for Local and Remote (WebDAV/FTP/Dropbox/OneDrive/SMB2) files on iOS and macOS."
# This description is used to generate tags and improve search results.
@@ -65,7 +65,7 @@ Pod::Spec.new do |s|
# the deployment target. You can optionally include the target after the platform.
#
s.swift_version = "4.1"
s.swift_version = "5.0"
s.ios.deployment_target = "8.0"
s.osx.deployment_target = "10.10"
# s.watchos.deployment_target = "2.0"
+25 -10
View File
@@ -60,6 +60,7 @@
7958155B1F478ED9003344DD /* HTTPFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 795815591F478ED9003344DD /* HTTPFileProvider.swift */; };
7958155C1F478ED9003344DD /* HTTPFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 795815591F478ED9003344DD /* HTTPFileProvider.swift */; };
798654331E8874BC002FA550 /* FTPHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 798654321E8874BC002FA550 /* FTPHelper.swift */; };
798D394020D01DA100FFB9EF /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 798D393F20D01DA000FFB9EF /* CoreGraphics.framework */; };
799396AA1D48C02300086753 /* DropboxFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396931D48C02300086753 /* DropboxFileProvider.swift */; };
799396AB1D48C02300086753 /* DropboxFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396931D48C02300086753 /* DropboxFileProvider.swift */; };
799396AC1D48C02300086753 /* DropboxFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396931D48C02300086753 /* DropboxFileProvider.swift */; };
@@ -113,7 +114,6 @@
79BD63AC1E2CC2C20035128C /* ImageIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63AB1E2CC2C20035128C /* ImageIO.framework */; };
79BD63B01E2CC3300035128C /* libxml2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63AF1E2CC3300035128C /* libxml2.tbd */; };
79BD63B21E2CC3350035128C /* ImageIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63B11E2CC3350035128C /* ImageIO.framework */; };
79BD63B61E2CC3860035128C /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63B51E2CC3860035128C /* CoreFoundation.framework */; };
79BD63B81E2CC38D0035128C /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63B71E2CC38D0035128C /* AVFoundation.framework */; };
79BD63BA1E2CC39B0035128C /* libxml2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63B91E2CC39B0035128C /* libxml2.tbd */; };
79BD63BE1E2CC3C20035128C /* ImageIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63BD1E2CC3C20035128C /* ImageIO.framework */; };
@@ -132,6 +132,9 @@
79F5745B1DFDB10B00179ABF /* FileObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79F5745A1DFDB10A00179ABF /* FileObject.swift */; };
79F5745C1DFDB10B00179ABF /* FileObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79F5745A1DFDB10A00179ABF /* FileObject.swift */; };
79F5745D1DFDB10B00179ABF /* FileObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79F5745A1DFDB10A00179ABF /* FileObject.swift */; };
CE27C49B219BFFA0000BE23C /* ServerTrustPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE27C498219BFFA0000BE23C /* ServerTrustPolicy.swift */; };
CE27C49C219C041E000BE23C /* ServerTrustPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE27C498219BFFA0000BE23C /* ServerTrustPolicy.swift */; };
CE27C49D219C041E000BE23C /* ServerTrustPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE27C498219BFFA0000BE23C /* ServerTrustPolicy.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -163,6 +166,7 @@
794C220D1D591A4B00EC49B8 /* SMB2QueryTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2QueryTypes.swift; sourceTree = "<group>"; };
795815591F478ED9003344DD /* HTTPFileProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPFileProvider.swift; sourceTree = "<group>"; };
798654321E8874BC002FA550 /* FTPHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTPHelper.swift; sourceTree = "<group>"; };
798D393F20D01DA000FFB9EF /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
799396671D48B7F600086753 /* FilesProvider.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FilesProvider.framework; sourceTree = BUILT_PRODUCTS_DIR; };
799396751D48B80D00086753 /* FilesProvider.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FilesProvider.framework; sourceTree = BUILT_PRODUCTS_DIR; };
799396821D48B82700086753 /* FilesProvider.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FilesProvider.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -205,6 +209,7 @@
79D903531FAB647400D61D31 /* FilesProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilesProviderTests.swift; sourceTree = "<group>"; };
79D903551FAB647400D61D31 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
79F5745A1DFDB10A00179ABF /* FileObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileObject.swift; sourceTree = "<group>"; };
CE27C498219BFFA0000BE23C /* ServerTrustPolicy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerTrustPolicy.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -213,8 +218,8 @@
buildActionMask = 2147483647;
files = (
79BD63AA1E2CC2BB0035128C /* AVFoundation.framework in Frameworks */,
79BD63AC1E2CC2C20035128C /* ImageIO.framework in Frameworks */,
79BD63A81E2CC2940035128C /* CoreGraphics.framework in Frameworks */,
79BD63AC1E2CC2C20035128C /* ImageIO.framework in Frameworks */,
791950F51DE58A5400B4426E /* libxml2.tbd in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -224,7 +229,7 @@
buildActionMask = 2147483647;
files = (
79BD63B81E2CC38D0035128C /* AVFoundation.framework in Frameworks */,
79BD63B61E2CC3860035128C /* CoreFoundation.framework in Frameworks */,
798D394020D01DA100FFB9EF /* CoreGraphics.framework in Frameworks */,
79BD63B21E2CC3350035128C /* ImageIO.framework in Frameworks */,
79BD63B01E2CC3300035128C /* libxml2.tbd in Frameworks */,
);
@@ -255,6 +260,7 @@
791950F31DE58A5300B4426E /* Frameworks */ = {
isa = PBXGroup;
children = (
798D393F20D01DA000FFB9EF /* CoreGraphics.framework */,
79BD63C11E2CC3D30035128C /* AVFoundation.framework */,
79BD63BF1E2CC3CD0035128C /* CoreGraphics.framework */,
79BD63BD1E2CC3C20035128C /* ImageIO.framework */,
@@ -350,6 +356,7 @@
798654321E8874BC002FA550 /* FTPHelper.swift */,
799396971D48C02300086753 /* SMBClient.swift */,
799396981D48C02300086753 /* SMBFileProvider.swift */,
CE27C498219BFFA0000BE23C /* ServerTrustPolicy.swift */,
);
path = Sources;
sourceTree = "<group>";
@@ -504,22 +511,25 @@
};
799396741D48B80D00086753 = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1020;
};
799396811D48B82700086753 = {
CreatedOnToolsVersion = 7.3.1;
};
79D903501FAB647400D61D31 = {
CreatedOnToolsVersion = 9.1;
LastSwiftMigration = 1020;
ProvisioningStyle = Automatic;
};
};
};
buildConfigurationList = 7993965F1D48B7BF00086753 /* Build configuration list for PBXProject "FilesProvider" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 7993965B1D48B7BF00086753;
productRefGroup = 799396681D48B7F600086753 /* Products */;
@@ -573,6 +583,7 @@
799396B31D48C02300086753 /* LocalFileProvider.swift in Sources */,
799396D41D48C02300086753 /* SMB2Tree.swift in Sources */,
7924B1A21D89DAE000589DB7 /* Options.swift in Sources */,
CE27C49D219C041E000BE23C /* ServerTrustPolicy.swift in Sources */,
792572411DF23BDA006A1526 /* LocalHelper.swift in Sources */,
7936BC121E880F5700A6C81C /* FTPFileProvider.swift in Sources */,
79480FF61E3ABDD0007E7275 /* CloudFileProvider.swift in Sources */,
@@ -617,6 +628,7 @@
799396B41D48C02300086753 /* LocalFileProvider.swift in Sources */,
799396D51D48C02300086753 /* SMB2Tree.swift in Sources */,
7924B1A31D89DAE000589DB7 /* Options.swift in Sources */,
CE27C49C219C041E000BE23C /* ServerTrustPolicy.swift in Sources */,
792572421DF23BDA006A1526 /* LocalHelper.swift in Sources */,
7936BC131E880F5700A6C81C /* FTPFileProvider.swift in Sources */,
79480FF71E3ABDD0007E7275 /* CloudFileProvider.swift in Sources */,
@@ -661,6 +673,7 @@
799396B51D48C02300086753 /* LocalFileProvider.swift in Sources */,
799396D61D48C02300086753 /* SMB2Tree.swift in Sources */,
7924B1A41D89DAE000589DB7 /* Options.swift in Sources */,
CE27C49B219BFFA0000BE23C /* ServerTrustPolicy.swift in Sources */,
792572431DF23BDA006A1526 /* LocalHelper.swift in Sources */,
7936BC141E880F5700A6C81C /* FTPFileProvider.swift in Sources */,
79480FF81E3ABDD0007E7275 /* CloudFileProvider.swift in Sources */,
@@ -740,7 +753,6 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_BITCODE = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_NO_COMMON_BLOCKS = YES;
@@ -780,7 +792,6 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_BITCODE = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
@@ -824,6 +835,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.mousavian.FilesProvider-iOS";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -854,6 +866,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.mousavian.FilesProvider-iOS";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
@@ -877,7 +890,6 @@
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_BITCODE = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
FRAMEWORK_VERSION = A;
@@ -895,6 +907,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.mousavian.FilesProvider-OSX";
SDKROOT = macosx;
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
@@ -916,7 +929,6 @@
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_BITCODE = NO;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
FRAMEWORK_VERSION = A;
@@ -929,6 +941,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.mousavian.FilesProvider-OSX";
SDKROOT = macosx;
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
@@ -965,6 +978,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.mousavian.FilesProvider-tvOS";
SDKROOT = appletvos;
SKIP_INSTALL = YES;
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = 3;
TVOS_DEPLOYMENT_TARGET = 10.0;
VERSIONING_SYSTEM = "apple-generic";
@@ -998,6 +1012,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.mousavian.FilesProvider-tvOS";
SDKROOT = appletvos;
SKIP_INSTALL = YES;
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = 3;
TVOS_DEPLOYMENT_TARGET = 10.0;
VALIDATE_PRODUCT = YES;
@@ -1023,7 +1038,6 @@
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
ENABLE_BITCODE = NO;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_PREPROCESSOR_DEFINITIONS = (
@@ -1040,6 +1054,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_VERSION = 5.0;
};
name = Debug;
};
@@ -1061,7 +1076,6 @@
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO;
ENABLE_BITCODE = NO;
ENABLE_NS_ASSERTIONS = NO;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
@@ -1073,6 +1087,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.mousavian.FilesProviderTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SWIFT_VERSION = 5.0;
};
name = Release;
};
@@ -28,7 +28,26 @@
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "79D903501FAB647400D61D31"
BuildableName = "FilesProviderTests.xctest"
BlueprintName = "FilesProviderTests"
ReferencedContainer = "container:FilesProvider.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "799396741D48B80D00086753"
BuildableName = "FilesProvider.framework"
BlueprintName = "FilesProvider OSX"
ReferencedContainer = "container:FilesProvider.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
+1 -1
View File
@@ -1,4 +1,4 @@
// swift-tools-version:4.0
// swift-tools-version:5.0
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
+1 -5
View File
@@ -40,7 +40,7 @@ All functions do async calls and it wont block your main thread.
- [ ] **GoogleFileProvider** A wrapper around Goodle Drive REST API.
- [ ] **SMBFileProvider** SMB2/3 introduced in 2006, which is a file and printer sharing protocol originated from Microsoft Windows and now is replacing AFP protocol on macOS.
* Data types and some basic functions are implemented but *main interface is not implemented yet!*.
* SMBv1/CIFS is insecure, deprecated and kinda tricky to be implemented due to strict memory allignment in Swift.
* For now, you can use [amosavian/AMSMB2](https://github.com/amosavian/AMSMB2) framework to connect to SMB2.
## Requirements
@@ -512,7 +512,3 @@ Distributed under the MIT license. See `LICENSE` for more information.
[cocoapods-apps]: https://img.shields.io/cocoapods/at/FilesProvider.svg
[docs-image]: https://img.shields.io/cocoapods/metrics/doc-percent/FilesProvider.svg
[docs-url]: http://cocoadocs.org/docsets/FilesProvider/
## Support on Beerpay
Hey dude! Help me out for a couple of :beers:!
[![Beerpay](https://beerpay.io/amosavian/FileProvider/badge.svg?style=beer-square)](https://beerpay.io/amosavian/FileProvider) [![Beerpay](https://beerpay.io/amosavian/FileProvider/make-wish.svg?style=flat-square)](https://beerpay.io/amosavian/FileProvider?focus=wish)
+1 -1
View File
@@ -43,7 +43,7 @@ internal class AEXMLDocument: AEXMLElement {
return rootElement
}
open let options: AEXMLOptions
public let options: AEXMLOptions
// MARK: - Lifecycle
+2 -2
View File
@@ -192,7 +192,7 @@ internal class AEXMLElement {
}
fileprivate func removeChild(_ child: AEXMLElement) {
if let childIndex = children.index(where: { $0 === child }) {
if let childIndex = children.firstIndex(where: { $0 === child }) {
children.remove(at: childIndex)
}
}
@@ -266,7 +266,7 @@ internal class AEXMLElement {
}
public extension String {
extension String {
/// String representation of self with XML special characters escaped.
public var xmlEscaped: String {
+75 -97
View File
@@ -37,7 +37,7 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
open fileprivate(set) var scope: UbiquitousScope
/// Set this property to ignore initiations asserting to be on secondary thread
static open var asserting: Bool = true
static public var asserting: Bool = true
/**
Initializes the provider for the iCloud container associated with the specified identifier and
@@ -82,11 +82,7 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
super.init(baseURL: baseURL)
self.isCoorinating = true
#if swift(>=3.1)
let queueLabel = "FileProvider.\(Swift.type(of: self).type)"
#else
let queueLabel = "FileProvider.\(type(of: self).type)"
#endif
let queueLabel = "FileProvider.\(Swift.type(of: self).type)"
dispatch_queue = DispatchQueue(label: queueLabel, attributes: .concurrent)
operation_queue = OperationQueue()
operation_queue.name = "\(queueLabel).Operation"
@@ -275,7 +271,7 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
if !mdquery.start() {
self.dispatch_queue.async {
completionHandler([], self.cocoaError(path, code: .fileReadNoPermission))
completionHandler([], CocoaError(.fileReadNoPermission, path: path))
}
}
}
@@ -342,7 +338,7 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
try self.opFileManager.copyItem(at: localFile, to: tmpFile)
let toUrl = self.url(of: toPath)
try self.opFileManager.setUbiquitous(true, itemAt: tmpFile, destinationURL: toUrl)
self.monitorFile(path: toPath, operation: operation, progress: progress)
self.monitorTransmissionProgress(path: toPath, operation: operation, progress: progress)
completionHandler?(nil)
self.delegateNotify(operation)
} catch {
@@ -362,7 +358,7 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
self.operation_queue.addOperation(moveblock)
})
} else {
let e = self.cocoaError(dest.path, code: .fileWriteFileExists)
let e = CocoaError(.fileWriteFileExists, path: dest.path)
dispatch_queue.async {
completionHandler?(e)
}
@@ -390,7 +386,7 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
open override func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) -> Progress? {
let operation = FileOperationType.copy(source: path, destination: toLocalURL.absoluteString)
let progress = super.copyItem(path: path, toLocalURL: toLocalURL, completionHandler: completionHandler)
monitorFile(path: path, operation: operation, progress: progress)
monitorTransmissionProgress(path: path, operation: operation, progress: progress)
do {
try self.opFileManager.startDownloadingUbiquitousItem(at: self.url(of: path))
} catch {
@@ -416,7 +412,7 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
open override func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> Progress? {
let operation = FileOperationType.fetch(path: path)
let progress = super.contents(path: path, completionHandler: completionHandler)
monitorFile(path: path, operation: operation, progress: progress)
monitorTransmissionProgress(path: path, operation: operation, progress: progress)
return progress
}
@@ -437,7 +433,7 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
open override func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> Progress? {
let operation = FileOperationType.fetch(path: path)
let progress = super.contents(path: path, offset: offset, length: length, completionHandler: completionHandler)
monitorFile(path: path, operation: operation, progress: progress)
monitorTransmissionProgress(path: path, operation: operation, progress: progress)
return progress
}
@@ -460,7 +456,7 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
progress.kind = .file
progress.setUserInfoObject(self.url(of: path), forKey: .fileURLKey)
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
monitorFile(path: path, operation: operation, progress: progress)
monitorTransmissionProgress(path: path, operation: operation, progress: progress)
_ = super.writeContents(path: path, contents: data, atomically: atomically, overwrite: overwrite, completionHandler: completionHandler)
return progress
}
@@ -527,6 +523,40 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
return monitors[path] != nil
}
open func publicLink(to path: String, completionHandler: @escaping ((_ link: URL?, _ attribute: FileObject?, _ expiration: Date?, _ error: Error?) -> Void)) {
operation_queue.addOperation {
do {
var expiration: NSDate?
let url = try self.opFileManager.url(forPublishingUbiquitousItemAt: self.url(of: path), expiration: &expiration)
self.dispatch_queue.async {
completionHandler(url, nil, expiration as Date?, nil)
}
} catch {
self.dispatch_queue.async {
completionHandler(nil, nil, nil, error)
}
}
}
}
/**
Removes local copy of file, but spares cloud copy.
- Parameter path: Path of file or directory to be removed from local.
- Parameter completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
*/
open func evictItem(path: String, completionHandler: SimpleCompletionHandler) {
operation_queue.addOperation {
do {
try self.opFileManager.evictUbiquitousItem(at: self.url(of: path))
completionHandler?(nil)
} catch {
completionHandler?(error)
}
}
}
}
extension CloudFileProvider {
fileprivate func updateQueryTypeKeys(_ queryComponent: NSPredicate) -> NSPredicate {
let mapDict: [String: String] = ["url": NSMetadataItemURLKey, "name": NSMetadataItemFSNameKey, "path": NSMetadataItemPathKey, "filesize": NSMetadataItemFSSizeKey, "modifiedDate": NSMetadataItemFSContentChangeDateKey, "creationDate": NSMetadataItemFSCreationDateKey, "contentType": NSMetadataItemContentTypeKey]
@@ -536,6 +566,7 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
case .and: return NSCompoundPredicate(andPredicateWithSubpredicates: newSub)
case .not: return NSCompoundPredicate(notPredicateWithSubpredicate: newSub.first!)
case .or: return NSCompoundPredicate(orPredicateWithSubpredicates: newSub)
@unknown default: fatalError()
}
} else if let cQuery = queryComponent as? NSComparisonPredicate {
var newLeft = cQuery.leftExpression
@@ -557,7 +588,7 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
return queryComponent
}
}
fileprivate func mapFileObject(attributes attribs: [String: Any]) -> FileObject? {
guard let url = (attribs[NSMetadataItemURLKey] as? URL)?.standardizedFileURL, let name = attribs[NSMetadataItemFSNameKey] as? String else {
@@ -565,11 +596,7 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
}
let path = self.relativePathOf(url: url)
#if swift(>=4.0)
let rpath = path.hasPrefix("/") ? String(path[path.index(after: path.startIndex)...]) : path
#else
let rpath = path.hasPrefix("/") ? path.substring(from: path.index(after: path.startIndex)) : path
#endif
let relativeUrl = URL(string: rpath.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? rpath, relativeTo: self.baseURL)
let file = FileObject(url: relativeUrl ?? url, name: name, path: path)
@@ -583,7 +610,7 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
return file
}
fileprivate func monitorFile(path: String, operation: FileOperationType, progress: Progress?) {
fileprivate func monitorTransmissionProgress(path: String, operation: FileOperationType, progress: Progress?) {
var isDownloadingOperation: Bool
let isUploadingOperation: Bool
switch operation {
@@ -615,7 +642,7 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
observer = NotificationCenter.default.addObserver(forName: .NSMetadataQueryDidUpdate, object: query, queue: .main) { [weak self] (notification) in
guard let items = notification.userInfo?[NSMetadataQueryUpdateChangedItemsKey] as? NSArray,
let item = items.firstObject as? NSMetadataItem else {
return
return
}
func terminateAndRemoveObserver() {
@@ -668,53 +695,37 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
}
}
open func publicLink(to path: String, completionHandler: @escaping ((_ link: URL?, _ attribute: FileObject?, _ expiration: Date?, _ error: Error?) -> Void)) {
operation_queue.addOperation {
do {
var expiration: NSDate?
let url = try self.opFileManager.url(forPublishingUbiquitousItemAt: self.url(of: path), expiration: &expiration)
self.dispatch_queue.async {
completionHandler(url, nil, expiration as Date?, nil)
}
} catch {
self.dispatch_queue.async {
completionHandler(nil, nil, nil, error)
}
fileprivate func metadataItem(for url: URL) -> NSMetadataItem? {
assert(!Thread.isMainThread, "CloudFileProvider.metadataItem(for:) is not recommended to be executed on Main Thread.")
let query = NSMetadataQuery()
query.predicate = NSPredicate(format: "(%K LIKE %@)", NSMetadataItemPathKey, url.path)
query.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope, NSMetadataQueryUbiquitousDataScope]
var item: NSMetadataItem?
let group = DispatchGroup()
group.enter()
var finishObserver: NSObjectProtocol?
finishObserver = NotificationCenter.default.addObserver(forName: .NSMetadataQueryDidFinishGathering, object: query, queue: nil, using: { (notification) in
defer {
query.stop()
group.leave()
NotificationCenter.default.removeObserver(finishObserver!)
}
}
}
/**
Removes local copy of file, but spares cloud copy.
- Parameter path: Path of file or directory to be removed from local
- Parameter completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
*/
open func evictItem(path: String, completionHandler: SimpleCompletionHandler) {
operation_queue.addOperation {
do {
try self.opFileManager.evictUbiquitousItem(at: self.url(of: path))
completionHandler?(nil)
} catch {
completionHandler?(error)
if query.resultCount > 0 {
item = query.result(at: 0) as? NSMetadataItem
}
query.disableUpdates()
})
DispatchQueue.main.async {
query.start()
}
}
/**
Returns current version of file on this device and all versions of files in user devices.
- Parameter path: Path of file or directory.
- Parameter completionHandler: Retrieve current version on this device and all versions available. `currentVersion` will be nil if file doesn't exist. If an error parameter was provided, a presentable `Error` will be returned.
*/
func versionsOfItem(path: String, completionHandler: @escaping ((_ currentVersion: NSFileVersion?, _ versions: [NSFileVersion], _ error: Error?) -> Void)) {
NotImplemented()
}
/// Resolves conflicts by selecting a version.
/// - Parameter path: Path of file or directory.
/// - Parameter version: Version than will be choose as main version. `nil` value indicates current version on this device will be selected.
/// - Parameter completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
func selectVersionOfItem(path: String, version: NSFileVersion? = nil, completionHandler: SimpleCompletionHandler) {
NotImplemented()
_ = group.wait(timeout: .now() + 30)
return item
}
}
@@ -759,36 +770,3 @@ public enum UbiquitousScope: RawRepresentable {
}
}
}
/*
func getMetadataItem(url: URL) -> NSMetadataItem? {
let query = NSMetadataQuery()
query.predicate = NSPredicate(format: "(%K LIKE %@)", NSMetadataItemPathKey, url.path)
query.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope, NSMetadataQueryUbiquitousDataScope]
var item: NSMetadataItem?
let group = DispatchGroup()
group.enter()
var finishObserver: NSObjectProtocol?
finishObserver = NotificationCenter.default.addObserver(forName: .NSMetadataQueryDidFinishGathering, object: query, queue: nil, using: { (notification) in
defer {
query.stop()
group.leave()
NotificationCenter.default.removeObserver(finishObserver!)
}
if query.resultCount > 0 {
item = query.result(at: 0) as? NSMetadataItem
}
query.disableUpdates()
})
DispatchQueue.main.async {
query.start()
}
_ = group.wait(timeout: .now() + 30)
return item
}
*/
+40 -36
View File
@@ -24,9 +24,9 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
override open class var type: String { return "Dropbox" }
/// Dropbox RPC API URL, which is equal with [https://api.dropboxapi.com/2/](https://api.dropboxapi.com/2/)
open let apiURL: URL
public let apiURL: URL
/// Dropbox contents download/upload API URL, which is equal with [https://content.dropboxapi.com/2/](https://content.dropboxapi.com/2/)
open let contentURL: URL
public let contentURL: URL
/**
Initializer for Dropbox provider with given client ID and Token.
@@ -91,7 +91,7 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
request.httpMethod = "POST"
request.setValue(authentication: credential, with: .oAuth2)
request.setValue(contentType: .json)
let requestDictionary: [String: AnyObject] = ["path": correctPath(path)! as NSString]
let requestDictionary: [String: Any] = ["path": correctPath(path)!]
request.httpBody = Data(jsonDictionary: requestDictionary)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var serverError: FileProviderHTTPError?
@@ -165,14 +165,14 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
let queryIsTruePredicate = query.predicateFormat == "TRUEPREDICATE"
return paginated(path, requestHandler: requestHandler,
pageHandler: { [weak self] (data, progress) -> (files: [FileObject], error: Error?, newToken: String?) in
guard let json = data?.deserializeJSON(), let entries = (json["entries"] ?? json["matches"]) as? [AnyObject] else {
let err = self?.urlError(path, code: .badServerResponse)
guard let json = data?.deserializeJSON(), let entries = (json["entries"] ?? json["matches"]) as? [Any] else {
let err = URLError(.badServerResponse, url: self?.url(of: path))
return ([], err, nil)
}
var files = [FileObject]()
for entry in entries {
if let entry = entry as? [String: AnyObject], let file = DropboxFileObject(json: entry), queryIsTruePredicate || query.evaluate(with: file.mapPredicate()) {
if let entry = entry as? [String: Any], let file = DropboxFileObject(json: entry), queryIsTruePredicate || query.evaluate(with: file.mapPredicate()) {
files.append(file)
progress.completedUnitCount += 1
foundItemHandler?(file)
@@ -193,11 +193,11 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
override func request(for operation: FileOperationType, overwrite: Bool = false, attributes: [URLResourceKey : Any] = [:]) -> URLRequest {
func uploadRequest(to path: String) -> URLRequest {
var requestDictionary = [String: AnyObject]()
var requestDictionary = [String: Any]()
let url: URL = URL(string: "files/upload", relativeTo: contentURL)!
requestDictionary["path"] = correctPath(path) as NSString?
requestDictionary["mode"] = (overwrite ? "overwrite" : "add") as NSString
//requestDictionary["client_modified"] = (attributes[.contentModificationDateKey] as? Date)?.format(with: .rfc3339) as NSString?
requestDictionary["path"] = correctPath(path)
requestDictionary["mode"] = (overwrite ? "overwrite" : "add")
//requestDictionary["client_modified"] = (attributes[.contentModificationDateKey] as? Date)?.format(with: .rfc3339)
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue(authentication: credential, with: .oAuth2)
@@ -211,7 +211,7 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
var request = URLRequest(url: url)
request = URLRequest(url: url)
request.setValue(authentication: credential, with: .oAuth2)
request.setValue(dropboxArgKey: ["path": correctPath(path)! as NSString])
request.setValue(dropboxArgKey: ["path": correctPath(path)!])
return request
}
@@ -234,7 +234,7 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
let url: String
let sourcePath = operation.source
let destPath = operation.destination
var requestDictionary = [String: AnyObject]()
var requestDictionary = [String: Any]()
switch operation {
case .create:
url = "files/create_folder_v2"
@@ -254,11 +254,11 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
request.httpMethod = "POST"
request.setValue(authentication: credential, with: .oAuth2)
request.setValue(contentType: .json)
if let dest = correctPath(destPath) as NSString? {
requestDictionary["from_path"] = correctPath(sourcePath) as NSString?
if let dest = correctPath(destPath) {
requestDictionary["from_path"] = correctPath(sourcePath)
requestDictionary["to_path"] = dest
} else {
requestDictionary["path"] = correctPath(sourcePath) as NSString?
requestDictionary["path"] = correctPath(sourcePath)
}
request.httpBody = Data(jsonDictionary: requestDictionary)
return request
@@ -267,7 +267,7 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
override func serverError(with code: FileProviderHTTPErrorCode, path: String?, data: Data?) -> FileProviderHTTPError {
let errorDesc: String?
if let response = data?.deserializeJSON() {
errorDesc = (response["user_message"] as? String) ?? (response["error"]?["tag"] as? String)
errorDesc = (response["user_message"] as? String) ?? ((response["error"] as? [String: Any])?["tag"] as? String)
} else {
errorDesc = data.flatMap({ String(data: $0, encoding: .utf8) })
}
@@ -300,7 +300,7 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
request.httpMethod = "POST"
request.setValue(authentication: credential, with: .oAuth2)
request.setValue(contentType: .json)
let requestDictionary: [String: AnyObject] = ["path": correctPath(path)! as NSString]
let requestDictionary: [String: Any] = ["path": correctPath(path)!]
request.httpBody = Data(jsonDictionary: requestDictionary)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var serverError: FileProviderHTTPError?
@@ -311,7 +311,7 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
serverError = code.flatMap { self.serverError(with: $0, path: path, data: data) }
if let json = data?.deserializeJSON() {
link = (json["link"] as? String).flatMap(URL.init(string:))
fileObject = (json["metadata"] as? [String: AnyObject]).flatMap(DropboxFileObject.init(json:))
fileObject = (json["metadata"] as? [String: Any]).flatMap(DropboxFileObject.init(json:))
}
}
@@ -334,7 +334,7 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
*/
open func copyItem(remoteURL: URL, to toPath: String, completionHandler: @escaping ((_ jobId: String?, _ attribute: DropboxFileObject?, _ error: Error?) -> Void)) {
if remoteURL.isFileURL {
completionHandler(nil, nil, self.urlError(remoteURL.path, code: .badURL))
completionHandler(nil, nil, URLError(.badURL, url: remoteURL))
return
}
let url = URL(string: "files/save_url", relativeTo: apiURL)!
@@ -342,7 +342,7 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
request.httpMethod = "POST"
request.setValue(authentication: credential, with: .oAuth2)
request.setValue(contentType: .json)
let requestDictionary: [String: AnyObject] = ["path": correctPath(toPath)! as NSString, "url" : remoteURL.absoluteString as NSString]
let requestDictionary: [String: Any] = ["path": correctPath(toPath)!, "url" : remoteURL.absoluteString]
request.httpBody = Data(jsonDictionary: requestDictionary)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var serverError: FileProviderHTTPError?
@@ -353,7 +353,7 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
serverError = code.flatMap { self.serverError(with: $0, path: toPath, data: data) }
if let json = data?.deserializeJSON() {
jobId = json["async_job_id"] as? String
fileObject = (json["metadata"] as? [String: AnyObject]).flatMap(DropboxFileObject.init(json:))
fileObject = (json["metadata"] as? [String: Any]).flatMap(DropboxFileObject.init(json:))
}
}
completionHandler(jobId, fileObject, serverError ?? error)
@@ -375,7 +375,7 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
request.httpMethod = "POST"
request.setValue(authentication: credential, with: .oAuth2)
request.setValue(contentType: .json)
let requestDictionary: [String: AnyObject] = ["path": correctPath(toPath)! as NSString, "copy_reference" : reference as NSString]
let requestDictionary: [String: Any] = ["path": correctPath(toPath)!, "copy_reference" : reference ]
request.httpBody = Data(jsonDictionary: requestDictionary)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var serverError: FileProviderHTTPError?
@@ -391,7 +391,7 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
extension DropboxFileProvider: ExtendedFileProvider {
open func propertiesOfFileSupported(path: String) -> Bool {
let fileExt = (path as NSString).pathExtension.lowercased()
let fileExt = path.pathExtension.lowercased()
switch fileExt {
case "jpg", "jpeg", "bmp", "gif", "png", "tif", "tiff":
return true
@@ -411,7 +411,7 @@ extension DropboxFileProvider: ExtendedFileProvider {
request.httpMethod = "POST"
request.setValue(authentication: credential, with: .oAuth2)
request.setValue(contentType: .json)
let requestDictionary: [String: AnyObject] = ["path": correctPath(path)! as NSString, "include_media_info": NSNumber(value: true)]
let requestDictionary: [String: Any] = ["path": correctPath(path)!, "include_media_info": NSNumber(value: true)]
request.httpBody = Data(jsonDictionary: requestDictionary)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var serverError: FileProviderHTTPError?
@@ -432,15 +432,15 @@ extension DropboxFileProvider: ExtendedFileProvider {
#if os(macOS) || os(iOS) || os(tvOS)
open func thumbnailOfFileSupported(path: String) -> Bool {
switch (path as NSString).pathExtension.lowercased() {
switch path.pathExtension.lowercased() {
case "jpg", "jpeg", "gif", "bmp", "png", "tif", "tiff":
return true
case "doc", "docx", "docm", "xls", "xlsx", "xlsm":
return true
return false
case "ppt", "pps", "ppsx", "ppsm", "pptx", "pptm":
return true
return false
case "rtf":
return true
return false
default:
return false
}
@@ -451,7 +451,7 @@ extension DropboxFileProvider: ExtendedFileProvider {
open func thumbnailOfFile(path: String, dimension: CGSize?, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void)) -> Progress? {
let url: URL
let thumbAPI: Bool
switch (path as NSString).pathExtension.lowercased() {
switch path.pathExtension.lowercased() {
case "jpg", "jpeg", "gif", "bmp", "png", "tif", "tiff":
url = URL(string: "files/get_thumbnail", relativeTo: contentURL)!
thumbAPI = true
@@ -467,9 +467,9 @@ extension DropboxFileProvider: ExtendedFileProvider {
}
var request = URLRequest(url: url)
request.setValue(authentication: credential, with: .oAuth2)
var requestDictionary: [String: AnyObject] = ["path": path as NSString]
var requestDictionary: [String: Any] = ["path": path]
if thumbAPI {
requestDictionary["format"] = "jpeg" as NSString
requestDictionary["format"] = "jpeg"
let size: String
switch dimension?.height ?? 64 {
case 0...32: size = "w32h32"
@@ -478,23 +478,27 @@ extension DropboxFileProvider: ExtendedFileProvider {
case 129...480: size = "w640h480"
default: size = "w1024h768"
}
requestDictionary["size"] = size as NSString
requestDictionary["size"] = size
}
request.setValue(dropboxArgKey: requestDictionary)
let task = self.session.dataTask(with: request, completionHandler: { (data, response, error) in
var image: ImageClass? = nil
if let r = response as? HTTPURLResponse, let result = r.allHeaderFields["Dropbox-API-Result"] as? String, let jsonResult = result.deserializeJSON() {
if jsonResult["error"] != nil {
completionHandler(nil, self.urlError(path, code: .cannotDecodeRawData))
completionHandler(nil, URLError(.cannotDecodeRawData, url: self.url(of: path)))
}
}
if let data = data {
if data.isPDF, let pageImage = DropboxFileProvider.convertToImage(pdfData: data) {
if data.isPDF, let pageImage = DropboxFileProvider.convertToImage(pdfData: data, maxSize: dimension) {
image = pageImage
} else if let contentType = (response as? HTTPURLResponse)?.allHeaderFields["Content-Type"] as? String, contentType.contains("text/html") {
// TODO: Implement converting html returned type of get_preview to image
} else if let fetchedimage = ImageClass(data: data) {
image = dimension.map({ DropboxFileProvider.scaleDown(image: fetchedimage, toSize: $0) }) ?? fetchedimage
} else {
if let dimension = dimension {
image = DropboxFileProvider.scaleDown(data: data, toSize: dimension)
} else {
}
}
}
completionHandler(image, error)
+14 -14
View File
@@ -22,9 +22,9 @@ public final class DropboxFileObject: FileObject {
self.init(json: json)
}
internal init? (json: [String: AnyObject]) {
internal init? (json: [String: Any]) {
var json = json
if json["name"] == nil, let metadata = json["metadata"] as? [String: AnyObject] {
if json["name"] == nil, let metadata = json["metadata"] as? [String: Any] {
json = metadata
}
guard let name = json["name"] as? String else { return nil }
@@ -34,13 +34,13 @@ public final class DropboxFileObject: FileObject {
self.serverTime = (json["server_modified"] as? String).flatMap(Date.init(rfcString:))
self.modifiedDate = (json["client_modified"] as? String).flatMap(Date.init(rfcString:))
self.type = (json[".tag"] as? String) == "folder" ? .directory : .regular
self.isReadOnly = (json["sharing_info"]?["read_only"] as? NSNumber)?.boolValue ?? false
self.isReadOnly = ((json["sharing_info"] as? [String: Any])?["read_only"] as? NSNumber)?.boolValue ?? false
self.id = json["id"] as? String
self.rev = json["rev"] as? String
}
/// The time contents of file has been modified on server, returns nil if not set
open internal(set) var serverTime: Date? {
public internal(set) var serverTime: Date? {
get {
return allValues[.serverDateKey] as? Date
}
@@ -51,7 +51,7 @@ public final class DropboxFileObject: FileObject {
/// The document identifier is a value assigned by the Dropbox to a file.
/// This value is used to identify the document regardless of where it is moved on a volume.
open internal(set) var id: String? {
public internal(set) var id: String? {
get {
return allValues[.fileResourceIdentifierKey] as? String
}
@@ -62,7 +62,7 @@ public final class DropboxFileObject: FileObject {
/// The revision of file, which changes when a file contents are modified.
/// Changes to attributes or other file metadata do not change the identifier.
open internal(set) var rev: String? {
public internal(set) var rev: String? {
get {
return allValues[.generationIdentifierKey] as? String
}
@@ -72,8 +72,8 @@ public final class DropboxFileObject: FileObject {
}
}
internal extension DropboxFileProvider {
internal func correctPath(_ path: String?) -> String? {
extension DropboxFileProvider {
func correctPath(_ path: String?) -> String? {
guard let path = path else { return nil }
if path.hasPrefix("id:") || path.hasPrefix("rev:") {
return path
@@ -85,7 +85,7 @@ internal extension DropboxFileProvider {
return p
}
internal func listRequest(path: String, queryStr: String? = nil, recursive: Bool = false) -> ((_ token: String?) -> URLRequest?) {
func listRequest(path: String, queryStr: String? = nil, recursive: Bool = false) -> ((_ token: String?) -> URLRequest?) {
if let queryStr = queryStr {
return { [weak self] (token) -> URLRequest? in
guard let `self` = self else { return nil }
@@ -94,8 +94,8 @@ internal extension DropboxFileProvider {
request.httpMethod = "POST"
request.setValue(authentication: self.credential, with: .oAuth2)
request.setValue(contentType: .json)
var requestDictionary: [String: AnyObject] = ["path": self.correctPath(path)! as NSString]
requestDictionary["query"] = queryStr as NSString
var requestDictionary: [String: Any] = ["path": self.correctPath(path)!]
requestDictionary["query"] = queryStr
requestDictionary["start"] = NSNumber(value: (token.flatMap( { Int($0) } ) ?? 0))
request.httpBody = Data(jsonDictionary: requestDictionary)
return request
@@ -103,14 +103,14 @@ internal extension DropboxFileProvider {
} else {
return { [weak self] (token) -> URLRequest? in
guard let `self` = self else { return nil }
var requestDictionary = [String: AnyObject]()
var requestDictionary = [String: Any]()
let url: URL
if let token = token {
url = URL(string: "files/list_folder/continue", relativeTo: self.apiURL)!
requestDictionary["cursor"] = token as NSString?
requestDictionary["cursor"] = token
} else {
url = URL(string: "files/list_folder", relativeTo: self.apiURL)!
requestDictionary["path"] = self.correctPath(path) as NSString?
requestDictionary["path"] = self.correctPath(path)
requestDictionary["recursive"] = NSNumber(value: recursive)
}
var request = URLRequest(url: url)
+32 -51
View File
@@ -14,7 +14,7 @@ import AVFoundation
extension LocalFileProvider: ExtendedFileProvider {
open func thumbnailOfFileSupported(path: String) -> Bool {
switch (path as NSString).pathExtension.lowercased() {
switch path.pathExtension.lowercased() {
case LocalFileInformationGenerator.imageThumbnailExtensions.contains:
return true
case LocalFileInformationGenerator.audioThumbnailExtensions.contains:
@@ -33,7 +33,7 @@ extension LocalFileProvider: ExtendedFileProvider {
}
open func propertiesOfFileSupported(path: String) -> Bool {
let fileExt = (path as NSString).pathExtension.lowercased()
let fileExt = path.pathExtension.lowercased()
switch fileExt {
case LocalFileInformationGenerator.imagePropertiesExtensions.contains:
return LocalFileInformationGenerator.imageProperties != nil
@@ -65,26 +65,23 @@ extension LocalFileProvider: ExtendedFileProvider {
// Create Thumbnail and cache
switch fileURL.pathExtension.lowercased() {
case LocalFileInformationGenerator.videoThumbnailExtensions.contains:
thumbnailImage = LocalFileInformationGenerator.videoThumbnail(fileURL)
thumbnailImage = LocalFileInformationGenerator.videoThumbnail(fileURL, dimension)
case LocalFileInformationGenerator.audioThumbnailExtensions.contains:
thumbnailImage = LocalFileInformationGenerator.audioThumbnail(fileURL)
thumbnailImage = LocalFileInformationGenerator.audioThumbnail(fileURL, dimension)
case LocalFileInformationGenerator.imageThumbnailExtensions.contains:
thumbnailImage = LocalFileInformationGenerator.imageThumbnail(fileURL)
thumbnailImage = LocalFileInformationGenerator.imageThumbnail(fileURL, dimension)
case LocalFileInformationGenerator.pdfThumbnailExtensions.contains:
thumbnailImage = LocalFileInformationGenerator.pdfThumbnail(fileURL)
thumbnailImage = LocalFileInformationGenerator.pdfThumbnail(fileURL, dimension)
case LocalFileInformationGenerator.officeThumbnailExtensions.contains:
thumbnailImage = LocalFileInformationGenerator.officeThumbnail(fileURL)
thumbnailImage = LocalFileInformationGenerator.officeThumbnail(fileURL, dimension)
case LocalFileInformationGenerator.customThumbnailExtensions.contains:
thumbnailImage = LocalFileInformationGenerator.customThumbnail(fileURL)
thumbnailImage = LocalFileInformationGenerator.customThumbnail(fileURL, dimension)
default:
completionHandler(nil, nil)
return
}
if let image = thumbnailImage {
let scaledImage = LocalFileProvider.scaleDown(image: image, toSize: dimension)
completionHandler(scaledImage, nil)
}
completionHandler(thumbnailImage, nil)
}
return nil
}
@@ -92,7 +89,7 @@ extension LocalFileProvider: ExtendedFileProvider {
@discardableResult
open func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String: Any], _ keys: [String], _ error: Error?) -> Void)) -> Progress? {
(dispatch_queue).async {
let fileExt = (path as NSString).pathExtension.lowercased()
let fileExt = path.pathExtension.lowercased()
var getter: ((_ fileURL: URL) -> (prop: [String: Any], keys: [String]))?
switch fileExt {
case LocalFileInformationGenerator.imagePropertiesExtensions.contains:
@@ -194,23 +191,19 @@ public struct LocalFileInformationGenerator {
static public var customPropertiesExtensions: [String] = []
/// Thumbnail generator closure for image files.
static public var imageThumbnail: (_ fileURL: URL) -> ImageClass? = { fileURL in
return ImageClass(contentsOfFile: fileURL.path)
static public var imageThumbnail: (_ fileURL: URL, _ dimension: CGSize?) -> ImageClass? = { fileURL, dimension in
return LocalFileProvider.scaleDown(fileURL: fileURL, toSize: dimension)
}
/// Thumbnail generator closure for audio and music files.
static public var audioThumbnail: (_ fileURL: URL) -> ImageClass? = { fileURL in
static public var audioThumbnail: (_ fileURL: URL, _ dimension: CGSize?) -> ImageClass? = { fileURL, dimension in
let playerItem = AVPlayerItem(url: fileURL)
let metadataList = playerItem.asset.commonMetadata
#if swift(>=4.0)
let commonKeyArtwork = AVMetadataKey.commonKeyArtwork
#else
let commonKeyArtwork = AVMetadataCommonKeyArtwork
#endif
for item in metadataList {
if item.commonKey == commonKeyArtwork {
if let data = item.dataValue {
return ImageClass(data: data)
return LocalFileProvider.scaleDown(data: data, toSize: dimension)
}
}
}
@@ -218,9 +211,10 @@ public struct LocalFileInformationGenerator {
}
/// Thumbnail generator closure for video files.
static public var videoThumbnail: (_ fileURL: URL) -> ImageClass? = { fileURL in
static public var videoThumbnail: (_ fileURL: URL, _ dimension: CGSize?) -> ImageClass? = { fileURL, dimension in
let asset = AVAsset(url: fileURL)
let assetImgGenerate = AVAssetImageGenerator(asset: asset)
assetImgGenerate.maximumSize = dimension ?? .zero
assetImgGenerate.appliesPreferredTrackTransform = true
let time = CMTime(value: asset.duration.value / 3, timescale: asset.duration.timescale)
if let cgImage = try? assetImgGenerate.copyCGImage(at: time, actualTime: nil) {
@@ -234,19 +228,19 @@ public struct LocalFileInformationGenerator {
}
/// Thumbnail generator closure for portable document files files.
static public var pdfThumbnail: (_ fileURL: URL) -> ImageClass? = { fileURL in
return LocalFileProvider.convertToImage(pdfURL: fileURL)
static public var pdfThumbnail: (_ fileURL: URL, _ dimension: CGSize?) -> ImageClass? = { fileURL, dimension in
return LocalFileProvider.convertToImage(pdfURL: fileURL, maxSize: dimension)
}
/// Thumbnail generator closure for office document files.
/// - Note: No default implementation is avaiable
static public var officeThumbnail: (_ fileURL: URL) -> ImageClass? = { fileURL in
static public var officeThumbnail: (_ fileURL: URL, _ dimension: CGSize?) -> ImageClass? = { fileURL, dimension in
return nil
}
/// Thumbnail generator closure for custom type of files.
/// - Note: No default implementation is avaiable
static public var customThumbnail: (_ fileURL: URL) -> ImageClass? = { fileURL in
static public var customThumbnail: (_ fileURL: URL, _ dimension: CGSize?) -> ImageClass? = { fileURL, dimension in
return nil
}
@@ -276,18 +270,17 @@ public struct LocalFileInformationGenerator {
return(Int(newTopVal), Int(newBottomVal))
}
guard let cgDataRef = CGImageSourceCreateWithURL(fileURL as CFURL, nil), let cfImageDict = CGImageSourceCopyPropertiesAtIndex(cgDataRef, 0, nil) else {
guard let source = CGImageSourceCreateWithURL(fileURL as CFURL, nil), let properties = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as NSDictionary? else {
return (dic, keys)
}
let imageDict = cfImageDict as NSDictionary
let tiffDict = imageDict[kCGImagePropertyTIFFDictionary as String] as? NSDictionary ?? [:]
let exifDict = imageDict[kCGImagePropertyExifDictionary as String] as? NSDictionary ?? [:]
let gpsDict = imageDict[kCGImagePropertyGPSDictionary as String] as? NSDictionary ?? [:]
let tiffDict = properties[kCGImagePropertyTIFFDictionary as String] as? NSDictionary ?? [:]
let exifDict = properties[kCGImagePropertyExifDictionary as String] as? NSDictionary ?? [:]
let gpsDict = properties[kCGImagePropertyGPSDictionary as String] as? NSDictionary ?? [:]
if let pixelWidth = imageDict.object(forKey: kCGImagePropertyPixelWidth) as? NSNumber, let pixelHeight = imageDict.object(forKey: kCGImagePropertyPixelHeight) as? NSNumber {
if let pixelWidth = properties.object(forKey: kCGImagePropertyPixelWidth) as? NSNumber, let pixelHeight = properties.object(forKey: kCGImagePropertyPixelHeight) as? NSNumber {
add(key: "Dimensions", value: "\(pixelWidth)x\(pixelHeight)")
}
add(key: "DPI", value: imageDict[kCGImagePropertyDPIWidth as String])
add(key: "DPI", value: properties[kCGImagePropertyDPIWidth as String])
add(key: "Device maker", value: tiffDict[kCGImagePropertyTIFFMake as String])
add(key: "Device model", value: tiffDict[kCGImagePropertyTIFFModel as String])
add(key: "Lens model", value: exifDict[kCGImagePropertyExifLensModel as String])
@@ -302,9 +295,9 @@ public struct LocalFileInformationGenerator {
}
add(key: "Area", value: gpsDict[kCGImagePropertyGPSAreaInformation as String])
add(key: "Color space", value: imageDict[kCGImagePropertyColorModel as String])
add(key: "Color depth", value: (imageDict[kCGImagePropertyDepth as String] as? NSNumber).map({ "\($0) bits" }))
add(key: "Color profile", value: imageDict[kCGImagePropertyProfileName as String])
add(key: "Color space", value: properties[kCGImagePropertyColorModel as String])
add(key: "Color depth", value: (properties[kCGImagePropertyDepth as String] as? NSNumber).map({ "\($0) bits" }))
add(key: "Color profile", value: properties[kCGImagePropertyProfileName as String])
add(key: "Focal length", value: exifDict[kCGImagePropertyExifFocalLength as String])
add(key: "White banance", value: exifDict[kCGImagePropertyExifWhiteBalance as String])
add(key: "F number", value: exifDict[kCGImagePropertyExifFNumber as String])
@@ -359,11 +352,7 @@ public struct LocalFileInformationGenerator {
let playerItem = AVPlayerItem(url: fileURL)
let metadataList = playerItem.asset.commonMetadata
for item in metadataList {
#if swift(>=4.0)
let commonKey = item.commonKey?.rawValue
#else
let commonKey = item.commonKey
#endif
let commonKey = item.commonKey?.rawValue
if let key = makeKeyDescription(commonKey) {
if commonKey == "location", let value = item.stringValue, let loc = parseLocationData(value) {
keys.append(key)
@@ -404,16 +393,12 @@ public struct LocalFileInformationGenerator {
dic = audioprops.prop
keys = audioprops.keys
dic.removeValue(forKey: "Duration")
if let index = keys.index(of: "Duration") {
if let index = keys.firstIndex(of: "Duration") {
keys.remove(at: index)
}
}
let asset = AVURLAsset(url: fileURL, options: nil)
#if swift(>=4.0)
let videoTracks = asset.tracks(withMediaType: AVMediaType.video)
#else
let videoTracks = asset.tracks(withMediaType: AVMediaTypeVideo)
#endif
if let videoTrack = videoTracks.first {
var bitrate: Float = 0
let width = Int(videoTrack.naturalSize.width)
@@ -427,11 +412,7 @@ public struct LocalFileInformationGenerator {
add(key: "Duration", value: TimeInterval(duration).formatshort)
add(key: "Video Bitrate", value: "\(Int(ceil(bitrate / 1000))) kbps")
}
#if swift(>=4.0)
let audioTracks = asset.tracks(withMediaType: AVMediaType.audio)
#else
let audioTracks = asset.tracks(withMediaType: AVMediaTypeAudio)
#endif
// dic["Audio channels"] = audioTracks.count
var bitrate: Float = 0
for track in audioTracks {
@@ -509,7 +490,7 @@ public struct LocalFileInformationGenerator {
add(key: "Content creator", value: getKey("Creator", from: dict))
add(key: "Creation date", value: convertDate(getKey("CreationDate", from: dict)))
add(key: "Modified date", value: convertDate(getKey("ModDate", from: dict)))
add(key: "Security", value: reference.isEncrypted)
add(key: "Security", value: reference.isEncrypted ? (reference.isUnlocked ? "Present" : "Password Protected") : "None")
add(key: "Allows printing", value: reference.allowsPrinting)
add(key: "Allows copying", value: reference.allowsCopying)
return (dic, keys)
+117 -19
View File
@@ -8,7 +8,7 @@
import Foundation
public extension Array where Element: FileObject {
extension Array where Element: FileObject {
/// Returns a sorted array of `FileObject`s by criterias set in attributes.
public func sort(by type: FileObjectSorting.SortType, ascending: Bool = true, isDirectoriesFirst: Bool = false) -> [Element] {
let sorting = FileObjectSorting(type: type, ascending: ascending, isDirectoriesFirst: isDirectoriesFirst)
@@ -28,7 +28,7 @@ public extension Sequence where Iterator.Element == UInt8 {
}
}
public extension URLFileResourceType {
extension URLFileResourceType {
/// **FileProvider** returns corresponding `URLFileResourceType` of a `FileAttributeType` value
public init(fileTypeValue: FileAttributeType) {
switch fileTypeValue {
@@ -44,7 +44,33 @@ public extension URLFileResourceType {
}
}
public extension URLResourceKey {
extension CocoaError {
init(_ code: CocoaError.Code, path: String?) {
if let path = path {
let userInfo: [String: Any] = [NSFilePathErrorKey: path]
self.init(code, userInfo: userInfo)
} else {
self.init(code)
}
}
}
extension URLError {
init(_ code: URLError.Code, url: URL?) {
if let url = url {
let userInfo: [String: Any] = [NSURLErrorKey: url,
NSURLErrorFailingURLErrorKey: url,
NSURLErrorFailingURLStringErrorKey: url.absoluteString,
]
self.init(code, userInfo: userInfo)
} else {
self.init(code)
}
}
}
extension URLResourceKey {
/// **FileProvider** returns url of file object.
public static let fileURLKey = URLResourceKey(rawValue: "NSURLFileURLKey")
/// **FileProvider** returns modification date of file in server
@@ -59,7 +85,7 @@ public extension URLResourceKey {
public static let childrensCount = URLResourceKey(rawValue: "MFPURLChildrensCount")
}
public extension ProgressUserInfoKey {
extension ProgressUserInfoKey {
/// **FileProvider** returns associated `FileProviderOperationType`
public static let fileProvderOperationTypeKey = ProgressUserInfoKey("MFPOperationTypeKey")
/// **FileProvider** returns start date/time of operation
@@ -76,7 +102,7 @@ internal extension URL {
}
var fileSize: Int64 {
return Int64((try? self.resourceValues(forKeys: [.fileSizeKey]))?.fileSize ?? -1)
return (try? self.resourceValues(forKeys: [.fileSizeKey]))?.allValues[.fileSizeKey] as? Int64 ?? -1
}
var fileExists: Bool {
@@ -91,7 +117,7 @@ internal extension URL {
#endif
}
public extension URLRequest {
extension URLRequest {
/// Defines HTTP Authentication method required to access
public enum AuthenticationType {
/// Basic method for authentication
@@ -358,7 +384,7 @@ internal extension URLRequest {
self.setValue(contentType.rawValue + parameter, forHTTPHeaderField: "Content-Type")
}
mutating func setValue(dropboxArgKey requestDictionary: [String: AnyObject]) {
mutating func setValue(dropboxArgKey requestDictionary: [String: Any]) {
guard let jsonData = try? JSONSerialization.data(withJSONObject: requestDictionary, options: []) else {
return
}
@@ -373,12 +399,12 @@ internal extension CharacterSet {
static let filePathAllowed = CharacterSet.urlPathAllowed.subtracting(CharacterSet(charactersIn: ":"))
}
internal extension Data {
internal var isPDF: Bool {
extension Data {
var isPDF: Bool {
return self.count > 4 && self.scanString(length: 4, using: .ascii) == "%PDF"
}
init? (jsonDictionary dictionary: [String: AnyObject]) {
init? (jsonDictionary dictionary: [String: Any]) {
guard JSONSerialization.isValidJSONObject(dictionary) else { return nil }
guard let data = try? JSONSerialization.data(withJSONObject: dictionary, options: []) else {
return nil
@@ -386,8 +412,8 @@ internal extension Data {
self = data
}
func deserializeJSON() -> [String: AnyObject]? {
return (try? JSONSerialization.jsonObject(with: self, options: [])) as? [String: AnyObject]
func deserializeJSON() -> [String: Any]? {
return (try? JSONSerialization.jsonObject(with: self, options: [])) as? [String: Any]
}
init<T>(value: T) {
@@ -397,13 +423,22 @@ internal extension Data {
func scanValue<T>() -> T? {
guard MemoryLayout<T>.size <= self.count else { return nil }
#if swift(>=5.0)
return self.withUnsafeBytes { $0.load(as: T.self) }
#else
return self.withUnsafeBytes { $0.pointee }
#endif
}
func scanValue<T>(start: Int) -> T? {
let length = MemoryLayout<T>.size
guard self.count >= start + length else { return nil }
return self.subdata(in: start..<start+length).withUnsafeBytes { $0.pointee }
let subdata = self.subdata(in: start..<start+length)
#if swift(>=5.0)
return subdata.withUnsafeBytes { $0.load(as: T.self) }
#else
return subdata.withUnsafeBytes { $0.pointee }
#endif
}
func scanString(start: Int = 0, length: Int, using encoding: String.Encoding = .utf8) -> String? {
@@ -413,14 +448,14 @@ internal extension Data {
}
internal extension String {
init? (jsonDictionary: [String: AnyObject]) {
init? (jsonDictionary: [String: Any]) {
guard let data = Data(jsonDictionary: jsonDictionary) else {
return nil
}
self.init(data: data, encoding: .utf8)
}
func deserializeJSON(using encoding: String.Encoding = .utf8) -> [String: AnyObject]? {
func deserializeJSON(using encoding: String.Encoding = .utf8) -> [String: Any]? {
guard let data = self.data(using: encoding) else {
return nil
}
@@ -441,7 +476,7 @@ internal extension String {
}
}
internal extension NSNumber {
extension NSNumber {
internal func format(precision: Int = 2, style: NumberFormatter.Style = .decimal) -> String {
let formatter = NumberFormatter()
formatter.maximumFractionDigits = precision
@@ -450,8 +485,26 @@ internal extension NSNumber {
}
}
internal extension TimeInterval {
internal var formatshort: String {
extension String {
internal var pathExtension: String {
return (self as NSString).pathExtension
}
internal func appendingPathComponent(_ pathComponent: String) -> String {
return (self as NSString).appendingPathComponent(pathComponent)
}
internal var lastPathComponent: String {
return (self as NSString).lastPathComponent
}
internal var deletingLastPathComponent: String {
return (self as NSString).deletingLastPathComponent
}
}
extension TimeInterval {
var formatshort: String {
var result = "0:00"
if self < TimeInterval(Int32.max) {
result = ""
@@ -476,7 +529,7 @@ internal extension TimeInterval {
}
}
public extension Date {
extension Date {
/// Date formats used commonly in internet messaging defined by various RFCs.
public enum RFCStandards: String {
/// Obsolete (2-digit year) date format defined by RFC 822 for http.
@@ -544,6 +597,51 @@ public extension Date {
}
}
extension InputStream {
func readData(ofLength length: Int) throws -> Data {
var data = Data(count: length)
#if swift(>=5.0)
let result = data.withUnsafeMutableBytes { (buf) -> Int in
let p = buf.bindMemory(to: UInt8.self).baseAddress!
return self.read(p, maxLength: buf.count)
}
#else
let bufcount = data.count
let result = data.withUnsafeMutableBytes { (p) -> Int in
return self.read(p, maxLength: bufcount)
}
#endif
if result < 0 {
throw self.streamError ?? POSIXError(.EIO)
} else {
data.count = result
return data
}
}
}
extension OutputStream {
func write(data: Data) throws -> Int {
#if swift(>=5.0)
let result = data.withUnsafeBytes { (buf) -> Int in
let p = buf.bindMemory(to: UInt8.self).baseAddress!
return self.write(p, maxLength: buf.count)
}
#else
let bufcount = data.count
let result = data.withUnsafeBytes { (p) -> Int in
return self.write(p, maxLength: bufcount)
}
#endif
if result < 0 {
throw self.streamError ?? POSIXError(.EIO)
} else {
return result
}
}
}
internal extension NSPredicate {
func findValue(forKey key: String?, operator op: NSComparisonPredicate.Operator? = nil) -> Any? {
let val = findAllValues(forKey: key).lazy.filter { (op == nil || $0.operator == op!) && !$0.not }
+18 -21
View File
@@ -323,11 +323,19 @@ final class HMAC<Variant: SHA2Variant> {
}
static func authenticate(message: Data, withKey key: Data) -> Data {
#if swift(>=5.0)
return Data(authenticate(message: Array(message), withKey: Array(key)))
#else
return Data(bytes: authenticate(message: Array(message), withKey: Array(key)))
#endif
}
static func authenticate(message: String, withKey key: Data) -> Data {
#if swift(>=5.0)
return Data(authenticate(message: [UInt8](message.utf8), withKey: Array(key)))
#else
return Data(bytes: authenticate(message: [UInt8](message.utf8), withKey: Array(key)))
#endif
}
}
@@ -397,29 +405,18 @@ fileprivate func toUInt64Array(_ slice: ArraySlice<UInt8>) -> Array<UInt64> {
return result
}
fileprivate func arrayOfBytes<T>(_ value:T, length:Int? = nil) -> [UInt8] {
let totalBytes = length ?? MemoryLayout<T>.size
let valuePointer = UnsafeMutablePointer<T>.allocate(capacity: 1)
valuePointer.pointee = value
let bytesPointer = UnsafeMutableRawPointer(valuePointer).assumingMemoryBound(to: UInt8.self)
var bytes = [UInt8](repeating: 0, count: totalBytes)
for j in 0..<min(MemoryLayout<T>.size,totalBytes) {
bytes[totalBytes - 1 - j] = (bytesPointer + j).pointee
fileprivate func arrayOfBytes<T>(_ value: T, length: Int? = nil) -> [UInt8] {
var value = value
return Swift.withUnsafeBytes(of: &value) { (buffer: UnsafeRawBufferPointer) -> [UInt8] in
if let length = length {
return Array(buffer.prefix(length))
} else {
return Array(buffer)
}
}
valuePointer.deinitialize(count: 1)
#if swift(>=4.1)
valuePointer.deallocate()
#else
valuePointer.deallocate(capacity: 1)
#endif
return bytes
}
public extension String {
extension String {
public func fp_sha256() -> [UInt8] {
return SHA2<SHA256>.calculate([UInt8](self.utf8))
}
@@ -433,7 +430,7 @@ public extension String {
}
}
public extension Data {
extension Data {
public func fp_sha256() -> [UInt8] {
return SHA2<SHA256>.calculate(Array(self))
}
File diff suppressed because it is too large Load Diff
+111 -37
View File
@@ -27,7 +27,7 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
}
open class var type: String { return "FTP" }
open let baseURL: URL?
public let baseURL: URL?
open var dispatch_queue: DispatchQueue
open var operation_queue: OperationQueue {
@@ -74,6 +74,10 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
}
}
#if os(macOS) || os(iOS) || os(tvOS)
open var undoManager: UndoManager? = nil
#endif
/**
Initializer for FTP provider with given username and password.
@@ -106,11 +110,7 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
self.credential = credential
self.supportsRFC3659 = true
#if swift(>=3.1)
let queueLabel = "FileProvider.\(Swift.type(of: self).type)"
#else
let queueLabel = "FileProvider.\(type(of: self).type)"
#endif
dispatch_queue = DispatchQueue(label: queueLabel, attributes: .concurrent)
operation_queue = OperationQueue()
operation_queue.name = "\(queueLabel).Operation"
@@ -135,7 +135,13 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
}
public required convenience init?(coder aDecoder: NSCoder) {
guard let baseURL = aDecoder.decodeObject(forKey: "baseURL") as? URL else { return nil }
guard let baseURL = aDecoder.decodeObject(of: NSURL.self, forKey: "baseURL") as URL? else {
if #available(macOS 10.11, iOS 9.0, tvOS 9.0, *) {
aDecoder.failWithError(CocoaError(.coderValueNotFound,
userInfo: [NSLocalizedDescriptionKey: "Base URL is not set."]))
}
return nil
}
let mode: Mode
if let modeStr = aDecoder.decodeObject(of: NSString.self, forKey: "mode") as String?, let mode_v = Mode(rawValue: modeStr) {
mode = mode_v
@@ -198,7 +204,7 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
- Note: Disabling this option will increase upload speed.
*/
public var uploadByREST: Bool = FileProviderStreamTask.defaultUseURLSession
public var uploadByREST: Bool = false
/**
Determines data connection must TLS or not. `false` value indicates to use `PROT C` and
@@ -206,6 +212,12 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
*/
public var securedDataConnection: Bool = true
/**
Trust all certificates if `disableEvaluation`, Otherwise validate certificate chain.
Default is `performDefaultEvaluation`.
*/
public var serverTrustPolicy: ServerTrustPolicy = .performDefaultEvaluation(validateHost: true)
open func contentsOfDirectory(path: String, completionHandler: @escaping ([FileObject], Error?) -> Void) {
self.contentsOfDirectory(path: path, rfc3659enabled: supportsRFC3659, completionHandler: completionHandler)
}
@@ -225,6 +237,8 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
let path = ftpPath(apath)
let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!)
task.serverTrustPolicy = serverTrustPolicy
task.taskDescription = FileOperationType.fetch(path: path).json
self.ftpLogin(task) { (error) in
if let error = error {
self.dispatch_queue.async {
@@ -280,6 +294,8 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
let path = ftpPath(apath)
let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!)
task.serverTrustPolicy = serverTrustPolicy
task.taskDescription = FileOperationType.fetch(path: path).json
self.ftpLogin(task) { (error) in
if let error = error {
self.dispatch_queue.async {
@@ -299,7 +315,7 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
}
guard let response = response, response.hasPrefix("250") || (response.hasPrefix("50") && rfc3659enabled) else {
throw self.urlError(path, code: .badServerResponse)
throw URLError(.badServerResponse, url: self.url(of: path))
}
if response.hasPrefix("500") {
@@ -309,9 +325,9 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
let lines = response.components(separatedBy: "\n").compactMap { $0.isEmpty ? nil : $0.trimmingCharacters(in: .whitespacesAndNewlines) }
guard lines.count > 2 else {
throw self.urlError(path, code: .badServerResponse)
throw URLError(.badServerResponse, url: self.url(of: path))
}
let dirPath = (path as NSString).deletingLastPathComponent
let dirPath = path.deletingLastPathComponent
let file: FileObject? = rfc3659enabled ?
self.parseMLST(lines[1], in: dirPath) :
(self.parseUnixList(lines[1], in: dirPath) ?? self.parseDOSList(lines[1], in: dirPath))
@@ -405,7 +421,7 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
@discardableResult
open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> Progress? {
let path = (atPath as NSString).appendingPathComponent(folderName) + "/"
let path = atPath.appendingPathComponent(folderName) + "/"
return doOperation(.create(path: path), completionHandler: completionHandler)
}
@@ -426,10 +442,17 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
@discardableResult
open func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
guard (try? localFile.checkResourceIsReachable()) ?? false else {
dispatch_queue.async {
completionHandler?(URLError(.fileDoesNotExist, url: localFile))
}
return nil
}
// check file is not a folder
guard (try? localFile.resourceValues(forKeys: [.fileResourceTypeKey]))?.fileResourceType ?? .unknown == .regular else {
dispatch_queue.async {
completionHandler?(self.urlError(localFile.path, code: .fileIsDirectory))
completionHandler?(URLError(.fileIsDirectory, url: localFile))
}
return nil
}
@@ -439,12 +462,17 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
return nil
}
let progress = Progress(totalUnitCount: 0)
let progress = Progress(totalUnitCount: -1)
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
progress.kind = .file
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!)
task.serverTrustPolicy = serverTrustPolicy
task.taskDescription = operation.json
progress.cancellationHandler = { [weak task] in
task?.cancel()
}
self.ftpLogin(task) { (error) in
if let error = error {
self.dispatch_queue.async {
@@ -454,7 +482,11 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
return
}
self.ftpStore(task, filePath: self.ftpPath(toPath), fromData: nil, fromFile: localFile, onTask: { task in
guard let stream = InputStream(url: localFile) else {
return
}
let size = localFile.fileSize
self.ftpStore(task, filePath: self.ftpPath(toPath), from: stream, size: size, onTask: { task in
weak var weakTask = task
progress.cancellationHandler = {
weakTask?.cancel()
@@ -485,12 +517,17 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: operation) ?? true == true else {
return nil
}
let progress = Progress(totalUnitCount: 0)
let progress = Progress(totalUnitCount: -1)
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
progress.kind = .file
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!)
task.serverTrustPolicy = serverTrustPolicy
task.taskDescription = operation.json
progress.cancellationHandler = { [weak task] in
task?.cancel()
}
self.ftpLogin(task) { (error) in
if let error = error {
self.dispatch_queue.async {
@@ -499,7 +536,12 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
return
}
self.ftpDownload(task, filePath: self.ftpPath(path), onTask: { task in
let tempURL = URL(fileURLWithPath: NSTemporaryDirectory().appendingPathComponent(UUID().uuidString))
guard let stream = OutputStream(url: tempURL, append: false) else {
completionHandler?(CocoaError(.fileWriteUnknown, path: destURL.path))
return
}
self.ftpDownload(task, filePath: self.ftpPath(path), to: stream, onTask: { task in
weak var weakTask = task
progress.cancellationHandler = {
weakTask?.cancel()
@@ -509,22 +551,24 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
progress.totalUnitCount = totalSize
progress.completedUnitCount = totalReceived
self.delegateNotify(operation, progress: progress.fractionCompleted)
}) { (tmpurl, error) in
if let error = error {
}) { (error) in
if error != nil {
progress.cancel()
}
do {
try FileManager.default.moveItem(at: tempURL, to: destURL)
} catch {
self.dispatch_queue.async {
completionHandler?(error)
self.delegateNotify(operation, error: error)
}
try? FileManager.default.removeItem(at: tempURL)
return
}
if let tmpurl = tmpurl {
try? FileManager.default.moveItem(at: tmpurl, to: destURL)
self.dispatch_queue.async {
completionHandler?(nil)
self.delegateNotify(operation)
}
self.dispatch_queue.async {
completionHandler?(error)
self.delegateNotify(operation, error: error)
}
}
}
@@ -541,12 +585,17 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
}
return nil
}
let progress = Progress(totalUnitCount: 0)
let progress = Progress(totalUnitCount: -1)
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
progress.kind = .file
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!)
task.serverTrustPolicy = serverTrustPolicy
task.taskDescription = operation.json
progress.cancellationHandler = { [weak task] in
task?.cancel()
}
self.ftpLogin(task) { (error) in
if let error = error {
self.dispatch_queue.async {
@@ -555,17 +604,18 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
return
}
self.ftpFileData(task, filePath: self.ftpPath(path), from: offset, length: length, onTask: { task in
let stream = OutputStream.toMemory()
self.ftpDownload(task, filePath: self.ftpPath(path), from: offset, length: length, to: stream, onTask: { task in
weak var weakTask = task
progress.cancellationHandler = {
weakTask?.cancel()
}
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
}, onProgress: { _, recevied, totalReceived, totalSize in
}, onProgress: { recevied, totalReceived, totalSize in
progress.totalUnitCount = totalSize
progress.completedUnitCount = totalReceived
self.delegateNotify(operation, progress: progress.fractionCompleted)
}) { (data, error) in
}) { (error) in
if let error = error {
progress.cancel()
self.dispatch_queue.async {
@@ -575,7 +625,7 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
return
}
if let data = data {
if let data = stream.property(forKey: .dataWrittenToMemoryStreamKey) as? Data {
self.dispatch_queue.async {
completionHandler(data, nil)
self.delegateNotify(operation)
@@ -594,12 +644,17 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
return nil
}
let progress = Progress(totalUnitCount: Int64(data?.count ?? 0))
let progress = Progress(totalUnitCount: Int64(data?.count ?? -1))
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
progress.kind = .file
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!)
task.serverTrustPolicy = serverTrustPolicy
task.taskDescription = operation.json
progress.cancellationHandler = { [weak task] in
task?.cancel()
}
self.ftpLogin(task) { (error) in
if let error = error {
self.dispatch_queue.async {
@@ -610,7 +665,9 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
}
let storeHandler = {
self.ftpStore(task, filePath: self.ftpPath(path), fromData: data ?? Data(), fromFile: nil, onTask: { task in
let data = data ?? Data()
let stream = InputStream(data: data)
self.ftpStore(task, filePath: self.ftpPath(path), from: stream, size: Int64(data.count), onTask: { task in
weak var weakTask = task
progress.cancellationHandler = {
weakTask?.cancel()
@@ -654,12 +711,17 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
}
return nil
}
let progress = Progress(totalUnitCount: 0)
let progress = Progress(totalUnitCount: -1)
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
progress.kind = .file
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!)
task.serverTrustPolicy = serverTrustPolicy
task.taskDescription = operation.json
progress.cancellationHandler = { [weak task] in
task?.cancel()
}
self.ftpLogin(task) { (error) in
if let error = error {
self.dispatch_queue.async {
@@ -668,7 +730,7 @@ open class FTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOpera
return
}
self.ftpFileData(task, filePath: self.ftpPath(path), from: offset, length: length, onTask: { task in
self.ftpDownloadData(task, filePath: self.ftpPath(path), from: offset, length: length, onTask: { task in
weak var weakTask = task
progress.cancellationHandler = {
weakTask?.cancel()
@@ -741,6 +803,11 @@ extension FTPFileProvider {
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!)
task.serverTrustPolicy = serverTrustPolicy
task.taskDescription = operation.json
progress.cancellationHandler = { [weak task] in
task?.cancel()
}
self.ftpLogin(task) { (error) in
if let error = error {
completionHandler?(error)
@@ -757,7 +824,7 @@ extension FTPFileProvider {
guard let response = response else {
completionHandler?(error)
self.delegateNotify(operation, error: self.urlError(sourcePath, code: .badServerResponse))
self.delegateNotify(operation, error: URLError(.badServerResponse, url: self.url(of: sourcePath)))
return
}
@@ -782,13 +849,16 @@ extension FTPFileProvider {
case .link: errorCode = .cannotWriteToFile
default: errorCode = .cannotOpenFile
}
let error = self.urlError(sourcePath, code: errorCode)
let error = URLError(errorCode, url: self.url(of: sourcePath))
progress.cancel()
completionHandler?(error)
self.delegateNotify(operation, error: error)
return
}
#if os(macOS) || os(iOS) || os(tvOS)
self._registerUndo(operation)
#endif
progress.completedUnitCount = progress.totalUnitCount
completionHandler?(nil)
self.delegateNotify(operation)
@@ -839,7 +909,7 @@ extension FTPFileProvider {
}
guard let response = response else {
throw self.urlError(sourcePath, code: .badServerResponse)
throw URLError(.badServerResponse, url: self.url(of: sourcePath))
}
if response.hasPrefix("50") {
@@ -848,7 +918,7 @@ extension FTPFileProvider {
}
if !response.hasPrefix("2") {
throw self.urlError(sourcePath, code: .cannotRemoveFile)
throw URLError(.cannotRemoveFile, url: self.url(of: sourcePath))
}
self.dispatch_queue.async {
completionHandler?(nil)
@@ -901,3 +971,7 @@ extension FTPFileProvider {
}
extension FTPFileProvider: FileProvider { }
#if os(macOS) || os(iOS) || os(tvOS)
extension FTPFileProvider: FileProvideUndoable { }
#endif
+467 -394
View File
File diff suppressed because it is too large Load Diff
+26 -30
View File
@@ -34,7 +34,10 @@ open class FileObject: NSObject {
if let url = allValues[.fileURLKey] as? URL {
return url
} else {
let path = self.path.addingPercentEncoding(withAllowedCharacters: .filePathAllowed) ?? self.path
var path = self.path.addingPercentEncoding(withAllowedCharacters: .filePathAllowed) ?? self.path
if path.hasPrefix("/") {
path.remove(at: path.startIndex)
}
return URL(string: path) ?? URL(string: "/")!
}
}
@@ -151,40 +154,33 @@ open class FileObject: NSObject {
}
extension FileObject {
open override var hashValue: Int {
open override var hash: Int {
#if swift(>=4.2)
var hasher = Hasher()
hasher.combine(url)
hasher.combine(size)
hasher.combine(modifiedDate)
return hasher.finalize()
#else
let hashURL = self.url.hashValue
let hashSize = self.size.hashValue
return (hashURL << 7) &+ hashURL &+ hashSize
}
open override var hash: Int {
return self.hashValue
}
/// Check `FileObject` equality
public static func ==(lhs: FileObject, rhs: FileObject) -> Bool {
if rhs === lhs {
return true
}
#if swift(>=3.1)
if Swift.type(of: lhs) != Swift.type(of: rhs) {
return false
}
#else
if type(of: lhs) != type(of: rhs) {
return false
}
#endif
if let rurl = rhs.allValues[.fileURLKey] as? URL, let lurl = lhs.allValues[.fileURLKey] as? URL {
return rurl == lurl && rhs.size == lhs.size
}
return rhs.path == lhs.path && rhs.size == lhs.size && rhs.modifiedDate == lhs.modifiedDate
}
open override func isEqual(_ object: Any?) -> Bool {
guard let object = object as? FileObject else { return false }
return self == object
if self === object {
return true
}
if Swift.type(of: self) != Swift.type(of: object) {
return false
}
if let rurl = self.allValues[.fileURLKey] as? URL, let lurl = object.allValues[.fileURLKey] as? URL {
return rurl == lurl && self.size == object.size
}
return self.path == object.path && self.size == object.size && self.modifiedDate == object.modifiedDate
}
}
@@ -227,6 +223,7 @@ extension FileObject {
case .and: return NSCompoundPredicate(andPredicateWithSubpredicates: newSub)
case .not: return NSCompoundPredicate(notPredicateWithSubpredicate: newSub[0])
case .or: return NSCompoundPredicate(orPredicateWithSubpredicates: newSub)
@unknown default: fatalError()
}
} else if let cQuery = query as? NSComparisonPredicate {
var newLeft = cQuery.leftExpression
@@ -343,7 +340,6 @@ open class VolumeObject: NSObject {
}
}
/// Sorting FileObject array by given criteria, **not thread-safe**
public struct FileObjectSorting {
@@ -419,8 +415,8 @@ public struct FileObjectSorting {
case .nameCaseInsensitive:
return ($0.name).localizedCaseInsensitiveCompare($1.name) == (ascending ? .orderedAscending : .orderedDescending)
case .extension:
let kind1 = $0.isDirectory ? "folder" : ($0.path as NSString).pathExtension
let kind2 = $1.isDirectory ? "folder" : ($1.path as NSString).pathExtension
let kind1 = $0.isDirectory ? "folder" : $0.path.pathExtension
let kind2 = $1.isDirectory ? "folder" : $1.path.pathExtension
return kind1.localizedCaseInsensitiveCompare(kind2) == (ascending ? .orderedAscending : .orderedDescending)
case .modifiedDate:
let fileMod1 = $0.modifiedDate ?? Date.distantPast
+171 -122
View File
@@ -9,9 +9,11 @@
import Foundation
#if os(iOS) || os(tvOS)
import UIKit
import ImageIO
public typealias ImageClass = UIImage
#elseif os(macOS)
import Cocoa
import ImageIO
public typealias ImageClass = NSImage
#endif
@@ -19,7 +21,7 @@ public typealias ImageClass = NSImage
public typealias SimpleCompletionHandler = ((_ error: Error?) -> Void)?
/// This protocol defines FileProvider neccesary functions and properties to connect and get contents list
public protocol FileProviderBasic: class, NSSecureCoding {
public protocol FileProviderBasic: class, NSSecureCoding, CustomDebugStringConvertible {
/// An string to identify type of provider.
static var type: String { get }
@@ -201,6 +203,13 @@ extension FileProviderBasic {
operation_queue.maxConcurrentOperationCount = newValue
}
}
public var debugDescription: String {
let typeDesc = "\(Self.type) provider"
let urlDesc = self.baseURL.map({ " - " + $0.absoluteString }) ?? ""
let credentialDesc = self.credential?.user.map({ " - " + $0.debugDescription }) ?? ""
return typeDesc + urlDesc + credentialDesc
}
}
/// Checking equality of two file provider, regardless of current path queues and delegates.
@@ -423,7 +432,7 @@ public protocol FileProviderOperations: FileProviderBasic {
func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) -> Progress?
}
public extension FileProviderOperations {
extension FileProviderOperations {
@discardableResult
public func moveItem(path: String, to: String, completionHandler: SimpleCompletionHandler) -> Progress? {
return self.moveItem(path: path, to: to, overwrite: false, completionHandler: completionHandler)
@@ -440,7 +449,7 @@ public extension FileProviderOperations {
}
}
internal extension FileProviderOperations {
extension FileProviderOperations {
internal func delegateNotify(_ operation: FileOperationType, error: Error? = nil) {
DispatchQueue.main.async(execute: {
if let error = error {
@@ -623,7 +632,7 @@ public protocol FileProviderReadWriteProgressive {
func contents(path: String, offset: Int64, length: Int, responseHandler: ((_ response: URLResponse) -> Void)?, progressHandler: @escaping (_ position: Int64, _ data: Data) -> Void, completionHandler: SimpleCompletionHandler) -> Progress?
}
public extension FileProviderReadWriteProgressive {
extension FileProviderReadWriteProgressive {
@discardableResult
public func contents(path: String, progressHandler: @escaping (_ position: Int64, _ data: Data) -> Void, completionHandler: SimpleCompletionHandler) -> Progress? {
return contents(path: path, offset: 0, length: -1, responseHandler: nil, progressHandler: progressHandler, completionHandler: completionHandler)
@@ -684,7 +693,7 @@ public protocol FileProvideUndoable: FileProviderOperations {
func canUndo(operation: FileOperationType) -> Bool
}
public extension FileProvideUndoable {
extension FileProvideUndoable {
public func canUndo(operation: FileOperationType) -> Bool {
return undoOperation(for: operation) != nil
}
@@ -722,6 +731,43 @@ public extension FileProvideUndoable {
self.undoManager = UndoManager()
self.undoManager?.levelsOfUndo = 10
}
public func _registerUndo(_ operation: FileOperationType) {
#if os(macOS) || os(iOS) || os(tvOS)
guard let undoManager = self.undoManager, let undoOp = self.undoOperation(for: operation) else {
return
}
let undoBox = UndoBox(provider: self, operation: operation, undoOperation: undoOp)
undoManager.beginUndoGrouping()
undoManager.registerUndo(withTarget: undoBox, selector: #selector(UndoBox.doSimpleOperation(_:)), object: undoBox)
undoManager.setActionName(operation.actionDescription)
undoManager.endUndoGrouping()
#endif
}
}
class UndoBox: NSObject {
weak var provider: FileProvideUndoable?
let operation: FileOperationType
let undoOperation: FileOperationType
init(provider: FileProvideUndoable, operation: FileOperationType, undoOperation: FileOperationType) {
self.provider = provider
self.operation = operation
self.undoOperation = undoOperation
}
@objc internal func doSimpleOperation(_ box: UndoBox) {
switch self.undoOperation {
case .move(source: let source, destination: let dest):
_=provider?.moveItem(path: source, to: dest, completionHandler: nil)
case .remove(let path):
_=provider?.removeItem(path: path, completionHandler: nil)
default:
break
}
}
}
#endif
@@ -744,24 +790,43 @@ public protocol FileProviderSharing {
func publicLink(to path: String, completionHandler: @escaping (_ link: URL?, _ attribute: FileObject?, _ expiration: Date?, _ error: Error?) -> Void)
}
//efines protocol for provider allows symbolic link operations.
public protocol FileProviderSymbolicLink {
/**
Creates a symbolic link at the specified path that points to an item at the given path.
This method does not traverse symbolic links contained in destination path, making it possible
to create symbolic links to locations that do not yet exist.
Also, if the final path component is a symbolic link, that link is not followed.
- Parameters:
- symbolicLink: The file path at which to create the new symbolic link. The last component of the path issued as the name of the link.
- withDestinationPath: The path that contains the item to be pointed to by the link. In other words, this is the destination of the link.
- completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
*/
func create(symbolicLink path: String, withDestinationPath destPath: String, completionHandler: SimpleCompletionHandler)
/// Returns the path of the item pointed to by a symbolic link.
///
/// - Parameters:
/// - path: The path of a file or directory.
/// - completionHandler: Returns destination url of given symbolic link, or an `Error` object if it fails.
func destination(ofSymbolicLink path: String, completionHandler: @escaping (_ file: FileObject?, _ error: Error?) -> Void)
}
/// Defines protocol for provider allows all common operations.
public protocol FileProvider: FileProviderOperations, FileProviderReadWrite, NSCopying {
}
internal let pathTrimSet = CharacterSet(charactersIn: " /")
public extension FileProviderBasic {
extension FileProviderBasic {
public var type: String {
#if swift(>=3.1)
return Swift.type(of: self).type
#else
return type(of: self).type
#endif
}
public func url(of path: String) -> URL {
var rpath: String = path
rpath = rpath.addingPercentEncoding(withAllowedCharacters: .filePathAllowed) ?? rpath
if let baseURL = baseURL {
if let baseURL = baseURL?.absoluteURL {
if rpath.hasPrefix("/") {
rpath.remove(at: rpath.startIndex)
}
@@ -825,24 +890,7 @@ public extension FileProviderBasic {
}
_ = group.wait(timeout: .now() + 5)
let finalFile = result + (!fileExt.isEmpty ? "." + fileExt : "")
return (dirPath as NSString).appendingPathComponent(finalFile)
}
internal func urlError(_ path: String, code: URLError.Code) -> Error {
let fileURL = self.url(of: path)
let userInfo: [String: Any] = [NSURLErrorKey: fileURL,
NSURLErrorFailingURLErrorKey: fileURL,
NSURLErrorFailingURLStringErrorKey: fileURL.absoluteString,
]
return URLError(code, userInfo: userInfo)
}
internal func cocoaError(_ path: String, code: CocoaError.Code) -> Error {
let fileURL = self.url(of: path)
let userInfo: [String: Any] = [NSFilePathErrorKey: path,
NSURLErrorKey: fileURL,
]
return CocoaError(code, userInfo: userInfo)
return dirPath.appendingPathComponent(finalFile)
}
internal func NotImplemented(_ fn: String = #function, file: StaticString = #file) {
@@ -922,133 +970,134 @@ extension ExtendedFileProvider {
return self.thumbnailOfFile(path: path, dimension: nil, completionHandler: completionHandler)
}
internal static func convertToImage(pdfData: Data?, page: Int = 1) -> ImageClass? {
internal static func convertToImage(pdfData: Data?, page: Int = 1, maxSize: CGSize?) -> ImageClass? {
guard let pdfData = pdfData else { return nil }
let cfPDFData: CFData = pdfData as CFData
if let provider = CGDataProvider(data: cfPDFData), let reference = CGPDFDocument(provider), let pageRef = reference.page(at: page) {
return self.convertToImage(pdfPage: pageRef)
return self.convertToImage(pdfPage: pageRef, maxSize: maxSize)
}
return nil
}
internal static func convertToImage(pdfURL: URL, page: Int = 1) -> ImageClass? {
internal static func convertToImage(pdfURL: URL, page: Int = 1, maxSize: CGSize?) -> ImageClass? {
// To accelerate, supporting only local file URL
guard pdfURL.isFileURL else { return nil }
if let reference = CGPDFDocument(pdfURL as CFURL), let pageRef = reference.page(at: page) {
return self.convertToImage(pdfPage: pageRef)
return self.convertToImage(pdfPage: pageRef, maxSize: maxSize)
}
return nil
}
private static func convertToImage(pdfPage: CGPDFPage) -> ImageClass? {
private static func convertToImage(pdfPage: CGPDFPage, maxSize: CGSize?) -> ImageClass? {
let scale: CGFloat
let frame = pdfPage.getBoxRect(CGPDFBox.mediaBox)
var size = frame.size
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
if let maxSize = maxSize {
scale = min(maxSize.width / frame.width, maxSize.height / frame.height)
} else {
#if os(macOS)
scale = NSScreen.main?.backingScaleFactor ?? 1.0 // fetch device is retina or not
#else
scale = UIScreen.main.scale // fetch device is retina or not
#endif
}
let rect = CGRect(origin: .zero, size: frame.size)
let size = CGSize(width: frame.size.width * scale, height: frame.size.height * scale)
let transform = pdfPage.getDrawingTransform(CGPDFBox.mediaBox, rect: rect, rotate: 0, preserveAspectRatio: true)
#if os(macOS)
#if swift(>=4.0)
let ppp = Int(NSScreen.main?.backingScaleFactor ?? 1) // fetch device is retina or not
#elseif swift(>=3.3)
let ppp = Int(NSScreen.main()?.backingScaleFactor ?? 1) // fetch device is retina or not
#elseif swift(>=3.2)
let ppp = Int(NSScreen.main?.backingScaleFactor ?? 1) // fetch device is retina or not
#else
let ppp = Int(NSScreen.main()?.backingScaleFactor ?? 1) // fetch device is retina or not
#endif
size.width *= CGFloat(ppp)
size.height *= CGFloat(ppp)
let rep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(size.width), pixelsHigh: Int(size.height),
bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: .calibratedRGB,
bytesPerRow: 0, bitsPerPixel: 0)
#if swift(>=4.0)
let rep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(size.width), pixelsHigh: Int(size.height),
bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: .calibratedRGB,
bytesPerRow: 0, bitsPerPixel: 0)
#elseif swift(>=3.3)
let rep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(size.width), pixelsHigh: Int(size.height),
bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: NSCalibratedRGBColorSpace,
bytesPerRow: 0, bitsPerPixel: 0)
#elseif swift(>=3.2)
let rep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(size.width), pixelsHigh: Int(size.height),
bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: .calibratedRGB,
bytesPerRow: 0, bitsPerPixel: 0)
#else
let rep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(size.width), pixelsHigh: Int(size.height),
bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: NSCalibratedRGBColorSpace,
bytesPerRow: 0, bitsPerPixel: 0)
#endif
guard let context = NSGraphicsContext(bitmapImageRep: rep!) else {
return nil
}
guard let context = NSGraphicsContext(bitmapImageRep: rep!) else {
return nil
}
NSGraphicsContext.saveGraphicsState()
#if swift(>=4.0)
NSGraphicsContext.current = context
#else
NSGraphicsContext.setCurrent(context)
#endif
let transform = pdfPage.getDrawingTransform(CGPDFBox.mediaBox, rect: rect, rotate: 0, preserveAspectRatio: true)
context.cgContext.concatenate(transform)
context.cgContext.translateBy(x: 0, y: size.height)
context.cgContext.scaleBy(x: CGFloat(ppp), y: CGFloat(-ppp))
context.cgContext.drawPDFPage(pdfPage)
let resultingImage = NSImage(size: size)
resultingImage.addRepresentation(rep!)
return resultingImage
NSGraphicsContext.saveGraphicsState()
NSGraphicsContext.current = context
context.cgContext.concatenate(transform)
context.cgContext.translateBy(x: 0, y: size.height)
context.cgContext.scaleBy(x: scale, y: -scale)
context.cgContext.drawPDFPage(pdfPage)
let resultingImage = NSImage(size: size)
resultingImage.addRepresentation(rep!)
return resultingImage
#else
let ppp = Int(UIScreen.main.scale) // fetch device is retina or not
size.width *= CGFloat(ppp)
size.height *= CGFloat(ppp)
let handler : (CGContext) -> Void = { context in
context.concatenate(transform)
context.translateBy(x: 0, y: size.height)
context.scaleBy(x: CGFloat(scale), y: CGFloat(-scale))
context.setFillColor(UIColor.white.cgColor)
context.fill(rect)
context.drawPDFPage(pdfPage)
}
if #available(iOS 10.0, tvOS 10.0, *) {
return UIGraphicsImageRenderer(size: size).image { (rendererContext) in
handler(rendererContext.cgContext)
}
} else {
UIGraphicsBeginImageContext(size)
guard let context = UIGraphicsGetCurrentContext() else {
return nil
}
context.saveGState()
let transform = pdfPage.getDrawingTransform(CGPDFBox.mediaBox, rect: rect, rotate: 0, preserveAspectRatio: true)
context.concatenate(transform)
context.translateBy(x: 0, y: size.height)
context.scaleBy(x: CGFloat(ppp), y: CGFloat(-ppp))
context.setFillColor(UIColor.white.cgColor)
context.fill(rect)
context.drawPDFPage(pdfPage)
handler(context)
context.restoreGState()
let resultingImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return resultingImage
}
#endif
}
internal static func scaleDown(image: ImageClass, toSize maxSize: CGSize) -> ImageClass {
let height, width: CGFloat
if image.size.width > image.size.height {
width = maxSize.width
height = (image.size.height / image.size.width) * width
internal static func scaleDown(fileURL: URL, toSize maxSize: CGSize?) -> ImageClass? {
guard let source = CGImageSourceCreateWithURL(fileURL as CFURL, nil) else {
return nil
}
return scaleDown(source: source, toSize: maxSize)
}
internal static func scaleDown(data: Data, toSize maxSize: CGSize?) -> ImageClass? {
guard let source = CGImageSourceCreateWithData(data as CFData, nil) else {
return nil
}
return scaleDown(source: source, toSize: maxSize)
}
internal static func scaleDown(source: CGImageSource, toSize maxSize: CGSize?) -> ImageClass? {
let options: [NSString: Any]
if let maxSize = maxSize {
let pixelSize: CGFloat
let properties = CGImageSourceCopyPropertiesAtIndex(source, 0, nil)
if let width: CGFloat = ((properties as NSDictionary?)?.object(forKey: kCGImagePropertyPixelWidth) as? CGFloat),
let height: CGFloat = ((properties as NSDictionary?)?.object(forKey: kCGImagePropertyPixelHeight) as? CGFloat) {
pixelSize = (width / maxSize.width < height / maxSize.height) ? maxSize.width : maxSize.height
} else {
pixelSize = max(maxSize.width, maxSize.height)
}
options = [
kCGImageSourceThumbnailMaxPixelSize: pixelSize,
kCGImageSourceCreateThumbnailFromImageAlways: true]
} else {
height = maxSize.height
width = (image.size.width / image.size.height) * height
options = [
kCGImageSourceCreateThumbnailFromImageAlways: true]
}
let newSize = CGSize(width: width, height: height)
guard let image = CGImageSourceCreateThumbnailAtIndex(source, 0, options as CFDictionary) else {
return nil
}
#if os(macOS)
var imageRect = NSRect(origin: .zero, size: image.size)
let imageRef = image.cgImage(forProposedRect: &imageRect, context: nil, hints: nil)
// Create NSImage from the CGImage using the new size
return NSImage(cgImage: imageRef!, size: newSize)
return ImageClass(cgImage: image, size: .zero)
#else
UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0)
image.draw(in: CGRect(origin: .zero, size: newSize))
let newImage = UIGraphicsGetImageFromCurrentImageContext() ?? image
UIGraphicsEndImageContext()
return newImage
return ImageClass(cgImage: image)
#endif
}
}
@@ -1107,7 +1156,7 @@ public enum FileOperationType: CustomStringConvertible {
return mirror.children.dropFirst().first?.value as? String
}
init? (json: [String: AnyObject]) {
init? (json: [String: Any]) {
guard let type = json["type"] as? String, let source = json["source"] as? String else {
return nil
}
@@ -1136,9 +1185,9 @@ public enum FileOperationType: CustomStringConvertible {
}
internal var json: String? {
var dictionary: [String: AnyObject] = ["type": self.description as NSString]
dictionary["source"] = source as NSString?
dictionary["dest"] = destination as NSString?
var dictionary: [String: Any] = ["type": self.description]
dictionary["source"] = source
dictionary["dest"] = destination
return String(jsonDictionary: dictionary)
}
}
+145 -69
View File
@@ -16,7 +16,7 @@ import Foundation
*/
open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOperations, FileProviderReadWrite, FileProviderReadWriteProgressive {
open class var type: String { fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.") }
open let baseURL: URL?
public let baseURL: URL?
open var dispatch_queue: DispatchQueue
open var operation_queue: OperationQueue {
willSet {
@@ -72,6 +72,10 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
return _longpollSession!
}
#if os(macOS) || os(iOS) || os(tvOS)
open var undoManager: UndoManager? = nil
#endif
/**
This is parent initializer for subclasses. Using this method on `HTTPFileProvider` will fail as `type` is not implemented.
@@ -89,11 +93,7 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
self.cache = cache
self.credential = credential
#if swift(>=3.1)
let queueLabel = "FileProvider.\(Swift.type(of: self).type)"
#else
let queueLabel = "FileProvider.\(type(of: self).type)"
#endif
let queueLabel = "FileProvider.\(Swift.type(of: self).type)"
dispatch_queue = DispatchQueue(label: queueLabel, attributes: .concurrent)
operation_queue = OperationQueue()
operation_queue.name = "\(queueLabel).Operation"
@@ -198,7 +198,7 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
@discardableResult
open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> Progress? {
let path = (atPath as NSString).appendingPathComponent(folderName) + "/"
let path = atPath.appendingPathComponent(folderName) + "/"
return doOperation(.create(path: path), completionHandler: completionHandler)
}
@@ -222,7 +222,7 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
// check file is not a folder
guard (try? localFile.resourceValues(forKeys: [.fileResourceTypeKey]))?.fileResourceType ?? .unknown == .regular else {
dispatch_queue.async {
completionHandler?(self.urlError(localFile.path, code: .fileIsDirectory))
completionHandler?(URLError(.fileIsDirectory, url: localFile))
}
return nil
}
@@ -239,8 +239,8 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
open func copyItem(path: String, toLocalURL destURL: URL, completionHandler: SimpleCompletionHandler) -> Progress? {
let operation = FileOperationType.copy(source: path, destination: destURL.absoluteString)
let request = self.request(for: operation)
let cantLoadError = urlError(path, code: .cannotLoadFromNetwork)
return self.download_simple(path: path, request: request, operation: operation, completionHandler: { [weak self] (tempURL, error) in
let cantLoadError = URLError(.cannotLoadFromNetwork, url: self.url(of: path))
return self.download_file(path: path, request: request, operation: operation, completionHandler: { [weak self] (tempURL, error) in
do {
if let error = error {
throw error
@@ -250,11 +250,11 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
throw cantLoadError
}
#if os(macOS) || os(iOS) || os(tvOS)
var coordError: NSError?
NSFileCoordinator().coordinate(writingItemAt: tempURL, options: .forMoving, writingItemAt: destURL, options: .forReplacing, error: &coordError, byAccessor: { (tempURL, destURL) in
do {
try FileManager.default.moveItem(at: tempURL, to: destURL)
completionHandler?(nil)
self?.delegateNotify(operation)
} catch {
@@ -266,6 +266,17 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
if let error = coordError {
throw error
}
#else
do {
try FileManager.default.moveItem(at: tempURL, to: destURL)
completionHandler?(nil)
self?.delegateNotify(operation)
} catch {
completionHandler?(error)
self?.delegateNotify(operation, error: error)
}
#endif
} catch {
completionHandler?(error)
self?.delegateNotify(operation, error: error)
@@ -309,24 +320,25 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
let operation = FileOperationType.fetch(path: path)
var request = self.request(for: operation)
let cantLoadError = urlError(path, code: .cannotLoadFromNetwork)
let cantLoadError = URLError(.cannotLoadFromNetwork, url: self.url(of: path))
request.setValue(rangeWithOffset: offset, length: length)
return self.download_simple(path: path, request: request, operation: operation, completionHandler: { (tempURL, error) in
let stream = OutputStream.toMemory()
return self.download(path: path, request: request, operation: operation, stream: stream) { (error) in
do {
if let error = error {
throw error
}
guard let tempURL = tempURL else {
guard let data = stream.property(forKey: .dataWrittenToMemoryStreamKey) as? Data else {
throw cantLoadError
}
let data = try Data(contentsOf: tempURL)
completionHandler(data, nil)
} catch {
completionHandler(nil, error)
}
})
}
}
@discardableResult
@@ -335,8 +347,10 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: operation) ?? true == true else {
return nil
}
let data = data ?? Data()
let request = self.request(for: operation, overwrite: overwrite, attributes: [.contentModificationDateKey: Date()])
return upload_data(path, request: request, data: data ?? Data(), operation: operation, completionHandler: completionHandler)
let stream = InputStream(data: data)
return upload(path, request: request, stream: stream, size: Int64(data.count), operation: operation, completionHandler: completionHandler)
}
internal func request(for operation: FileOperationType, overwrite: Bool = false, attributes: [URLResourceKey: Any] = [:]) -> URLRequest {
@@ -347,8 +361,9 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.")
}
internal func multiStatusHandler(source: String, data: Data, completionHandler: SimpleCompletionHandler) -> Void {
internal func multiStatusError(operation: FileOperationType, data: Data) -> FileProviderHTTPError? {
// WebDAV will override this function
return nil
}
/**
@@ -374,25 +389,32 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
let request = self.request(for: operation, overwrite: overwrite)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var serverError: FileProviderHTTPError?
if let response = response as? HTTPURLResponse {
if response.statusCode >= 300, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) {
serverError = self.serverError(with: code, path: operation.source, data: data)
do {
if let error = error {
throw error
}
if FileProviderHTTPErrorCode(rawValue: response.statusCode) == .multiStatus, let data = data {
self.multiStatusHandler(source: operation.source, data: data, completionHandler: completionHandler)
if let response = response as? HTTPURLResponse {
if response.statusCode >= 300, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) {
throw self.serverError(with: code, path: operation.source, data: data)
}
if FileProviderHTTPErrorCode(rawValue: response.statusCode) == .multiStatus, let data = data,
let ms_error = self.multiStatusError(operation: operation, data: data) {
throw ms_error
}
}
}
if serverError == nil && error == nil {
#if os(macOS) || os(iOS) || os(tvOS)
self._registerUndo(operation)
#endif
progress.completedUnitCount = 1
} else {
progress.cancel()
completionHandler?(nil)
self.delegateNotify(operation)
} catch {
completionHandler?(error)
self.delegateNotify(operation, error: error)
}
completionHandler?(serverError ?? error)
self.delegateNotify(operation, error: serverError ?? error)
})
task.taskDescription = operation.json
progress.cancellationHandler = { [weak task] in
@@ -445,28 +467,28 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
}
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
if let error = error {
do {
if let error = error {
throw error
}
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
throw self.serverError(with: rCode, path: path, data: data)
}
let (newFiles, err, newToken) = pageHandler(data, progress)
if let error = err {
throw error
}
let files = previousResult + newFiles
if let newToken = newToken, !progress.isCancelled {
_ = self.paginated(path, startToken: newToken, currentProgress: progress, previousResult: files, requestHandler: requestHandler, pageHandler: pageHandler, completionHandler: completionHandler)
} else {
completionHandler(files, nil)
}
} catch {
completionHandler(previousResult, error)
return
}
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
let responseError = self.serverError(with: rCode, path: path, data: data)
completionHandler(previousResult, responseError)
return
}
let (newFiles, err, newToken) = pageHandler(data, progress)
if let error = err {
completionHandler(previousResult, error)
return
}
let files = previousResult + newFiles
if let newToken = newToken, !progress.isCancelled {
_ = self.paginated(path, startToken: newToken, currentProgress: progress, previousResult: files, requestHandler: requestHandler, pageHandler: pageHandler, completionHandler: completionHandler)
} else {
completionHandler(files, nil)
}
})
progress.cancellationHandler = { [weak task] in
task?.cancel()
@@ -480,7 +502,6 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
func upload_task(_ targetPath: String, progress: Progress, task: URLSessionTask, operation: FileOperationType,
completionHandler: SimpleCompletionHandler) -> Void {
var progress = progress
var allData = Data()
dataCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { data in
@@ -499,7 +520,7 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
self?.delegateNotify(operation, error: responseError ?? error)
}
task.taskDescription = operation.json
task.addObserver(self.sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesSent), options: .new, context: &progress)
sessionDelegate?.observerProgress(of: task, using: progress, kind: .upload)
progress.cancellationHandler = { [weak task] in
task?.cancel()
}
@@ -507,9 +528,8 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
task.resume()
}
func upload_data(_ targetPath: String, request: URLRequest, data: Data, operation: FileOperationType,
func upload(_ targetPath: String, request: URLRequest, stream: InputStream, size: Int64, operation: FileOperationType,
completionHandler: SimpleCompletionHandler) -> Progress? {
let size: Int64 = Int64(data.count)
if size > maxUploadSimpleSupported {
let error = self.serverError(with: .payloadTooLarge, path: targetPath, data: nil)
completionHandler?(error)
@@ -522,7 +542,9 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
progress.kind = .file
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
let task = session.uploadTask(with: request, from: data)
var request = request
request.httpBodyStream = stream
let task = session.uploadTask(withStreamedRequest: request)
self.upload_task(targetPath, progress: progress, task: task, operation: operation, completionHandler: completionHandler)
return progress
@@ -530,7 +552,7 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
func upload_file(_ targetPath: String, request: URLRequest, localFile: URL, operation: FileOperationType,
completionHandler: SimpleCompletionHandler) -> Progress? {
let fSize = (try? localFile.resourceValues(forKeys: [.fileSizeKey]))?.fileSize
let fSize = (try? localFile.resourceValues(forKeys: [.fileSizeKey]))?.allValues[.fileSizeKey] as? Int64
let size = Int64(fSize ?? -1)
if size > maxUploadSimpleSupported {
let error = self.serverError(with: .payloadTooLarge, path: targetPath, data: nil)
@@ -544,6 +566,7 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
progress.kind = .file
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
#if os(macOS) || os(iOS) || os(tvOS)
var error: NSError?
NSFileCoordinator().coordinate(readingItemAt: localFile, options: .forUploading, error: &error, byAccessor: { (url) in
let task = self.session.uploadTask(with: request, fromFile: localFile)
@@ -552,15 +575,66 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
if let error = error {
completionHandler?(error)
}
#else
self.upload_task(targetPath, progress: progress, task: task, operation: operation, completionHandler: completionHandler)
#endif
return progress
}
internal func download(path: String, request: URLRequest, operation: FileOperationType,
responseHandler: ((_ response: URLResponse) -> Void)? = nil,
stream: OutputStream,
completionHandler: @escaping (_ error: Error?) -> Void) -> Progress? {
let progress = Progress(totalUnitCount: -1)
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
progress.kind = .file
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
let task = session.dataTask(with: request)
if let responseHandler = responseHandler {
responseCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { response in
responseHandler(response)
}
}
stream.open()
dataCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { [weak task, weak self] data in
guard !data.isEmpty else { return }
task.flatMap { self?.delegateNotify(operation, progress: Double($0.countOfBytesReceived) / Double($0.countOfBytesExpectedToReceive)) }
let result = (try? stream.write(data: data)) ?? -1
if result < 0 {
completionHandler(stream.streamError!)
self?.delegateNotify(operation, error: stream.streamError!)
task?.cancel()
}
}
completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { error in
if error != nil {
progress.cancel()
}
stream.close()
completionHandler(error)
self.delegateNotify(operation, error: error)
}
task.taskDescription = operation.json
sessionDelegate?.observerProgress(of: task, using: progress, kind: .download)
progress.cancellationHandler = { [weak task] in
task?.cancel()
}
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
task.resume()
return progress
}
internal func download_progressive(path: String, request: URLRequest, operation: FileOperationType,
responseHandler: ((_ response: URLResponse) -> Void)? = nil,
progressHandler: @escaping (_ data: Data) -> Void,
completionHandler: @escaping (_ error: Error?) -> Void) -> Progress? {
var progress = Progress(totalUnitCount: -1)
let progress = Progress(totalUnitCount: -1)
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
progress.kind = .file
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
@@ -586,8 +660,7 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
}
task.taskDescription = operation.json
task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesReceived), options: .new, context: &progress)
task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToReceive), options: .new, context: &progress)
sessionDelegate?.observerProgress(of: task, using: progress, kind: .download)
progress.cancellationHandler = { [weak task] in
task?.cancel()
}
@@ -596,20 +669,20 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
return progress
}
internal func download_simple(path: String, request: URLRequest, operation: FileOperationType,
internal func download_file(path: String, request: URLRequest, operation: FileOperationType,
completionHandler: @escaping ((_ tempURL: URL?, _ error: Error?) -> Void)) -> Progress? {
var progress = Progress(totalUnitCount: -1)
let progress = Progress(totalUnitCount: -1)
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
progress.kind = .file
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
let task = session.downloadTask(with: request)
completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { error in
if error != nil {
if let error = error {
progress.cancel()
completionHandler(nil, error)
self.delegateNotify(operation, error: error)
}
completionHandler(nil, error)
self.delegateNotify(operation, error: error)
}
downloadCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { tempURL in
guard let httpResponse = task.response as? HTTPURLResponse , httpResponse.statusCode < 300 else {
@@ -627,8 +700,7 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
completionHandler(tempURL, nil)
}
task.taskDescription = operation.json
task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesReceived), options: .new, context: &progress)
task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToReceive), options: .new, context: &progress)
sessionDelegate?.observerProgress(of: task, using: progress, kind: .download)
progress.cancellationHandler = { [weak task] in
task?.cancel()
}
@@ -639,3 +711,7 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper
}
extension HTTPFileProvider: FileProvider { }
#if os(macOS) || os(iOS) || os(tvOS)
extension HTTPFileProvider: FileProvideUndoable { }
#endif
+32 -54
View File
@@ -14,7 +14,7 @@ import Foundation
it uses `FileManager` foundation class with some additions like searching and reading a portion of file.
*/
open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor, FileProviderSymbolicLink {
open class var type: String { return "Local" }
open fileprivate(set) var baseURL: URL?
open var dispatch_queue: DispatchQueue
@@ -105,11 +105,7 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
self.credential = nil
self.isCoorinating = false
#if swift(>=3.1)
let queueLabel = "FileProvider.\(Swift.type(of: self).type)"
#else
let queueLabel = "FileProvider.\(type(of: self).type)"
#endif
dispatch_queue = DispatchQueue(label: queueLabel, attributes: .concurrent)
operation_queue = OperationQueue()
operation_queue.name = "\(queueLabel).Operation"
@@ -122,6 +118,10 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
public required convenience init?(coder aDecoder: NSCoder) {
guard let baseURL = aDecoder.decodeObject(of: NSURL.self, forKey: "baseURL") as URL? else {
if #available(macOS 10.11, iOS 9.0, tvOS 9.0, *) {
aDecoder.failWithError(CocoaError(.coderValueNotFound,
userInfo: [NSLocalizedDescriptionKey: "Base URL is not set."]))
}
return nil
}
self.init(baseURL: baseURL)
@@ -246,7 +246,7 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
@discardableResult
open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> Progress? {
let operation = FileOperationType.create(path: (atPath as NSString).appendingPathComponent(folderName) + "/")
let operation = FileOperationType.create(path: atPath.appendingPathComponent(folderName) + "/")
return self.doOperation(operation, completionHandler: completionHandler)
}
@@ -280,15 +280,6 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
return self.doOperation(operation, completionHandler: completionHandler)
}
#if os(macOS) || os(iOS) || os(tvOS)
@objc dynamic func doSimpleOperation(_ box: UndoBox) {
guard let _ = self.undoManager else { return }
_ = self.doOperation(box.undoOperation) { (_) in
return
}
}
#endif
@discardableResult
fileprivate func doOperation(_ operation: FileOperationType, data: Data? = nil, overwrite: Bool = true, atomically: Bool = false, forUploading: Bool = false, completionHandler: SimpleCompletionHandler) -> Progress? {
let progress = Progress(totalUnitCount: -1)
@@ -316,7 +307,7 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
if !overwrite, let dest = dest, /* fileExists */ ((try? dest.checkResourceIsReachable()) ?? false) ||
((try? dest.checkPromisedItemIsReachable()) ?? false) {
let e = self.cocoaError(destPath!, code: .fileWriteFileExists)
let e = CocoaError(.fileWriteFileExists, path: destPath!)
dispatch_queue.async {
completionHandler?(e)
}
@@ -325,14 +316,6 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
}
#if os(macOS) || os(iOS) || os(tvOS)
if let undoManager = self.undoManager, let undoOp = self.undoOperation(for: operation) {
let undoBox = UndoBox(provider: self, operation: operation, undoOperation: undoOp)
undoManager.beginUndoGrouping()
undoManager.registerUndo(withTarget: self, selector: #selector(LocalFileProvider.doSimpleOperation(_:)), object: undoBox)
undoManager.setActionName(operation.actionDescription)
undoManager.endUndoGrouping()
}
var successfulSecurityScopedResourceAccess = false
#endif
@@ -372,7 +355,10 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
source.stopAccessingSecurityScopedResource()
}
#endif
#if os(macOS) || os(iOS) || os(tvOS)
self._registerUndo(operation)
#endif
progress.completedUnitCount = progress.totalUnitCount
self.dispatch_queue.async {
completionHandler?(nil)
@@ -510,7 +496,7 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
let operationHandler: (URL) -> Void = { url in
do {
guard let handle = FileHandle(forReadingAtPath: url.path) else {
throw self.cocoaError(path, code: .fileNoSuchFile)
throw CocoaError(.fileNoSuchFile, path: path)
}
defer {
@@ -521,13 +507,13 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
progress.totalUnitCount = size
guard size > offset else {
progress.cancel()
throw self.cocoaError(path, code: .fileReadTooLarge)
throw CocoaError(.fileReadTooLarge, path: path)
}
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
handle.seek(toFileOffset: UInt64(offset))
guard Int64(handle.offsetInFile) == offset else {
progress.cancel()
throw self.cocoaError(path, code: .fileReadTooLarge)
throw CocoaError(.fileReadTooLarge, path: path)
}
let data = handle.readData(ofLength: length)
@@ -570,7 +556,7 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
let fileExists = ((try? self.url(of: path).checkResourceIsReachable()) ?? false) ||
((try? self.url(of: path).checkPromisedItemIsReachable()) ?? false)
if !overwrite && fileExists {
let e = self.cocoaError(path, code: .fileWriteFileExists)
let e = CocoaError(.fileWriteFileExists, path: path)
dispatch_queue.async {
completionHandler?(e)
}
@@ -587,11 +573,10 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
open func registerNotifcation(path: String, eventHandler: @escaping (() -> Void)) {
self.unregisterNotifcation(path: path)
let url = self.url(of: path)
let monitor = LocalFileMonitor(url: url) {
eventHandler()
if let monitor = LocalFileMonitor(url: url, handler: eventHandler) {
monitor.start()
monitors.append(monitor)
}
monitor.start()
monitors.append(monitor)
}
open func unregisterNotifcation(path: String) {
@@ -609,22 +594,20 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
return monitors.map( { self.relativePathOf(url: $0.url) } ).contains(path.trimmingCharacters(in: CharacterSet(charactersIn: "/")))
}
/**
Creates a symbolic link at the specified path that points to an item at the given path.
This method does not traverse symbolic links contained in destination path, making it possible
to create symbolic links to locations that do not yet exist.
Also, if the final path component is a symbolic link, that link is not followed.
- Parameters:
- symbolicLink: The file path at which to create the new symbolic link. The last component of the path issued as the name of the link.
- withDestinationPath: The path that contains the item to be pointed to by the link. In other words, this is the destination of the link.
- completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
*/
open func create(symbolicLink path: String, withDestinationPath destPath: String, completionHandler: SimpleCompletionHandler) {
operation_queue.addOperation {
let operation = FileOperationType.link(link: path, target: destPath)
do {
try self.opFileManager.createSymbolicLink(at: self.url(of: path), withDestinationURL: self.url(of: destPath))
let url = self.url(of: path)
let destURL = self.url(of: destPath)
let homePath = NSHomeDirectory()
if destURL.path.hasPrefix(homePath) {
let canonicalHomePath = "/" + homePath.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
let destRelativePath = destURL.path.replacingOccurrences(of: canonicalHomePath, with: "~", options: .anchored)
try self.opFileManager.createSymbolicLink(atPath: url.path, withDestinationPath: destRelativePath)
} else {
try self.opFileManager.createSymbolicLink(at: url, withDestinationURL: destURL)
}
completionHandler?(nil)
self.delegateNotify(operation)
} catch {
@@ -634,17 +617,13 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
}
}
/// Returns the path of the item pointed to by a symbolic link.
///
/// - Parameters:
/// - path: The path of a file or directory.
/// - completionHandler: Returns destination url of given symbolic link, or an `Error` object if it fails.
open func destination(ofSymbolicLink path: String, completionHandler: @escaping (_ url: URL?, _ error: Error?) -> Void) {
open func destination(ofSymbolicLink path: String, completionHandler: @escaping (_ file: FileObject?, _ error: Error?) -> Void) {
dispatch_queue.async {
do {
let destPath = try self.opFileManager.destinationOfSymbolicLink(atPath: self.url(of: path).path)
let destUrl = URL(fileURLWithPath: destPath)
completionHandler(destUrl, nil)
let absoluteDestPath = (destPath as NSString).expandingTildeInPath
let file = LocalFileObject(fileWithPath: absoluteDestPath, relativeTo: self.baseURL)
completionHandler(file, nil)
} catch {
completionHandler(nil, error)
}
@@ -653,7 +632,6 @@ open class LocalFileProvider: NSObject, FileProvider, FileProviderMonitor {
}
#if os(macOS) || os(iOS) || os(tvOS)
extension LocalFileProvider: FileProvideUndoable { }
internal extension LocalFileProvider {
+41 -25
View File
@@ -17,12 +17,19 @@ public final class LocalFileObject: FileObject {
/// Initiates a `LocalFileObject` with attributes of file in path.
public convenience init? (fileWithPath path: String, relativeTo relativeURL: URL?) {
var fileURL: URL?
var rpath = path.replacingOccurrences(of: relativeURL?.path ?? "", with: "", options: .anchored).replacingOccurrences(of: "/", with: "", options: .anchored)
var rpath = path.replacingOccurrences(of: "/", with: "", options: .anchored)
var resolvedRelativeURL: URL?
if let relPath = relativeURL?.path.replacingOccurrences(of: "/", with: "", options: .anchored), rpath.hasPrefix(relPath) {
rpath = rpath.replacingOccurrences(of: relPath, with: "", options: .anchored).replacingOccurrences(of: "/", with: "", options: .anchored)
resolvedRelativeURL = relativeURL
} else {
resolvedRelativeURL = relativeURL
}
if #available(iOS 9.0, macOS 10.11, *) {
fileURL = URL(fileURLWithPath: rpath, relativeTo: relativeURL)
fileURL = URL(fileURLWithPath: rpath, relativeTo: resolvedRelativeURL)
} else {
rpath = rpath.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? rpath
fileURL = URL(string: rpath, relativeTo: relativeURL) ?? relativeURL
fileURL = URL(string: rpath, relativeTo: resolvedRelativeURL) ?? resolvedRelativeURL
}
if let fileURL = fileURL {
@@ -48,7 +55,7 @@ public final class LocalFileObject: FileObject {
}
/// The total size allocated on disk for the file
open internal(set) var allocatedSize: Int64 {
public internal(set) var allocatedSize: Int64 {
get {
return allValues[.fileAllocatedSizeKey] as? Int64 ?? 0
}
@@ -60,7 +67,7 @@ public final class LocalFileObject: FileObject {
/// The document identifier is a value assigned by the kernel/system to a file or directory.
/// This value is used to identify the document regardless of where it is moved on a volume.
/// The identifier persists across system restarts.
open internal(set) var id: Int? {
public internal(set) var id: Int? {
get {
return allValues[.documentIdentifierKey] as? Int
}
@@ -71,7 +78,7 @@ public final class LocalFileObject: FileObject {
/// The revision of file, which changes when a file contents are modified.
/// Changes to attributes or other file metadata do not change the identifier.
open var rev: String? {
public var rev: String? {
get {
let data = allValues[.generationIdentifierKey] as? Data
return data?.map { String(format: "%02hhx", $0) }.joined()
@@ -79,7 +86,7 @@ public final class LocalFileObject: FileObject {
}
/// Count of children items of a driectory. It costs disk access for local directories.
open public(set) override var childrensCount: Int? {
public override var childrensCount: Int? {
get {
return try? FileManager.default.contentsOfDirectory(atPath: self.url.path).count
}
@@ -94,13 +101,36 @@ public final class LocalFileMonitor {
fileprivate let descriptor: CInt
fileprivate let qq: DispatchQueue = DispatchQueue.global(qos: .default)
fileprivate var state: Bool = false
fileprivate var monitoredTime: TimeInterval = Date().timeIntervalSinceReferenceDate
fileprivate var _monitoredTime: TimeInterval = Date().timeIntervalSinceReferenceDate
fileprivate let _monitoredTimeLock = NSLock()
fileprivate var monitoredTime: TimeInterval {
get {
_monitoredTimeLock.lock()
defer {
_monitoredTimeLock.unlock()
}
return _monitoredTime
}
set {
_monitoredTimeLock.lock()
defer {
_monitoredTimeLock.unlock()
}
_monitoredTime = newValue
}
}
public var url: URL
/// Creates a folder monitor object with monitoring enabled.
public init(url: URL, handler: @escaping ()->Void) {
public init?(url: URL, handler: @escaping ()->Void) {
self.url = url
descriptor = open((url.absoluteURL as NSURL).fileSystemRepresentation, O_EVTONLY)
descriptor = url.absoluteURL.withUnsafeFileSystemRepresentation { rep in
guard let rep = rep else { return -1 }
return open(rep, O_EVTONLY)
}
guard descriptor >= 0 else { return nil }
let event: DispatchSource.FileSystemEvent = url.fileIsDirectory ? [.write] : .all
source = DispatchSource.makeFileSystemObjectSource(fileDescriptor: descriptor, eventMask: event, queue: qq)
@@ -108,7 +138,7 @@ public final class LocalFileMonitor {
// We have a 0.2 second delay to ensure we wont call handler 1000s times when there is
// a huge file operation. This ensures app will work smoothly while this 250 milisec won't
// affect user experince much
let main_handler: ()->Void = { [weak self] in
let main_handler: DispatchSourceProtocol.DispatchSourceHandler = { [weak self] in
guard let `self` = self else { return }
if Date().timeIntervalSinceReferenceDate < self.monitoredTime + 0.2 {
return
@@ -225,17 +255,3 @@ internal class LocalFileProviderManagerDelegate: NSObject, FileManagerDelegate {
return delegate.fileProvider(provider, shouldProceedAfterError: error, operation: .link(link: srcPath, target: dstPath))
}
}
#if os(macOS) || os(iOS) || os(tvOS)
class UndoBox: NSObject {
weak var provider: FileProvideUndoable?
let operation: FileOperationType
let undoOperation: FileOperationType
init(provider: FileProvideUndoable, operation: FileOperationType, undoOperation: FileOperationType) {
self.provider = provider
self.operation = operation
self.undoOperation = undoOperation
}
}
#endif
+63 -30
View File
@@ -101,7 +101,7 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
public static var graphVersion = "v1.0"
/// Route for container, default is `.me`.
open let route: Route
public let route: Route
/**
Initializer for Onedrive provider with given client ID and Token.
@@ -149,7 +149,7 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
if let driveId = aDecoder.decodeObject(of: NSString.self, forKey: "drive") as String?, let uuid = UUID(uuidString: driveId) {
route = .drive(uuid: uuid)
} else {
route = (aDecoder.decodeObject(forKey: "route") as? String).flatMap({ Route(rawValue: $0) }) ?? .me
route = (aDecoder.decodeObject(of: NSString.self, forKey: "route") as String?).flatMap({ Route(rawValue: $0) }) ?? .me
}
self.init(credential: aDecoder.decodeObject(of: URLCredential.self, forKey: "credential"),
serverURL: aDecoder.decodeObject(of: NSURL.self, forKey: "baseURL") as URL?,
@@ -194,14 +194,14 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
}, pageHandler: { [weak self] (data, _) -> (files: [FileObject], error: Error?, newToken: String?) in
guard let `self` = self else { return ([], nil, nil) }
guard let json = data?.deserializeJSON(), let entries = json["value"] as? [AnyObject] else {
let err = self.urlError(path, code: .badServerResponse)
guard let json = data?.deserializeJSON(), let entries = json["value"] as? [Any] else {
let err = URLError(.badServerResponse, url: self.url(of: path))
return ([], err, nil)
}
var files = [FileObject]()
for entry in entries {
if let entry = entry as? [String: AnyObject], let file = OneDriveFileObject(baseURL: self.baseURL, route: self.route, json: entry) {
if let entry = entry as? [String: Any], let file = OneDriveFileObject(baseURL: self.baseURL, route: self.route, json: entry) {
files.append(file)
}
}
@@ -257,8 +257,9 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
volume.uuid = json["id"] as? String
volume.name = json["name"] as? String
volume.creationDate = (json["createdDateTime"] as? String).flatMap { Date(rfcString: $0) }
volume.totalCapacity = (json["quota"]?["total"] as? NSNumber)?.int64Value ?? -1
volume.availableCapacity = (json["quota"]?["remaining"] as? NSNumber)?.int64Value ?? 0
let quota = json["quota"] as? [String: Any]
volume.totalCapacity = (quota?["total"] as? NSNumber)?.int64Value ?? -1
volume.availableCapacity = (quota?["remaining"] as? NSNumber)?.int64Value ?? 0
completionHandler(volume)
})
task.resume()
@@ -314,14 +315,14 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
return request
}, pageHandler: { [weak self] (data, progress) -> (files: [FileObject], error: Error?, newToken: String?) in
guard let `self` = self else { return ([], nil, nil) }
guard let json = data?.deserializeJSON(), let entries = json["value"] as? [AnyObject] else {
let err = self.urlError(path, code: .badServerResponse)
guard let json = data?.deserializeJSON(), let entries = json["value"] as? [Any] else {
let err = URLError(.badServerResponse, url: self.url(of: path))
return ([], err, nil)
}
var foundFiles = [FileObject]()
for entry in entries {
if let entry = entry as? [String: AnyObject], let file = OneDriveFileObject(baseURL: self.baseURL, route: self.route, json: entry), query.evaluate(with: file.mapPredicate()) {
if let entry = entry as? [String: Any], let file = OneDriveFileObject(baseURL: self.baseURL, route: self.route, json: entry), query.evaluate(with: file.mapPredicate()) {
foundFiles.append(file)
foundItemHandler?(file)
}
@@ -378,6 +379,33 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
task.resume()
}
/*internal var cachedDriveID: String?
override func doOperation(_ operation: FileOperationType, overwrite: Bool, progress: Progress?, completionHandler: SimpleCompletionHandler) -> Progress? {
switch operation {
case .copy(source: let source, destination: let dest) where !source.hasPrefix("file://") && !dest.hasPrefix("file://"):
fallthrough
case .move:
if self.cachedDriveID != nil {
return super.doOperation(operation, overwrite: overwrite, progress: progress, completionHandler: completionHandler)
} else {
let progress = Progress(totalUnitCount: 1)
self.storageProperties(completionHandler: { (volume) in
if let volumeId = volume?.uuid {
self.cachedDriveID = volumeId
_ = super.doOperation(operation, overwrite: overwrite, progress: progress, completionHandler: completionHandler)
} else {
let error = self.urlError(operation.source, code: .badServerResponse)
completionHandler?(error)
}
})
return progress
}
default:
return super.doOperation(operation, overwrite: overwrite, progress: progress, completionHandler: completionHandler)
}
}*/
/**
Uploads a file from local file url to designated path asynchronously.
Method will fail if source is not a local url with `file://` scheme.
@@ -396,7 +424,7 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
// check file is not a folder
guard (try? localFile.resourceValues(forKeys: [.fileResourceTypeKey]))?.fileResourceType ?? .unknown == .regular else {
dispatch_queue.async {
completionHandler?(self.urlError(localFile.path, code: .fileIsDirectory))
completionHandler?(URLError(.fileIsDirectory, url: localFile))
}
return nil
}
@@ -449,7 +477,7 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
url = self.url(of: path, modifier: "content")
case .create(path: let path) where path.hasSuffix("/"):
method = "POST"
let parent = (path as NSString).deletingLastPathComponent
let parent = path.deletingLastPathComponent
url = self.url(of: parent, modifier: "children")
case .modify(path: let path):
method = "PUT"
@@ -486,32 +514,32 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
switch operation {
case .create(path: let path) where path.hasSuffix("/"):
request.setValue(contentType: .json)
var requestDictionary = [String: AnyObject]()
let name = (path as NSString).lastPathComponent
requestDictionary["name"] = name as NSString
requestDictionary["folder"] = NSDictionary()
requestDictionary["@microsoft.graph.conflictBehavior"] = "fail" as NSString
var requestDictionary = [String: Any]()
let name = path.lastPathComponent
requestDictionary["name"] = name
requestDictionary["folder"] = [String: Any]()
requestDictionary["@microsoft.graph.conflictBehavior"] = "fail"
request.httpBody = Data(jsonDictionary: requestDictionary)
case .copy(let source, let dest) where !source.hasPrefix("file://") && !dest.hasPrefix("file://"),
.move(source: let source, destination: let dest):
request.setValue(contentType: .json, charset: .utf8)
let cdest = correctPath(dest) as NSString
var parentReference: [String: AnyObject] = [:]
let cdest = correctPath(dest)
var parentReference: [String: Any] = [:]
if cdest.hasPrefix("id:") {
parentReference["id"] = cdest.components(separatedBy: "/").first?.replacingOccurrences(of: "id:", with: "", options: .anchored) as NSString?
parentReference["id"] = cdest.components(separatedBy: "/").first?.replacingOccurrences(of: "id:", with: "", options: .anchored)
} else {
parentReference["path"] = ("/drive/root:" as NSString).appendingPathComponent(cdest.deletingLastPathComponent) as NSString
parentReference["path"] = "/drive/root:".appendingPathComponent(cdest.deletingLastPathComponent)
}
switch self.route {
case .drive(uuid: let uuid):
parentReference["driveId"] = uuid.uuidString as NSString
parentReference["driveId"] = uuid.uuidString
default:
//parentReference["driveId"] = cachedDriveID as NSString? ?? ""
//parentReference["driveId"] = cachedDriveID ?? ""
break
}
var requestDictionary = [String: AnyObject]()
requestDictionary["parentReference"] = parentReference as NSDictionary
requestDictionary["name"] = (cdest as NSString).lastPathComponent as NSString
var requestDictionary = [String: Any]()
requestDictionary["parentReference"] = parentReference
requestDictionary["name"] = cdest.lastPathComponent
request.httpBody = Data(jsonDictionary: requestDictionary)
default:
break
@@ -523,7 +551,7 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
override func serverError(with code: FileProviderHTTPErrorCode, path: String?, data: Data?) -> FileProviderHTTPError {
let errorDesc: String?
if let response = data?.deserializeJSON() {
errorDesc = response["error"]?["message"] as? String
errorDesc = (response["error"] as? [String: Any])?["message"] as? String
} else {
errorDesc = data.flatMap({ String(data: $0, encoding: .utf8) })
}
@@ -550,7 +578,7 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
open func publicLink(to path: String, completionHandler: @escaping ((_ link: URL?, _ attribute: FileObject?, _ expiration: Date?, _ error: Error?) -> Void)) {
var request = URLRequest(url: self.url(of: path, modifier: "createLink"))
request.httpMethod = "POST"
let requestDictionary: [String: AnyObject] = ["type": "view" as NSString]
let requestDictionary: [String: Any] = ["type": "view"]
request.httpBody = Data(jsonDictionary: requestDictionary)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var serverError: FileProviderHTTPError?
@@ -560,7 +588,7 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
serverError = code.flatMap { self.serverError(with: $0, path: path, data: data) }
}
if let json = data?.deserializeJSON() {
if let linkDic = json["link"] as? NSDictionary, let linkStr = linkDic["webUrl"] as? String {
if let linkDic = json["link"] as? [String: Any], let linkStr = linkDic["webUrl"] as? String {
link = URL(string: linkStr)
}
}
@@ -600,7 +628,7 @@ extension OneDriveFileProvider: ExtendedFileProvider {
#if os(macOS) || os(iOS) || os(tvOS)
open func thumbnailOfFileSupported(path: String) -> Bool {
let fileExt = (path as NSString).pathExtension.lowercased()
let fileExt = path.pathExtension.lowercased()
switch fileExt {
case "jpg", "jpeg", "bmp", "gif", "png", "tif", "tiff":
return true
@@ -623,6 +651,11 @@ extension OneDriveFileProvider: ExtendedFileProvider {
case 97...176: thumbQuery = "medium"
default: thumbQuery = "large"
}
/*if let dimension = dimension {
thumbQuery = "c\(Int(dimension.width))x\(Int(dimension.height))"
} else {
thumbQuery = "small"
}*/
let url = self.url(of: path, modifier: "thumbnails")
.appendingPathComponent("0").appendingPathComponent(thumbQuery)
.appendingPathComponent("content")
+24 -29
View File
@@ -22,22 +22,18 @@ public final class OneDriveFileObject: FileObject {
self.init(baseURL: baseURL, route: route, json: json)
}
internal init? (baseURL: URL?, route: OneDriveFileProvider.Route, json: [String: AnyObject]) {
internal init? (baseURL: URL?, route: OneDriveFileProvider.Route, json: [String: Any]) {
guard let name = json["name"] as? String else { return nil }
guard let id = json["id"] as? String else { return nil }
let path: String
if let refpath = json["parentReference"]?["path"] as? String {
if let refpath = (json["parentReference"] as? [String: Any])?["path"] as? String {
let parentPath: String
if let colonIndex = refpath.index(of: ":") {
#if swift(>=4.0)
if let colonIndex = refpath.firstIndex(of: ":") {
parentPath = String(refpath[refpath.index(after: colonIndex)...])
#else
parentPath = refpath.substring(from: refpath.index(after: colonIndex))
#endif
} else {
parentPath = refpath
}
path = (parentPath as NSString).appendingPathComponent(name)
path = parentPath.appendingPathComponent(name)
} else {
path = "id:\(id)"
}
@@ -45,20 +41,20 @@ public final class OneDriveFileObject: FileObject {
super.init(url: url, name: name, path: path)
self.id = id
self.size = (json["size"] as? NSNumber)?.int64Value ?? -1
self.childrensCount = json["folder"]?["childCount"] as? Int
self.childrensCount = (json["folder"] as? [String: Any])?["childCount"] as? Int
self.modifiedDate = (json["lastModifiedDateTime"] as? String).flatMap { Date(rfcString: $0) }
self.creationDate = (json["createdDateTime"] as? String).flatMap { Date(rfcString: $0) }
self.type = json["folder"] != nil ? .directory : .regular
self.contentType = (json["file"]?["mimeType"] as? String).flatMap(ContentMIMEType.init(rawValue:)) ?? .stream
self.contentType = ((json["file"] as? [String: Any])?["mimeType"] as? String).flatMap(ContentMIMEType.init(rawValue:)) ?? .stream
self.entryTag = json["eTag"] as? String
let hashes = json["file"]?["hashes"] as? NSDictionary
let hashes = (json["file"] as? [String: Any])?["hashes"] as? [String: Any]
// checks for both sha1 or quickXor. First is available in personal drives, second in business one.
self.fileHash = (hashes?["sha1Hash"] as? String) ?? (hashes?["quickXorHash"] as? String)
}
/// The document identifier is a value assigned by the OneDrive to a file.
/// This value is used to identify the document regardless of where it is moved on a volume.
open internal(set) var id: String? {
public internal(set) var id: String? {
get {
return allValues[.fileResourceIdentifierKey] as? String
}
@@ -68,7 +64,7 @@ public final class OneDriveFileObject: FileObject {
}
/// MIME type of file contents returned by OneDrive server.
open internal(set) var contentType: ContentMIMEType {
public internal(set) var contentType: ContentMIMEType {
get {
return (allValues[.mimeTypeKey] as? String).flatMap(ContentMIMEType.init(rawValue:)) ?? .stream
}
@@ -78,7 +74,7 @@ public final class OneDriveFileObject: FileObject {
}
/// HTTP E-Tag, can be used to mark changed files.
open internal(set) var entryTag: String? {
public internal(set) var entryTag: String? {
get {
return allValues[.entryTagKey] as? String
}
@@ -88,7 +84,7 @@ public final class OneDriveFileObject: FileObject {
}
/// Calculated hash from OneDrive server. Hex string SHA1 in personal or Base65 string [QuickXOR](https://dev.onedrive.com/snippets/quickxorhash.htm) in business drives.
open internal(set) var fileHash: String? {
public internal(set) var fileHash: String? {
get {
return allValues[.documentIdentifierKey] as? String
}
@@ -141,7 +137,7 @@ public final class OneDriveFileObject: FileObject {
switch crudePath {
case hasPrefix("items/"):
let components = (crudePath as NSString).pathComponents
let components = crudePath.components(separatedBy: "/")
return components.dropFirst().first.map { "id:\($0)" } ?? ""
case hasPrefix("root:"):
return crudePath.components(separatedBy: ":").dropFirst().first ?? ""
@@ -151,8 +147,8 @@ public final class OneDriveFileObject: FileObject {
}
}
internal extension OneDriveFileProvider {
internal func upload_multipart_data(_ targetPath: String, data: Data, operation: FileOperationType,
extension OneDriveFileProvider {
func upload_multipart_data(_ targetPath: String, data: Data, operation: FileOperationType,
overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
return self.upload_multipart(targetPath, operation: operation, size: Int64(data.count), overwrite: overwrite, dataProvider: {
let range = $0.clamped(to: 0..<Int64(data.count))
@@ -160,13 +156,13 @@ internal extension OneDriveFileProvider {
}, completionHandler: completionHandler)
}
internal func upload_multipart_file(_ targetPath: String, file: URL, operation: FileOperationType,
func upload_multipart_file(_ targetPath: String, file: URL, operation: FileOperationType,
overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
// upload task can't handle uploading file
return self.upload_multipart(targetPath, operation: operation, size: file.fileSize, overwrite: overwrite, dataProvider: { range in
guard let handle = FileHandle(forReadingAtPath: file.path) else {
throw self.cocoaError(targetPath, code: .fileNoSuchFile)
throw CocoaError(.fileNoSuchFile, path: targetPath)
}
defer {
@@ -176,7 +172,7 @@ internal extension OneDriveFileProvider {
let offset = range.lowerBound
handle.seek(toFileOffset: UInt64(offset))
guard Int64(handle.offsetInFile) == offset else {
throw self.cocoaError(targetPath, code: .fileReadTooLarge)
throw CocoaError(.fileReadTooLarge, path: targetPath)
}
return handle.readData(ofLength: range.count)
@@ -221,7 +217,6 @@ internal extension OneDriveFileProvider {
private func upload_multipart(url: URL, operation: FileOperationType, size: Int64, range: Range<Int64>? = nil, uploadedSoFar: Int64 = 0,
progress: Progress, dataProvider: @escaping (Range<Int64>) throws -> Data, completionHandler: SimpleCompletionHandler) {
guard !progress.isCancelled else { return }
var progress = progress
let maximumSize: Int64 = 10_485_760 // Recommended by OneDrive documentations and divides evenly by 320 KiB, max 60MiB.
var request = URLRequest(url: url)
@@ -252,13 +247,13 @@ internal extension OneDriveFileProvider {
}
let task = session.uploadTask(with: request, from: data)
var dictionary: [String: AnyObject] = ["type": operation.description as NSString]
dictionary["source"] = operation.source as NSString?
dictionary["dest"] = operation.destination as NSString?
dictionary["uploadedBytes"] = uploadedSoFar as NSNumber
dictionary["totalBytes"] = data.count as NSNumber
var dictionary: [String: Any] = ["type": operation.description]
dictionary["source"] = operation.source
dictionary["dest"] = operation.destination
dictionary["uploadedBytes"] = NSNumber(value: uploadedSoFar)
dictionary["totalBytes"] = NSNumber(value: data.count)
task.taskDescription = String(jsonDictionary: dictionary)
task.addObserver(self.sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesSent), options: .new, context: &progress)
sessionDelegate?.observerProgress(of: task, using: progress, kind: .upload)
progress.cancellationHandler = { [weak task, weak self] in
task?.cancel()
var deleteRequest = URLRequest(url: url)
@@ -339,7 +334,7 @@ internal extension OneDriveFileProvider {
var keys = [String]()
func add(key: String, value: Any?) {
if let value = value {
if let value = value, !((value as? String)?.isEmpty ?? false) {
keys.append(key)
dic[key] = value
}
+83 -73
View File
@@ -26,8 +26,8 @@ extension FileProviderHTTPError {
return "Status \(code.rawValue): \(code.description)"
}
public var errorDescription: String? {
return "Status \(code.rawValue): \(code.description)"
public var localizedDescription: String {
return description
}
}
@@ -61,57 +61,93 @@ final public class SessionDelegate: NSObject, URLSessionDataDelegate, URLSession
self.credential = fileProvider.credential
}
open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let progress = context?.load(as: Progress.self), let newVal = change?[.newKey] as? Int64 {
switch keyPath ?? "" {
case #keyPath(URLSessionTask.countOfBytesReceived):
progress.completedUnitCount = newVal
if let startTime = progress.userInfo[ProgressUserInfoKey.startingTimeKey] as? Date, let task = object as? URLSessionTask {
let elapsed = Date().timeIntervalSince(startTime)
let throughput = Double(newVal) / elapsed
progress.setUserInfoObject(NSNumber(value: throughput), forKey: .throughputKey)
if task.countOfBytesExpectedToReceive > 0 {
let remain = task.countOfBytesExpectedToReceive - task.countOfBytesReceived
let estimatedTimeRemaining = Double(remain) / elapsed
progress.setUserInfoObject(NSNumber(value: estimatedTimeRemaining), forKey: .estimatedTimeRemainingKey)
}
public enum ObserveKind {
case upload
case download
}
private let observeProgressesLock = NSLock()
private var observeProgresses = [(task: URLSessionTask, progress: Progress, kind: ObserveKind)]()
public func observerProgress(of task: URLSessionTask, using: Progress, kind: ObserveKind) {
switch kind {
case .upload:
task.addObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesSent), options: .new, context: nil)
case .download:
task.addObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesReceived), options: .new, context: nil)
task.addObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToReceive), options: .new, context: nil)
}
observeProgressesLock.lock()
observeProgresses.append((task, using, kind))
observeProgressesLock.unlock()
}
func removeObservers(for task: URLSessionTask) {
observeProgressesLock.lock()
observeProgresses = observeProgresses.filter { (item) -> Bool in
if item.task == task {
switch item.kind {
case .upload:
task.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesSent))
case .download:
task.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesReceived))
task.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToReceive))
}
case #keyPath(URLSessionTask.countOfBytesSent):
if let startTime = progress.userInfo[ProgressUserInfoKey.startingTimeKey] as? Date, let task = object as? URLSessionTask {
let elapsed = Date().timeIntervalSince(startTime)
let throughput = Double(newVal) / elapsed
progress.setUserInfoObject(NSNumber(value: throughput), forKey: .throughputKey)
// wokaround for multipart uploading
let json = task.taskDescription?.deserializeJSON()
let uploadedBytes = ((json?["uploadedBytes"] as? Int64) ?? 0) + newVal
let totalBytes = (json?["totalBytes"] as? Int64) ?? task.countOfBytesExpectedToSend
progress.completedUnitCount = uploadedBytes
if totalBytes > 0 {
let remain = totalBytes - uploadedBytes
let estimatedTimeRemaining = Double(remain) / elapsed
progress.setUserInfoObject(NSNumber(value: estimatedTimeRemaining), forKey: .estimatedTimeRemainingKey)
}
} else {
progress.completedUnitCount = newVal
}
case #keyPath(URLSessionTask.countOfBytesExpectedToReceive), #keyPath(URLSessionTask.countOfBytesExpectedToSend):
progress.totalUnitCount = newVal
default:
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return false
} else {
return true
}
}
observeProgressesLock.unlock()
}
public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard let context = context, let keyPath = keyPath, keyPath.contains("countOfBytes") else { return }
let progress = context.assumingMemoryBound(to: Progress.self).pointee
guard let newVal = change?[.newKey] as? Int64 else { return }
switch keyPath {
case #keyPath(URLSessionTask.countOfBytesReceived):
progress.completedUnitCount = newVal
if let startTime = progress.userInfo[ProgressUserInfoKey.startingTimeKey] as? Date, let task = object as? URLSessionTask {
let elapsed = Date().timeIntervalSince(startTime)
let throughput = Double(newVal) / elapsed
progress.setUserInfoObject(NSNumber(value: throughput), forKey: .throughputKey)
if task.countOfBytesExpectedToReceive > 0 {
let remain = task.countOfBytesExpectedToReceive - task.countOfBytesReceived
let estimatedTimeRemaining = Double(remain) / elapsed
progress.setUserInfoObject(NSNumber(value: estimatedTimeRemaining), forKey: .estimatedTimeRemainingKey)
}
}
case #keyPath(URLSessionTask.countOfBytesSent):
if let startTime = progress.userInfo[ProgressUserInfoKey.startingTimeKey] as? Date, let task = object as? URLSessionTask {
let elapsed = Date().timeIntervalSince(startTime)
let throughput = Double(newVal) / elapsed
progress.setUserInfoObject(NSNumber(value: throughput), forKey: .throughputKey)
// wokaround for multipart uploading
let json = task.taskDescription?.deserializeJSON()
let uploadedBytes = ((json?["uploadedBytes"] as? Int64) ?? 0) + newVal
let totalBytes = (json?["totalBytes"] as? Int64) ?? task.countOfBytesExpectedToSend
progress.completedUnitCount = uploadedBytes
if totalBytes > 0 {
let remain = totalBytes - uploadedBytes
let estimatedTimeRemaining = Double(remain) / elapsed
progress.setUserInfoObject(NSNumber(value: estimatedTimeRemaining), forKey: .estimatedTimeRemainingKey)
}
} else {
progress.completedUnitCount = newVal
}
case #keyPath(URLSessionTask.countOfBytesExpectedToReceive), #keyPath(URLSessionTask.countOfBytesExpectedToSend):
progress.totalUnitCount = newVal
default:
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
// codebeat:disable[ARITY]
public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if task is URLSessionUploadTask {
task.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesSent))
//task.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToSend))
} else if task is URLSessionDownloadTask {
task.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesReceived))
task.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToReceive))
}
self.removeObservers(for: task)
_ = dataCompletionHandlersForTasks[session.sessionDescription!]?.removeValue(forKey: task.taskIdentifier)
if !(error == nil && task is URLSessionDownloadTask) {
@@ -119,32 +155,6 @@ final public class SessionDelegate: NSObject, URLSessionDataDelegate, URLSession
completionHandler?(error)
_ = completionHandlersForTasks[session.sessionDescription!]?.removeValue(forKey: task.taskIdentifier)
}
guard let json = task.taskDescription?.deserializeJSON(),
let op = FileOperationType(json: json), let fileProvider = fileProvider else {
return
}
switch op {
case .fetch:
if task is URLSessionDataTask {
task.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesReceived))
task.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToReceive))
}
default:
break
}
if !(task is URLSessionDownloadTask), case FileOperationType.fetch = op {
return
}
if #available(iOS 9.0, macOS 10.11, *) {
if task is URLSessionStreamTask {
return
}
}
fileProvider.delegateNotify(op, error: error)
}
public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
@@ -216,7 +226,7 @@ final public class SessionDelegate: NSObject, URLSessionDataDelegate, URLSession
authenticate(didReceive: challenge, completionHandler: completionHandler)
}
func authenticate(didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
private func authenticate(didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
switch (challenge.previousFailureCount, credential != nil) {
case (0...1, true):
completionHandler(.useCredential, credential)
+5 -7
View File
@@ -90,8 +90,8 @@ class SMBClient: NSObject, StreamDelegate {
inputStream.delegate = self
outputStream.delegate = self
inputStream.schedule(in: RunLoop.main, forMode: .defaultRunLoopMode)
outputStream.schedule(in: RunLoop.main, forMode: .defaultRunLoopMode)
inputStream.schedule(in: RunLoop.main, forMode: .init("kCFRunLoopDefaultMode"))
outputStream.schedule(in: RunLoop.main, forMode: .init("kCFRunLoopDefaultMode"))
inputStream.open()
outputStream.open()
@@ -101,8 +101,8 @@ class SMBClient: NSObject, StreamDelegate {
fileprivate func close() {
self.inputStream?.close()
self.outputStream?.close()
self.inputStream?.remove(from: RunLoop.main, forMode: .defaultRunLoopMode)
self.outputStream?.remove(from: RunLoop.main, forMode: .defaultRunLoopMode)
self.inputStream?.remove(from: RunLoop.main, forMode: .init("kCFRunLoopDefaultMode"))
self.outputStream?.remove(from: RunLoop.main, forMode: .init("kCFRunLoopDefaultMode"))
self.inputStream?.delegate = nil
self.outputStream?.delegate = nil
@@ -119,9 +119,7 @@ class SMBClient: NSObject, StreamDelegate {
var data = data
var byteSent: Int = 0
while data.count > 0 {
let bytesWritten = data.withUnsafeBytes {
outputStream.write($0, maxLength: data.count)
}
let bytesWritten: Int = (try? outputStream.write(data: data)) ?? -1
if bytesWritten > 0 {
let range = 0..<bytesWritten
+6 -10
View File
@@ -11,7 +11,6 @@ import Foundation
class SMBFileProvider: FileProvider, FileProviderMonitor {
open class var type: String { return "SMB" }
open var baseURL: URL?
open var currentPath: String = ""
open var dispatch_queue: DispatchQueue
open var operation_queue: OperationQueue
open weak var delegate: FileProviderDelegate?
@@ -25,11 +24,7 @@ class SMBFileProvider: FileProvider, FileProviderMonitor {
}
self.baseURL = baseURL.appendingPathComponent("")
#if swift(>=3.1)
let queueLabel = "FileProvider.\(Swift.type(of: self).type)"
#else
let queueLabel = "FileProvider.\(type(of: self).type)"
#endif
dispatch_queue = DispatchQueue(label: queueLabel, attributes: .concurrent)
operation_queue = OperationQueue()
operation_queue.name = "\(queueLabel).Operation"
@@ -38,18 +33,20 @@ class SMBFileProvider: FileProvider, FileProviderMonitor {
}
public required convenience init?(coder aDecoder: NSCoder) {
guard let baseURL = aDecoder.decodeObject(forKey: "baseURL") as? URL else {
guard let baseURL = aDecoder.decodeObject(of: NSURL.self, forKey: "baseURL") as URL? else {
if #available(macOS 10.11, iOS 9.0, tvOS 9.0, *) {
aDecoder.failWithError(CocoaError(.coderValueNotFound,
userInfo: [NSLocalizedDescriptionKey: "Base URL is not set."]))
}
return nil
}
self.init(baseURL: baseURL,
credential: aDecoder.decodeObject(forKey: "credential") as? URLCredential)
self.currentPath = aDecoder.decodeObject(forKey: "currentPath") as? String ?? ""
credential: aDecoder.decodeObject(of: URLCredential.self, forKey: "credential"))
}
open func encode(with aCoder: NSCoder) {
aCoder.encode(self.baseURL, forKey: "baseURL")
aCoder.encode(self.credential, forKey: "credential")
aCoder.encode(self.currentPath, forKey: "currentPath")
}
public static var supportsSecureCoding: Bool {
@@ -138,7 +135,6 @@ class SMBFileProvider: FileProvider, FileProviderMonitor {
open func copy(with zone: NSZone? = nil) -> Any {
let copy = SMBFileProvider(baseURL: self.baseURL!, credential: self.credential!)!
copy.currentPath = self.currentPath
copy.delegate = self.delegate
copy.fileOperationDelegate = self.fileOperationDelegate
return copy
+1 -5
View File
@@ -31,11 +31,7 @@ protocol SMBRequestBody {
extension SMBRequestBody {
var command: SMB2.Command {
#if swift(>=3.1)
return Swift.type(of: self).command
#else
return type(of: self).command
#endif
return Swift.type(of: self).command
}
func data() -> Data {
+83
View File
@@ -0,0 +1,83 @@
//
// ServerTrustPolicy.swift
//
// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/)
//
// 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: - ServerTrustPolicy
/// The `ServerTrustPolicy` evaluates the server trust generally provided by an `NSURLAuthenticationChallenge` when
/// connecting to a server over a secure HTTPS connection. The policy configuration then evaluates the server trust
/// with a given set of criteria to determine whether the server trust is valid and the connection should be made.
///
/// Using pinned certificates or public keys for evaluation helps prevent man-in-the-middle (MITM) attacks and other
/// vulnerabilities. Applications dealing with sensitive customer data or financial information are strongly encouraged
/// to route all communication over an HTTPS connection with pinning enabled.
///
/// - performDefaultEvaluation: Uses the default server trust evaluation while allowing you to control whether to
/// validate the host provided by the challenge. Applications are encouraged to always
/// validate the host in production environments to guarantee the validity of the server's
/// certificate chain.
///
/// - disableEvaluation: Disables all evaluation which in turn will always consider any server trust as valid.
///
public enum ServerTrustPolicy {
case performDefaultEvaluation(validateHost: Bool)
case disableEvaluation
// MARK: - Evaluation
/// Evaluates whether the server trust is valid.
///
/// - returns: Whether the server trust is valid.
public func evaluate() -> Bool {
var serverTrustIsValid = false
switch self {
case .performDefaultEvaluation(_):
break
case .disableEvaluation:
serverTrustIsValid = true
}
return serverTrustIsValid
}
/// Evaluates whether the server trust is valid for the given host.
///
/// - parameter serverTrust: The server trust to evaluate.
/// - parameter host: The host of the challenge protection space.
///
/// - returns: Whether the server trust is valid.
public func evaluate(_ serverTrust: SecTrust, forHost host: String) -> Bool {
var serverTrustIsValid = false
switch self {
case let .performDefaultEvaluation(validateHost):
let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
SecTrustSetPolicies(serverTrust, policy)
case .disableEvaluation:
serverTrustIsValid = true
}
return serverTrustIsValid
}
}
+13 -13
View File
@@ -47,6 +47,10 @@ open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
public required convenience init?(coder aDecoder: NSCoder) {
guard let baseURL = aDecoder.decodeObject(of: NSURL.self, forKey: "baseURL") as URL? else {
if #available(macOS 10.11, iOS 9.0, tvOS 9.0, *) {
aDecoder.failWithError(CocoaError(.coderValueNotFound,
userInfo: [NSLocalizedDescriptionKey: "Base URL is not set."]))
}
return nil
}
self.init(baseURL: baseURL,
@@ -289,7 +293,7 @@ open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
open func publicLink(to path: String, completionHandler: @escaping ((URL?, FileObject?, Date?, Error?) -> Void)) {
guard self.baseURL?.host?.contains("dav.yandex.") ?? false else {
dispatch_queue.async {
completionHandler(nil, nil, nil, self.urlError(path, code: .resourceUnavailable))
completionHandler(nil, nil, nil, URLError(.resourceUnavailable, url: self.url(of: path)))
}
return
}
@@ -373,13 +377,13 @@ open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
return FileProviderWebDavError(code: code, path: path ?? "", serverDescription: data.flatMap({ String(data: $0, encoding: .utf8) }), url: self.url(of: path ?? ""))
}
override func multiStatusHandler(source: String, data: Data, completionHandler: SimpleCompletionHandler) {
override func multiStatusError(operation: FileOperationType, data: Data) -> FileProviderHTTPError? {
let xresponses = DavResponse.parse(xmlResponse: data, baseURL: self.baseURL)
for xresponse in xresponses where (xresponse.status ?? 0) >= 300 {
let code = xresponse.status.flatMap { FileProviderHTTPErrorCode(rawValue: $0) } ?? .internalServerError
let error = self.serverError(with: code, path: source, data: data)
completionHandler?(error)
return self.serverError(with: code, path: operation.source, data: data)
}
return nil
}
/*
@@ -405,14 +409,14 @@ extension WebDAVFileProvider: ExtendedFileProvider {
return false
}
let supportedExt: [String] = ["jpg", "jpeg", "png", "gif"]
return supportedExt.contains((path as NSString).pathExtension)
return supportedExt.contains(path.pathExtension)
}
@discardableResult
open func thumbnailOfFile(path: String, dimension: CGSize?, completionHandler: @escaping ((ImageClass?, Error?) -> Void)) -> Progress? {
guard self.baseURL?.host?.contains("dav.yandex.") ?? false else {
dispatch_queue.async {
completionHandler(nil, self.urlError(path, code: .resourceUnavailable))
completionHandler(nil, URLError(.resourceUnavailable, url: self.url(of: path)))
}
return nil
}
@@ -444,7 +448,7 @@ extension WebDAVFileProvider: ExtendedFileProvider {
@discardableResult
open func propertiesOfFile(path: String, completionHandler: @escaping (([String : Any], [String], Error?) -> Void)) -> Progress? {
dispatch_queue.async {
completionHandler([:], [], self.urlError(path, code: .resourceUnavailable))
completionHandler([:], [], URLError(.resourceUnavailable, url: self.url(of: path)))
}
return nil
}
@@ -463,11 +467,7 @@ struct DavResponse {
init? (_ node: AEXMLElement, baseURL: URL?) {
func standardizePath(_ str: String) -> String {
#if swift(>=4.0)
let trimmedStr = str.hasPrefix("/") ? String(str[str.index(after: str.startIndex)...]) : str
#else
let trimmedStr = str.hasPrefix("/") ? str.substring(from: str.index(after: str.startIndex)) : str
#endif
return trimmedStr.addingPercentEncoding(withAllowedCharacters: .filePathAllowed) ?? str
}
@@ -578,7 +578,7 @@ public final class WebDavFileObject: FileObject {
}
/// MIME type of the file.
open internal(set) var contentType: ContentMIMEType {
public internal(set) var contentType: ContentMIMEType {
get {
return (allValues[.mimeTypeKey] as? String).flatMap(ContentMIMEType.init(rawValue:)) ?? .stream
}
@@ -588,7 +588,7 @@ public final class WebDavFileObject: FileObject {
}
/// HTTP E-Tag, can be used to mark changed files.
open internal(set) var entryTag: String? {
public internal(set) var entryTag: String? {
get {
return allValues[.entryTagKey] as? String
}
+31 -1
View File
@@ -65,6 +65,7 @@ class FilesProviderTests: XCTestCase, FileProviderDelegate {
}
func testFTPPassive() {
/*
guard let urlStr = ProcessInfo.processInfo.environment["ftp_url"] else { return }
let url = URL(string: urlStr)!
let cred: URLCredential?
@@ -73,6 +74,11 @@ class FilesProviderTests: XCTestCase, FileProviderDelegate {
} else {
cred = nil
}
*/
let url = URL(string: "ftp://ftp.edmapplication.com")!
let cred = URLCredential(user: "abbas@edmapplication.com", password: "baTsivWZ4", persistence: .forSession)
//let url = URL(string: "ftpes://ftp.adidas-group.com:21")!
//let cred = URLCredential(user: "ecomwe-reversals-full", password: "rNeUj726Xqk2k", persistence: .forSession)
let provider = FTPFileProvider(baseURL: url, mode: .extendedPassive, credential: cred)!
provider.delegate = self
testArchiving(provider)
@@ -235,8 +241,9 @@ class FilesProviderTests: XCTestCase, FileProviderDelegate {
private func randomData(size: Int = 262144) -> Data {
var keyData = Data(count: size)
let count = keyData.count
let result = keyData.withUnsafeMutableBytes {
SecRandomCopyBytes(kSecRandomDefault, keyData.count, $0)
SecRandomCopyBytes(kSecRandomDefault, count, $0)
}
if result == errSecSuccess {
return keyData
@@ -334,6 +341,26 @@ class FilesProviderTests: XCTestCase, FileProviderDelegate {
}
}
fileprivate func testSymlink(_ provider: FileProvider & FileProviderSymbolicLink, filePath: String) {
let desc = "Symlink in \(provider.type)"
print("Test started: \(desc).")
let expectation = XCTestExpectation(description: desc)
provider.create(symbolicLink: filePath + " Link", withDestinationPath: filePath) { (error) in
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
provider.destination(ofSymbolicLink: filePath + " Link", completionHandler: { (fileObject, error) in
provider.removeItem(path: filePath + " Link", completionHandler: nil)
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
XCTAssertNotNil(fileObject, "file '\(filePath)' didn't exist")
guard fileObject != nil else { return }
XCTAssertEqual(fileObject!.path, filePath, "file path is different from '\(filePath)'")
XCTAssertEqual(fileObject!.type, URLFileResourceType.regular, "file '\(filePath)' is not a regular file")
expectation.fulfill()
})
}
wait(for: [expectation], timeout: timeout)
print("Test fulfilled: \(desc).")
}
fileprivate func testBasic(_ provider: FileProvider) {
let filepath = "/test/file.txt"
let fileurl = provider.url(of: filepath)
@@ -360,6 +387,9 @@ class FilesProviderTests: XCTestCase, FileProviderDelegate {
testContentsFile(provider, filePath: textFilePath)
testRenameFile(provider, filePath: textFilePath, to: renamedFilePath)
testCopyFile(provider, filePath: renamedFilePath, to: textFilePath)
if let provider = provider as? FileProvider & FileProviderSymbolicLink {
testSymlink(provider, filePath: textFilePath)
}
testRemoveFile(provider, filePath: textFilePath)
// TODO: Test search