Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| abf68a6254 | |||
| 9384264d29 | |||
| 90706f1842 | |||
| cb42552573 | |||
| b9c1729733 | |||
| 83eb97a100 | |||
| 7c7763d105 | |||
| d1ac332ace | |||
| 737cd7a7c3 | |||
| d050ceb3bc | |||
| 504ca76077 | |||
| d462b5d17b | |||
| 004966aa69 | |||
| d97f6c0289 | |||
| 967a5a05fd | |||
| 9d5ac1064c | |||
| 8a5edc5700 | |||
| 933ef9b5d4 | |||
| d6d777fd65 | |||
| 9cbe0fc661 | |||
| 59ba5b7817 | |||
| 3f59178b9e | |||
| 92f0970097 | |||
| 1fd724c3dc | |||
| ab1da39fc2 | |||
| 36c600ebb0 | |||
| 26a9e2de1e | |||
| 5c33683a8c | |||
| 08d476654e | |||
| b597244be4 | |||
| 76aee40c0d | |||
| 38fb3fc89a | |||
| e2572d2810 | |||
| 5e3db16401 | |||
| 311fcc5a88 | |||
| fb389c1d69 | |||
| f1f7955b86 | |||
| 7b21605eeb | |||
| cf6e6f96f5 | |||
| 91f2610a72 | |||
| 50f0d33233 | |||
| ba9cad8daf | |||
| 057bf1a663 | |||
| 753055602f | |||
| 7a5cef47b5 | |||
| f5c6403769 | |||
| e2520ff154 | |||
| 7a3c4a297a | |||
| b946d514a0 | |||
| 5e49202ba6 | |||
| 399d755eac |
+7
-7
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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:!
|
||||
|
||||
[](https://beerpay.io/amosavian/FileProvider) [](https://beerpay.io/amosavian/FileProvider?focus=wish)
|
||||
@@ -43,7 +43,7 @@ internal class AEXMLDocument: AEXMLElement {
|
||||
return rootElement
|
||||
}
|
||||
|
||||
open let options: AEXMLOptions
|
||||
public let options: AEXMLOptions
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
+673
-569
File diff suppressed because it is too large
Load Diff
+111
-37
@@ -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
File diff suppressed because it is too large
Load Diff
+26
-30
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user