Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d2cf657ab2 | |||
| f2d54c7f92 | |||
| 0e9a82a288 | |||
| ea7e089718 | |||
| 19ea14ebc1 | |||
| 47885c0a4e | |||
| ac44aa190f | |||
| 50f6a393a0 | |||
| 779b38f381 | |||
| a374841883 | |||
| 0123d8b117 | |||
| fdc4bb818d | |||
| a08a9fe7a0 | |||
| 792ac6b015 | |||
| 8bad5944bc | |||
| 6ef2ab11c4 | |||
| 7f27d46c70 | |||
| b1ec99b1b8 | |||
| c4b8065cd3 | |||
| 2d8454c711 |
@@ -16,7 +16,7 @@ Pod::Spec.new do |s|
|
||||
#
|
||||
|
||||
s.name = "FileProvider"
|
||||
s.version = "0.8.0"
|
||||
s.version = "0.9.0"
|
||||
s.summary = "FileManager replacement for Local and Remote (WebDAV/Dropbox/SMB2) files on iOS and macOS."
|
||||
|
||||
# This description is used to generate tags and improve search results.
|
||||
|
||||
@@ -92,6 +92,26 @@
|
||||
799396E01D48C02300086753 /* WebDAVFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A61D48C02300086753 /* WebDAVFileProvider.swift */; };
|
||||
799396E11D48C02300086753 /* WebDAVFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A61D48C02300086753 /* WebDAVFileProvider.swift */; };
|
||||
799396E21D48C02300086753 /* WebDAVFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A61D48C02300086753 /* WebDAVFileProvider.swift */; };
|
||||
79BD638C1E2CC2300035128C /* ExtendedLocalFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD638B1E2CC2300035128C /* ExtendedLocalFileProvider.swift */; };
|
||||
79BD638D1E2CC2300035128C /* ExtendedLocalFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD638B1E2CC2300035128C /* ExtendedLocalFileProvider.swift */; };
|
||||
79BD638E1E2CC2300035128C /* ExtendedLocalFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD638B1E2CC2300035128C /* ExtendedLocalFileProvider.swift */; };
|
||||
79BD63A81E2CC2940035128C /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63A71E2CC2940035128C /* CoreGraphics.framework */; };
|
||||
79BD63AA1E2CC2BB0035128C /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63A91E2CC2BB0035128C /* AVFoundation.framework */; };
|
||||
79BD63AC1E2CC2C20035128C /* ImageIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63AB1E2CC2C20035128C /* ImageIO.framework */; };
|
||||
79BD63AE1E2CC2EB0035128C /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63AD1E2CC2EB0035128C /* MediaPlayer.framework */; };
|
||||
79BD63B01E2CC3300035128C /* libxml2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63AF1E2CC3300035128C /* libxml2.tbd */; };
|
||||
79BD63B21E2CC3350035128C /* ImageIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63B11E2CC3350035128C /* ImageIO.framework */; };
|
||||
79BD63B41E2CC33D0035128C /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63B31E2CC33D0035128C /* MediaPlayer.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 */; };
|
||||
79BD63BC1E2CC3B90035128C /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63BB1E2CC3B90035128C /* MediaPlayer.framework */; };
|
||||
79BD63BE1E2CC3C20035128C /* ImageIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63BD1E2CC3C20035128C /* ImageIO.framework */; };
|
||||
79BD63C01E2CC3CD0035128C /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63BF1E2CC3CD0035128C /* CoreGraphics.framework */; };
|
||||
79BD63C21E2CC3D30035128C /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63C11E2CC3D30035128C /* AVFoundation.framework */; };
|
||||
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 */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
@@ -131,6 +151,22 @@
|
||||
799396A31D48C02300086753 /* SMB2Types.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2Types.swift; sourceTree = "<group>"; };
|
||||
799396A41D48C02300086753 /* SMBErrorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMBErrorType.swift; sourceTree = "<group>"; };
|
||||
799396A61D48C02300086753 /* WebDAVFileProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebDAVFileProvider.swift; sourceTree = "<group>"; };
|
||||
79BD638B1E2CC2300035128C /* ExtendedLocalFileProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtendedLocalFileProvider.swift; sourceTree = "<group>"; };
|
||||
79BD63A71E2CC2940035128C /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.2.sdk/System/Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; };
|
||||
79BD63A91E2CC2BB0035128C /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.2.sdk/System/Library/Frameworks/AVFoundation.framework; sourceTree = DEVELOPER_DIR; };
|
||||
79BD63AB1E2CC2C20035128C /* ImageIO.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ImageIO.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.2.sdk/System/Library/Frameworks/ImageIO.framework; sourceTree = DEVELOPER_DIR; };
|
||||
79BD63AD1E2CC2EB0035128C /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.2.sdk/System/Library/Frameworks/MediaPlayer.framework; sourceTree = DEVELOPER_DIR; };
|
||||
79BD63AF1E2CC3300035128C /* libxml2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libxml2.tbd; path = usr/lib/libxml2.tbd; sourceTree = SDKROOT; };
|
||||
79BD63B11E2CC3350035128C /* ImageIO.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ImageIO.framework; path = System/Library/Frameworks/ImageIO.framework; sourceTree = SDKROOT; };
|
||||
79BD63B31E2CC33D0035128C /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = System/Library/Frameworks/MediaPlayer.framework; sourceTree = SDKROOT; };
|
||||
79BD63B51E2CC3860035128C /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
|
||||
79BD63B71E2CC38D0035128C /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
|
||||
79BD63B91E2CC39B0035128C /* libxml2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libxml2.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.1.sdk/usr/lib/libxml2.tbd; sourceTree = DEVELOPER_DIR; };
|
||||
79BD63BB1E2CC3B90035128C /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.1.sdk/System/Library/Frameworks/MediaPlayer.framework; sourceTree = DEVELOPER_DIR; };
|
||||
79BD63BD1E2CC3C20035128C /* ImageIO.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ImageIO.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.1.sdk/System/Library/Frameworks/ImageIO.framework; sourceTree = DEVELOPER_DIR; };
|
||||
79BD63BF1E2CC3CD0035128C /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.1.sdk/System/Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; };
|
||||
79BD63C11E2CC3D30035128C /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.1.sdk/System/Library/Frameworks/AVFoundation.framework; sourceTree = DEVELOPER_DIR; };
|
||||
79F5745A1DFDB10A00179ABF /* FileObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileObject.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -138,6 +174,10 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
79BD63AA1E2CC2BB0035128C /* AVFoundation.framework in Frameworks */,
|
||||
79BD63AC1E2CC2C20035128C /* ImageIO.framework in Frameworks */,
|
||||
79BD63A81E2CC2940035128C /* CoreGraphics.framework in Frameworks */,
|
||||
79BD63AE1E2CC2EB0035128C /* MediaPlayer.framework in Frameworks */,
|
||||
791950F51DE58A5400B4426E /* libxml2.tbd in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -146,6 +186,11 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
79BD63B81E2CC38D0035128C /* AVFoundation.framework in Frameworks */,
|
||||
79BD63B61E2CC3860035128C /* CoreFoundation.framework in Frameworks */,
|
||||
79BD63B21E2CC3350035128C /* ImageIO.framework in Frameworks */,
|
||||
79BD63B41E2CC33D0035128C /* MediaPlayer.framework in Frameworks */,
|
||||
79BD63B01E2CC3300035128C /* libxml2.tbd in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -153,6 +198,11 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
79BD63C21E2CC3D30035128C /* AVFoundation.framework in Frameworks */,
|
||||
79BD63C01E2CC3CD0035128C /* CoreGraphics.framework in Frameworks */,
|
||||
79BD63BE1E2CC3C20035128C /* ImageIO.framework in Frameworks */,
|
||||
79BD63BC1E2CC3B90035128C /* MediaPlayer.framework in Frameworks */,
|
||||
79BD63BA1E2CC39B0035128C /* libxml2.tbd in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -162,6 +212,20 @@
|
||||
791950F31DE58A5300B4426E /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
79BD63C11E2CC3D30035128C /* AVFoundation.framework */,
|
||||
79BD63BF1E2CC3CD0035128C /* CoreGraphics.framework */,
|
||||
79BD63BD1E2CC3C20035128C /* ImageIO.framework */,
|
||||
79BD63BB1E2CC3B90035128C /* MediaPlayer.framework */,
|
||||
79BD63B91E2CC39B0035128C /* libxml2.tbd */,
|
||||
79BD63B71E2CC38D0035128C /* AVFoundation.framework */,
|
||||
79BD63B51E2CC3860035128C /* CoreFoundation.framework */,
|
||||
79BD63B31E2CC33D0035128C /* MediaPlayer.framework */,
|
||||
79BD63B11E2CC3350035128C /* ImageIO.framework */,
|
||||
79BD63AF1E2CC3300035128C /* libxml2.tbd */,
|
||||
79BD63AD1E2CC2EB0035128C /* MediaPlayer.framework */,
|
||||
79BD63AB1E2CC2C20035128C /* ImageIO.framework */,
|
||||
79BD63A91E2CC2BB0035128C /* AVFoundation.framework */,
|
||||
79BD63A71E2CC2940035128C /* CoreGraphics.framework */,
|
||||
791950F41DE58A5400B4426E /* libxml2.tbd */,
|
||||
);
|
||||
name = Frameworks;
|
||||
@@ -182,6 +246,7 @@
|
||||
7993965B1D48B7BF00086753 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
79E34A101E2AC6C600E1293B /* Extra */,
|
||||
799396911D48C02300086753 /* Sources */,
|
||||
7993968A1D48B8C700086753 /* Pod */,
|
||||
799396681D48B7F600086753 /* Products */,
|
||||
@@ -218,8 +283,10 @@
|
||||
794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */,
|
||||
799396941D48C02300086753 /* FileProvider.h */,
|
||||
799396951D48C02300086753 /* FileProvider.swift */,
|
||||
79F5745A1DFDB10A00179ABF /* FileObject.swift */,
|
||||
799396961D48C02300086753 /* LocalFileProvider.swift */,
|
||||
792572401DF23BDA006A1526 /* LocalHelper.swift */,
|
||||
79BD638B1E2CC2300035128C /* ExtendedLocalFileProvider.swift */,
|
||||
7902C0851D61B56D00564440 /* RemoteSession.swift */,
|
||||
7924B1A81D89F79200589DB7 /* FPSStreamTask.swift */,
|
||||
799396971D48C02300086753 /* SMBClient.swift */,
|
||||
@@ -249,6 +316,13 @@
|
||||
path = SMBTypes;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
79E34A101E2AC6C600E1293B /* Extra */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
name = Extra;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
@@ -402,6 +476,7 @@
|
||||
799396D41D48C02300086753 /* SMB2Tree.swift in Sources */,
|
||||
7924B1A21D89DAE000589DB7 /* Options.swift in Sources */,
|
||||
792572411DF23BDA006A1526 /* LocalHelper.swift in Sources */,
|
||||
79F5745B1DFDB10B00179ABF /* FileObject.swift in Sources */,
|
||||
7924B1991D89DAE000589DB7 /* Element.swift in Sources */,
|
||||
799396C81D48C02300086753 /* SMB2IOCtl.swift in Sources */,
|
||||
799396D71D48C02300086753 /* SMB2Types.swift in Sources */,
|
||||
@@ -422,6 +497,7 @@
|
||||
799396C21D48C02300086753 /* SMB2FileHandle.swift in Sources */,
|
||||
799396CB1D48C02300086753 /* SMB2Query.swift in Sources */,
|
||||
799396AA1D48C02300086753 /* DropboxFileProvider.swift in Sources */,
|
||||
79BD638C1E2CC2300035128C /* ExtendedLocalFileProvider.swift in Sources */,
|
||||
7924B1B31D89FD6400589DB7 /* SMBClient.swift in Sources */,
|
||||
7924B1A51D89DAE000589DB7 /* Parser.swift in Sources */,
|
||||
799396B01D48C02300086753 /* FileProvider.swift in Sources */,
|
||||
@@ -437,6 +513,7 @@
|
||||
799396D51D48C02300086753 /* SMB2Tree.swift in Sources */,
|
||||
7924B1A31D89DAE000589DB7 /* Options.swift in Sources */,
|
||||
792572421DF23BDA006A1526 /* LocalHelper.swift in Sources */,
|
||||
79F5745C1DFDB10B00179ABF /* FileObject.swift in Sources */,
|
||||
7924B1B01D89F7DE00589DB7 /* FPSStreamTask.swift in Sources */,
|
||||
7924B19A1D89DAE000589DB7 /* Element.swift in Sources */,
|
||||
799396C91D48C02300086753 /* SMB2IOCtl.swift in Sources */,
|
||||
@@ -457,6 +534,7 @@
|
||||
799396C31D48C02300086753 /* SMB2FileHandle.swift in Sources */,
|
||||
799396CC1D48C02300086753 /* SMB2Query.swift in Sources */,
|
||||
7924B1AD1D89F7D800589DB7 /* SMBClient.swift in Sources */,
|
||||
79BD638D1E2CC2300035128C /* ExtendedLocalFileProvider.swift in Sources */,
|
||||
799396AB1D48C02300086753 /* DropboxFileProvider.swift in Sources */,
|
||||
7924B1A61D89DAE000589DB7 /* Parser.swift in Sources */,
|
||||
799396B11D48C02300086753 /* FileProvider.swift in Sources */,
|
||||
@@ -472,6 +550,7 @@
|
||||
799396D61D48C02300086753 /* SMB2Tree.swift in Sources */,
|
||||
7924B1A41D89DAE000589DB7 /* Options.swift in Sources */,
|
||||
792572431DF23BDA006A1526 /* LocalHelper.swift in Sources */,
|
||||
79F5745D1DFDB10B00179ABF /* FileObject.swift in Sources */,
|
||||
7924B1B11D89F7DF00589DB7 /* FPSStreamTask.swift in Sources */,
|
||||
7924B19B1D89DAE000589DB7 /* Element.swift in Sources */,
|
||||
799396CA1D48C02300086753 /* SMB2IOCtl.swift in Sources */,
|
||||
@@ -492,6 +571,7 @@
|
||||
799396C41D48C02300086753 /* SMB2FileHandle.swift in Sources */,
|
||||
799396CD1D48C02300086753 /* SMB2Query.swift in Sources */,
|
||||
7924B1AE1D89F7D900589DB7 /* SMBClient.swift in Sources */,
|
||||
79BD638E1E2CC2300035128C /* ExtendedLocalFileProvider.swift in Sources */,
|
||||
799396AC1D48C02300086753 /* DropboxFileProvider.swift in Sources */,
|
||||
7924B1A71D89DAE000589DB7 /* Parser.swift in Sources */,
|
||||
799396B21D48C02300086753 /* FileProvider.swift in Sources */,
|
||||
@@ -505,7 +585,7 @@
|
||||
799396601D48B7BF00086753 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_VERSION_STRING = 0.7.0;
|
||||
BUNDLE_VERSION_STRING = 0.8.3;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
@@ -528,14 +608,14 @@
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SWIFT_VERSION = 3.0.1;
|
||||
SWIFT_VERSION = 3.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
799396611D48B7BF00086753 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_VERSION_STRING = 0.7.0;
|
||||
BUNDLE_VERSION_STRING = 0.8.3;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
@@ -556,7 +636,7 @@
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_VERSION = 3.0.1;
|
||||
SWIFT_VERSION = 3.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -565,7 +645,7 @@
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
BUNDLE_VERSION_STRING = 0.8.0;
|
||||
BUNDLE_VERSION_STRING = 0.8.2;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
@@ -618,7 +698,7 @@
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
BUNDLE_VERSION_STRING = 0.8.0;
|
||||
BUNDLE_VERSION_STRING = 0.8.2;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
@@ -686,6 +766,7 @@
|
||||
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;
|
||||
@@ -741,6 +822,7 @@
|
||||
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;
|
||||
|
||||
@@ -43,21 +43,26 @@ Legacy version is available in swift-2 branch
|
||||
|
||||
### Cocoapods / Carthage / Swift Package Manager
|
||||
|
||||
FileProvider supports both CocoaPods.
|
||||
|
||||
Add this line to your pods file:
|
||||
|
||||
```ruby
|
||||
pod "FileProvider"
|
||||
```
|
||||
|
||||
Or add this to cartfile:
|
||||
Or add this to Cartfile:
|
||||
|
||||
```
|
||||
github "amosavian/FileProvider"
|
||||
```
|
||||
|
||||
### Git
|
||||
Or to use in Swift Package Manager add this line in `Dependencies`:
|
||||
|
||||
```swift
|
||||
.Package(url: "https://github.com/amosavian/FileProvider.git", majorVersion: 0, minorVersion: 8)
|
||||
```
|
||||
|
||||
### Manually
|
||||
|
||||
To have latest updates with ease, use this command on terminal to get a clone:
|
||||
|
||||
```bash
|
||||
@@ -75,11 +80,11 @@ if you have a git based project, use this command in your projects directory to
|
||||
```bash
|
||||
git submodule add https://github.com/amosavian/FileProvider
|
||||
```
|
||||
Then you can do either:
|
||||
|
||||
### Manually
|
||||
**First way:** Copy Source folder to your project and Voila!
|
||||
* Copy Source folder to your project and Voila!
|
||||
|
||||
**Second way:** Drop FileProvider.xcodeproj to you Xcode workspace and add the framework to your Embeded Binaries in target.
|
||||
* Drop FileProvider.xcodeproj to you Xcode workspace and add the framework to your Embeded Binaries in target.
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -112,7 +117,7 @@ let webdavProvider = WebDAVFileProvider(baseURL: URL(string: "http://www.example
|
||||
|
||||
* In case you want to connect non-secure servers for WebDAV (http) in iOS 9+ / macOS 10.11+ you should disable App Transport Security (ATS) according to [this guide.](https://gist.github.com/mlynch/284699d676fe9ed0abfa)
|
||||
|
||||
* For Dropbox, user is clientID and password is Token which both must be retrieved via [OAuth2 API of Dropbox](https://www.dropbox.com/developers/reference/oauth-guide). There are libraries like [p2/OAuth2](https://github.com/p2/OAuth2) or [OAuthSwift](https://github.com/OAuthSwift/OAuthSwift) which can facilate the procedure to retrieve token.
|
||||
* For Dropbox, user is clientID and password is Token which both must be retrieved via [OAuth2 API of Dropbox](https://www.dropbox.com/developers/reference/oauth-guide). There are libraries like [p2/OAuth2](https://github.com/p2/OAuth2) or [OAuthSwift](https://github.com/OAuthSwift/OAuthSwift) which can facilate the procedure to retrieve token. The latter is easier to use and prefered.
|
||||
|
||||
For interaction with UI, set delegate variable of `FileProvider` object
|
||||
|
||||
@@ -323,6 +328,41 @@ documentsProvider.unregisterNotifcation(path: provider.currentPath)
|
||||
|
||||
* **Please note** in LocalFileProvider it will also monitor changes in subfolders. This behaviour can varies according to file system specification.
|
||||
|
||||
### Thumbnail and meta-information
|
||||
|
||||
Providers which conform `ExtendedFileProvider` are able to generate thumbnail or provide file meta-information for images, media and pdf files.
|
||||
|
||||
Local and Dropbox providers support this functionality.
|
||||
|
||||
##### Thumbnails
|
||||
To check either file thumbnail is supported or not and fetch thumbnail, use (and modify) these example code:
|
||||
|
||||
```swift
|
||||
let path = "/newImage.jpg"
|
||||
let thumbSize = CGSize(width: 64, height: 64)
|
||||
if documentsProvider.thumbnailOfFileSupported(path: path {
|
||||
documentsProvider..thumbnailOfFile(path: file.path, dimension: thumbSize, completionHandler: { (image, error) in
|
||||
DispatchQueue.main.async {
|
||||
self.previewImage.image = image
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### Meta-informations
|
||||
|
||||
To get meta-information like image/video taken date, dimension, etc., use (and modify) these example code:
|
||||
|
||||
```swift
|
||||
if documentsProvider..propertiesOfFile(path: file.path, completionHandler: { (propertiesDictionary, keys, error) in
|
||||
for key in keys {
|
||||
print("\(key): \(propertiesDictionary[key])")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* **Bonus:** You can modify/extend Local provider generator by setting `LocalFileInformationGenerator` static variables and methods
|
||||
|
||||
## Contribute
|
||||
|
||||
We would love for you to contribute to **FileProvider**, check the `LICENSE` file for more info.
|
||||
|
||||
@@ -118,11 +118,11 @@ extension DropboxFileProvider: FileProviderOperations {
|
||||
return self.writeContents(path: filePath, contents: data ?? Data(), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
public func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
public func moveItem(path: String, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
return doOperation(.move(source: path, destination: toPath), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
public func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
public func copyItem(path: String, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
return doOperation(.copy(source: path, destination: toPath), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@@ -174,17 +174,12 @@ extension DropboxFileProvider: FileProviderOperations {
|
||||
return RemoteOperationHandle(operationType: operation, tasks: [task])
|
||||
}
|
||||
|
||||
public func copyItem(localFile: URL, to toPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
public func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.copy(source: localFile.absoluteString, destination: toPath)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
guard let data = try? Data(contentsOf: localFile) else {
|
||||
let error = throwError(localFile.absoluteString, code: URLError.fileDoesNotExist as FoundationErrorEnum)
|
||||
completionHandler?(error)
|
||||
return nil
|
||||
}
|
||||
return upload_simple(toPath, data: data, overwrite: true, operation: opType, completionHandler: completionHandler)
|
||||
return upload_simple(toPath, localFile: localFile, overwrite: overwrite, operation: opType, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
public func copyItem(path: String, toLocalURL destURL: URL, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
@@ -196,13 +191,14 @@ extension DropboxFileProvider: FileProviderOperations {
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "GET"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
let requestDictionary = ["path": correctPath(path)! as NSString]
|
||||
request.setValue(dictionaryToJSON(requestDictionary), forHTTPHeaderField: "Dropbox-API-Arg")
|
||||
let requestDictionary = ["path": path]
|
||||
let requestJson = dictionaryToJSON(requestDictionary as [String : AnyObject]) ?? ""
|
||||
request.setValue(requestJson, forHTTPHeaderField: "Dropbox-API-Arg")
|
||||
let task = session.downloadTask(with: request, completionHandler: { (cacheURL, response, error) in
|
||||
guard let cacheURL = cacheURL, let httpResponse = response as? HTTPURLResponse , httpResponse.statusCode < 300 else {
|
||||
let code = FileProviderHTTPErrorCode(rawValue: (response as? HTTPURLResponse)?.statusCode ?? -1)
|
||||
let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: nil) : nil
|
||||
let errorData : Data? = nil //Data(contentsOf:cacheURL) // TODO: Figure out how to get error response data for the error description
|
||||
let dbError : FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: String(data: errorData ?? Data(), encoding: .utf8)) : nil
|
||||
completionHandler?(dbError ?? error)
|
||||
return
|
||||
}
|
||||
@@ -220,10 +216,6 @@ extension DropboxFileProvider: FileProviderOperations {
|
||||
}
|
||||
|
||||
extension DropboxFileProvider: FileProviderReadWrite {
|
||||
public func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
|
||||
return self.contents(path: path, offset: 0, length: -1, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
public func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
|
||||
let opType = FileOperationType.fetch(path: path)
|
||||
let url = URL(string: "https://content.dropboxapi.com/2/files/download")!
|
||||
@@ -250,13 +242,13 @@ extension DropboxFileProvider: FileProviderReadWrite {
|
||||
return RemoteOperationHandle(operationType: opType, tasks: [task])
|
||||
}
|
||||
|
||||
public func writeContents(path: String, contents data: Data, atomically: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
public func writeContents(path: String, contents data: Data, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.modify(path: path)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
// FIXME: remove 150MB restriction
|
||||
return upload_simple(path, data: data, overwrite: true, operation: opType, completionHandler: completionHandler)
|
||||
return upload_simple(path, data: data, overwrite: overwrite, operation: opType, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
public func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
|
||||
@@ -348,32 +340,43 @@ extension DropboxFileProvider: ExtendedFileProvider {
|
||||
switch (path as NSString).pathExtension.lowercased() {
|
||||
case "jpg", "jpeg", "gif", "bmp", "png", "tif", "tiff":
|
||||
return true
|
||||
/*case "doc", "docx", "docm", "xls", "xlsx", "xlsm":
|
||||
case "doc", "docx", "docm", "xls", "xlsx", "xlsm":
|
||||
return true
|
||||
case "ppt", "pps", "ppsx", "ppsm", "pptx", "pptm":
|
||||
return true
|
||||
case "rtf":
|
||||
return true*/
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public func propertiesOfFileSupported(path: String) -> Bool {
|
||||
return false
|
||||
let fileExt = (path as NSString).pathExtension.lowercased()
|
||||
switch fileExt {
|
||||
case "jpg", "jpeg", "bmp", "gif", "png", "tif", "tiff":
|
||||
return true
|
||||
/*case "mp3", "aac", "m4a":
|
||||
return true*/
|
||||
case "mp4", "mpg", "3gp", "mov", "avi":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public func thumbnailOfFile(path: String, dimension: CGSize, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void)) {
|
||||
|
||||
/// Default value for dimension is 64x64, according to Dropbox documentation
|
||||
public func thumbnailOfFile(path: String, dimension: CGSize?, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void)) {
|
||||
let url: URL
|
||||
switch (path as NSString).pathExtension.lowercased() {
|
||||
case "jpg", "jpeg", "gif", "bmp", "png", "tif", "tiff":
|
||||
url = URL(string: "https://content.dropboxapi.com/2/files/get_thumbnail")!
|
||||
/*case "doc", "docx", "docm", "xls", "xlsx", "xlsm":
|
||||
case "doc", "docx", "docm", "xls", "xlsx", "xlsm":
|
||||
fallthrough
|
||||
case "ppt", "pps", "ppsx", "ppsm", "pptx", "pptm":
|
||||
fallthrough
|
||||
case "rtf":
|
||||
url = NSURL(string: "https://content.dropboxapi.com/2/files/get_preview")!*/
|
||||
url = URL(string: "https://content.dropboxapi.com/2/files/get_preview")!
|
||||
default:
|
||||
return
|
||||
}
|
||||
@@ -381,7 +384,9 @@ extension DropboxFileProvider: ExtendedFileProvider {
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
var requestDictionary = ["path": path as NSString]
|
||||
requestDictionary["format"] = "jpeg" as NSString
|
||||
requestDictionary["size"] = "w\(Int(dimension.width))h\(Int(dimension.height))" as NSString
|
||||
if let dimension = dimension {
|
||||
requestDictionary["size"] = "w\(Int(dimension.width))h\(Int(dimension.height))" as NSString
|
||||
}
|
||||
request.setValue(dictionaryToJSON(requestDictionary), forHTTPHeaderField: "Dropbox-API-Arg")
|
||||
let task = self.session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var image: ImageClass? = nil
|
||||
@@ -391,7 +396,13 @@ extension DropboxFileProvider: ExtendedFileProvider {
|
||||
}
|
||||
}
|
||||
if let data = data {
|
||||
image = ImageClass(data: data)
|
||||
if DropboxFileProvider.dataIsPDF(data) {
|
||||
image = DropboxFileProvider.convertToImage(pdfData: data)
|
||||
} 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 {
|
||||
image = ImageClass(data: data)
|
||||
}
|
||||
}
|
||||
completionHandler(image, error)
|
||||
})
|
||||
@@ -399,7 +410,27 @@ extension DropboxFileProvider: ExtendedFileProvider {
|
||||
}
|
||||
|
||||
public func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String : Any], _ keys: [String], _ error: Error?) -> Void)) {
|
||||
NotImplemented()
|
||||
let url = URL(string: "https://api.dropboxapi.com/2/files/get_metadata")!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
let requestDictionary = ["path": correctPath(path)! as NSString, "include_media_info": NSNumber(value: true)]
|
||||
request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: .utf8)
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var dbError: FileProviderDropboxError?
|
||||
var dic = [String: Any]()
|
||||
var keys = [String]()
|
||||
if let response = response as? HTTPURLResponse {
|
||||
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
|
||||
dbError = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8)) : nil
|
||||
if let data = data, let jsonStr = String(data: data, encoding: .utf8), let json = jsonToDictionary(jsonStr), let properties = json["media_info"] as? [String: Any] {
|
||||
(dic, keys) = self.mapMediaInfo(properties)
|
||||
}
|
||||
}
|
||||
completionHandler(dic, keys, dbError ?? error)
|
||||
})
|
||||
task.resume()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -99,19 +99,17 @@ internal extension DropboxFileProvider {
|
||||
|
||||
func upload_simple(_ targetPath: String, data: Data, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
assert(data.count < 150*1024*1024, "Maximum size of allowed size to upload is 150MB")
|
||||
var requestDictionary = [String: AnyObject]()
|
||||
var requestDictionary = [String: Any]()
|
||||
let url: URL
|
||||
url = URL(string: "https://content.dropboxapi.com/2/files/upload")!
|
||||
requestDictionary["path"] = correctPath(targetPath) as NSString?
|
||||
requestDictionary["mode"] = (overwrite ? "overwrite" : "add") as NSString
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssz"
|
||||
requestDictionary["client_modified"] = dateFormatter.string(from: modifiedDate) as NSString
|
||||
requestDictionary["client_modified"] = string(from:modifiedDate)
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
|
||||
request.setValue(dictionaryToJSON(requestDictionary), forHTTPHeaderField: "Dropbox-API-Arg")
|
||||
request.setValue(dictionaryToJSON(requestDictionary as [String : AnyObject]), forHTTPHeaderField: "Dropbox-API-Arg")
|
||||
request.httpBody = data
|
||||
let task = session.uploadTask(with: request, from: data, completionHandler: { (data, response, error) in
|
||||
var responseError: FileProviderDropboxError?
|
||||
@@ -120,7 +118,32 @@ internal extension DropboxFileProvider {
|
||||
}
|
||||
completionHandler?(responseError ?? error)
|
||||
self.delegateNotify(.create(path: targetPath), error: responseError ?? error)
|
||||
})
|
||||
})
|
||||
task.taskDescription = operation.json
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: operation, tasks: [task])
|
||||
}
|
||||
|
||||
func upload_simple(_ targetPath: String, localFile: URL, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
var requestDictionary = [String: Any]()
|
||||
let url: URL
|
||||
url = URL(string: "https://content.dropboxapi.com/2/files/upload")!
|
||||
requestDictionary["path"] = correctPath(targetPath) as NSString?
|
||||
requestDictionary["mode"] = (overwrite ? "overwrite" : "add") as NSString
|
||||
requestDictionary["client_modified"] = string(from:modifiedDate)
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
|
||||
request.setValue(dictionaryToJSON(requestDictionary as [String : AnyObject]), forHTTPHeaderField: "Dropbox-API-Arg")
|
||||
let task = session.uploadTask(with: request, fromFile: localFile, completionHandler: { (data, response, error) in
|
||||
var responseError: FileProviderDropboxError?
|
||||
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = FileProviderDropboxError(code: rCode, path: targetPath, errorDescription: String(data: data ?? Data(), encoding: .utf8))
|
||||
}
|
||||
completionHandler?(responseError ?? error)
|
||||
self.delegateNotify(.create(path: targetPath), error: responseError ?? error)
|
||||
})
|
||||
task.taskDescription = operation.json
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: operation, tasks: [task])
|
||||
@@ -180,13 +203,44 @@ internal extension DropboxFileProvider {
|
||||
fileObject.size = (json["size"] as? NSNumber)?.int64Value ?? -1
|
||||
fileObject.serverTime = resolve(dateString: json["server_modified"] as? String ?? "")
|
||||
fileObject.modifiedDate = resolve(dateString: json["client_modified"] as? String ?? "")
|
||||
fileObject.fileType = (json[".tag"] as? String) == "folder" ? .directory : .regular
|
||||
fileObject.type = (json[".tag"] as? String) == "folder" ? .directory : .regular
|
||||
fileObject.isReadOnly = (json["sharing_info"]?["read_only"] as? NSNumber)?.boolValue ?? false
|
||||
fileObject.id = json["id"] as? String
|
||||
fileObject.rev = json["id"] as? String
|
||||
return fileObject
|
||||
}
|
||||
|
||||
static let dateFormatter = DateFormatter()
|
||||
static let decimalFormatter = NumberFormatter()
|
||||
|
||||
func mapMediaInfo(_ json: [String: Any]) -> (dictionary: [String: Any], keys: [String]) {
|
||||
var dic = [String: Any]()
|
||||
var keys = [String]()
|
||||
if let dimensions = json["dimensions"] as? [String: Any], let height = dimensions["height"] as? UInt64, let width = dimensions["width"] as? UInt64 {
|
||||
keys.append("Dimensions")
|
||||
dic["Dimensions"] = "\(width)x\(height)"
|
||||
}
|
||||
if let location = json["location"] as? [String: Any], let latitude = location["latitude"] as? Double, let longitude = location["longitude"] as? Double {
|
||||
|
||||
DropboxFileProvider.decimalFormatter.numberStyle = .decimal
|
||||
DropboxFileProvider.decimalFormatter.maximumFractionDigits = 5
|
||||
keys.append("Location")
|
||||
let latStr = DropboxFileProvider.decimalFormatter.string(from: NSNumber(value: latitude))
|
||||
let longStr = DropboxFileProvider.decimalFormatter.string(from: NSNumber(value: longitude))
|
||||
dic["Location"] = "\(latStr), \(longStr)"
|
||||
}
|
||||
if let timeTakenStr = json["time_taken"] as? String, let timeTaken = self.resolve(dateString: timeTakenStr) {
|
||||
keys.append("Date taken")
|
||||
DropboxFileProvider.dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
||||
dic["Date taken"] = DropboxFileProvider.dateFormatter.string(from: timeTaken)
|
||||
}
|
||||
if let duration = json["duration"] as? UInt64 {
|
||||
keys.append("Duration")
|
||||
dic["Duration"] = DropboxFileProvider.formatshort(interval: TimeInterval(duration))
|
||||
}
|
||||
return (dic, keys)
|
||||
}
|
||||
|
||||
func delegateNotify(_ operation: FileOperationType, error: Error?) {
|
||||
DispatchQueue.main.async(execute: {
|
||||
if error == nil {
|
||||
|
||||
@@ -0,0 +1,444 @@
|
||||
//
|
||||
// ExtendedLocalFileProvider.swift
|
||||
// FileProvider
|
||||
//
|
||||
// Created by Amir Abbas Mousavian.
|
||||
// Copyright © 2017 Mousavian. Distributed under MIT license.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import ImageIO
|
||||
import CoreGraphics
|
||||
import AVFoundation
|
||||
import MediaPlayer
|
||||
#if os(iOS) || os(tvOS)
|
||||
import UIKit
|
||||
#elseif os(OSX)
|
||||
import Cocoa
|
||||
#endif
|
||||
|
||||
extension LocalFileProvider: ExtendedFileProvider {
|
||||
|
||||
public func thumbnailOfFileSupported(path: String) -> Bool {
|
||||
switch (path as NSString).pathExtension.lowercased() {
|
||||
case LocalFileInformationGenerator.imageThumbnailExtensions:
|
||||
return true
|
||||
case LocalFileInformationGenerator.audioThumbnailExtensions:
|
||||
return true
|
||||
case LocalFileInformationGenerator.videoThumbnailExtensions:
|
||||
return true
|
||||
case LocalFileInformationGenerator.pdfThumbnailExtensions:
|
||||
return true
|
||||
case LocalFileInformationGenerator.officeThumbnailExtensions:
|
||||
return true
|
||||
case LocalFileInformationGenerator.customThumbnailExtensions:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public func propertiesOfFileSupported(path: String) -> Bool {
|
||||
let fileExt = (path as NSString).pathExtension.lowercased()
|
||||
switch fileExt {
|
||||
case LocalFileInformationGenerator.imagePropertiesExtensions:
|
||||
return LocalFileInformationGenerator.imageProperties != nil
|
||||
case LocalFileInformationGenerator.audioPropertiesExtensions:
|
||||
return LocalFileInformationGenerator.audioProperties != nil
|
||||
case LocalFileInformationGenerator.videoPropertiesExtensions:
|
||||
return LocalFileInformationGenerator.videoProperties != nil
|
||||
case LocalFileInformationGenerator.pdfPropertiesExtensions:
|
||||
return LocalFileInformationGenerator.pdfProperties != nil
|
||||
case LocalFileInformationGenerator.archivePropertiesExtensions:
|
||||
return LocalFileInformationGenerator.archiveProperties != nil
|
||||
case LocalFileInformationGenerator.officePropertiesExtensions:
|
||||
return LocalFileInformationGenerator.officeProperties != nil
|
||||
case LocalFileInformationGenerator.customPropertiesExtensions:
|
||||
return LocalFileInformationGenerator.customProperties != nil
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public func thumbnailOfFile(path: String, dimension: CGSize? = nil, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void)) {
|
||||
(dispatch_queue).async {
|
||||
var thumbnailImage: ImageClass? = nil
|
||||
// Check cache
|
||||
let fileURL = self.absoluteURL(path)
|
||||
// Create Thumbnail and cache
|
||||
switch fileURL.pathExtension.lowercased() {
|
||||
case LocalFileInformationGenerator.videoThumbnailExtensions:
|
||||
thumbnailImage = LocalFileInformationGenerator.videoThumbnail(fileURL)
|
||||
case LocalFileInformationGenerator.audioThumbnailExtensions:
|
||||
thumbnailImage = LocalFileInformationGenerator.audioThumbnail(fileURL)
|
||||
case LocalFileInformationGenerator.imageThumbnailExtensions:
|
||||
thumbnailImage = LocalFileInformationGenerator.imageThumbnail(fileURL)
|
||||
case LocalFileInformationGenerator.pdfThumbnailExtensions:
|
||||
thumbnailImage = LocalFileInformationGenerator.pdfThumbnail(fileURL)
|
||||
case LocalFileInformationGenerator.officeThumbnailExtensions:
|
||||
thumbnailImage = LocalFileInformationGenerator.officeThumbnail(fileURL)
|
||||
case LocalFileInformationGenerator.customThumbnailExtensions:
|
||||
thumbnailImage = LocalFileInformationGenerator.customThumbnail(fileURL)
|
||||
default:
|
||||
completionHandler(nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if let image = thumbnailImage {
|
||||
let scaledImage = dimension != nil ? LocalFileProvider.scaleDown(image: image, toSize: dimension!) : image
|
||||
completionHandler(scaledImage, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String: Any], _ keys: [String], _ error: Error?) -> Void)) {
|
||||
(dispatch_queue).async {
|
||||
let fileExt = (path as NSString).pathExtension.lowercased()
|
||||
var getter: ((_ fileURL: URL) -> (prop: [String: Any], keys: [String]))?
|
||||
switch fileExt {
|
||||
case LocalFileInformationGenerator.imagePropertiesExtensions:
|
||||
getter = LocalFileInformationGenerator.imageProperties
|
||||
case LocalFileInformationGenerator.audioPropertiesExtensions:
|
||||
getter = LocalFileInformationGenerator.audioProperties
|
||||
case LocalFileInformationGenerator.videoPropertiesExtensions:
|
||||
getter = LocalFileInformationGenerator.videoProperties
|
||||
case LocalFileInformationGenerator.pdfPropertiesExtensions:
|
||||
getter = LocalFileInformationGenerator.pdfProperties
|
||||
case LocalFileInformationGenerator.archivePropertiesExtensions:
|
||||
getter = LocalFileInformationGenerator.archiveProperties
|
||||
case LocalFileInformationGenerator.officePropertiesExtensions:
|
||||
getter = LocalFileInformationGenerator.officeProperties
|
||||
case LocalFileInformationGenerator.customPropertiesExtensions:
|
||||
getter = LocalFileInformationGenerator.customProperties
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
var dic = [String: Any]()
|
||||
var keys = [String]()
|
||||
if let getterMethod = getter {
|
||||
(dic, keys) = getterMethod(self.absoluteURL(path))
|
||||
}
|
||||
|
||||
completionHandler(dic, keys, nil)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public struct LocalFileInformationGenerator {
|
||||
static public var imageThumbnailExtensions: [String] = ["jpg", "jpeg", "gif", "bmp", "png", "tif", "tiff", "ico"]
|
||||
static public var audioThumbnailExtensions: [String] = ["mp3", "aac", "m4a"]
|
||||
static public var videoThumbnailExtensions: [String] = ["mov", "mp4", "m4v", "mpg", "mpeg"]
|
||||
static public var pdfThumbnailExtensions: [String] = ["pdf"]
|
||||
static public var officeThumbnailExtensions: [String] = []
|
||||
static public var customThumbnailExtensions: [String] = []
|
||||
|
||||
static public var imagePropertiesExtensions: [String] = ["jpg", "jpeg", "bmp", "gif", "png", "tif", "tiff"]
|
||||
static public var audioPropertiesExtensions: [String] = ["mp3", "aac", "m4a", "caf"]
|
||||
static public var videoPropertiesExtensions: [String] = ["mp4", "mpg", "3gp", "mov", "avi"]
|
||||
static public var pdfPropertiesExtensions: [String] = ["pdf"]
|
||||
static public var archivePropertiesExtensions: [String] = []
|
||||
static public var officePropertiesExtensions: [String] = []
|
||||
static public var customPropertiesExtensions: [String] = []
|
||||
|
||||
static public var imageThumbnail: (_ fileURL: URL) -> ImageClass? = { fileURL in
|
||||
return ImageClass(contentsOfFile: fileURL.path)
|
||||
}
|
||||
|
||||
static public var audioThumbnail: (_ fileURL: URL) -> ImageClass? = { fileURL in
|
||||
let playerItem = AVPlayerItem(url: fileURL)
|
||||
let metadataList = playerItem.asset.commonMetadata
|
||||
for item in metadataList {
|
||||
if item.commonKey == AVMetadataCommonKeyArtwork {
|
||||
if let data = item.dataValue {
|
||||
return ImageClass(data: data)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
static public var videoThumbnail: (_ fileURL: URL) -> ImageClass? = { fileURL in
|
||||
let asset = AVAsset(url: fileURL)
|
||||
let assetImgGenerate = AVAssetImageGenerator(asset: asset)
|
||||
assetImgGenerate.appliesPreferredTrackTransform = true
|
||||
let time = CMTimeMake(asset.duration.value / 3, asset.duration.timescale)
|
||||
if let cgImage = try? assetImgGenerate.copyCGImage(at: time, actualTime: nil) {
|
||||
#if os(OSX)
|
||||
return ImageClass(cgImage: cgImage, size: NSSize.zero)
|
||||
#else
|
||||
return ImageClass(cgImage: cgImage)
|
||||
#endif
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
static public var pdfThumbnail: (_ fileURL: URL) -> ImageClass? = { fileURL in
|
||||
guard let data = try? Data(contentsOf: fileURL) else { return nil }
|
||||
return LocalFileProvider.convertToImage(pdfData: data)
|
||||
}
|
||||
|
||||
static public var officeThumbnail: (_ fileURL: URL) -> ImageClass? = { fileURL in
|
||||
return nil
|
||||
}
|
||||
|
||||
static public var customThumbnail: (_ fileURL: URL) -> ImageClass? = { fileURL in
|
||||
return nil
|
||||
}
|
||||
|
||||
static public var imageProperties: ((_ fileURL: URL) -> (prop: [String: Any], keys: [String]))? = { fileURL in
|
||||
func simplify(_ top:Int64, _ bottom:Int64) -> (newTop:Int, newBottom:Int) {
|
||||
var x = top
|
||||
var y = bottom
|
||||
while (y != 0) {
|
||||
let buffer = y
|
||||
y = x % y
|
||||
x = buffer
|
||||
}
|
||||
let hcfVal = x
|
||||
let newTopVal = top/hcfVal
|
||||
let newBottomVal = bottom/hcfVal
|
||||
return(Int(newTopVal), Int(newBottomVal))
|
||||
}
|
||||
|
||||
var dic = [String: Any]()
|
||||
var keys = [String]()
|
||||
guard let cgDataRef = CGImageSourceCreateWithURL(fileURL as CFURL, nil), let cfImageDict = CGImageSourceCopyPropertiesAtIndex(cgDataRef, 0, nil) else {
|
||||
return (dic, keys)
|
||||
}
|
||||
let imageDict = cfImageDict as NSDictionary
|
||||
let tiffDict = imageDict[kCGImagePropertyTIFFDictionary as String] as? [String : AnyObject] ?? [:]
|
||||
let exifDict = imageDict[kCGImagePropertyExifDictionary as String] as? [String : AnyObject] ?? [:]
|
||||
if let pixelWidth: AnyObject = imageDict.object(forKey: kCGImagePropertyPixelWidth) as? NSNumber, let pixelHeight: AnyObject = imageDict.object(forKey: kCGImagePropertyPixelHeight) as? NSNumber {
|
||||
keys.append("Dimensions")
|
||||
dic["Dimensions"] = "\(pixelWidth)x\(pixelHeight)"
|
||||
}
|
||||
if let dpi = imageDict[kCGImagePropertyDPIWidth as String] {
|
||||
keys.append("DPI")
|
||||
dic["DPI"] = dpi
|
||||
}
|
||||
if let devicemake = tiffDict[kCGImagePropertyTIFFMake as String] {
|
||||
keys.append("Device make")
|
||||
dic["Device make"] = devicemake
|
||||
}
|
||||
if let devicemodel = tiffDict[kCGImagePropertyTIFFModel as String] {
|
||||
keys.append("Device model")
|
||||
dic["Device model"] = devicemodel
|
||||
}
|
||||
if let lensmodel = exifDict[kCGImagePropertyExifLensModel as String] {
|
||||
keys.append("Lens model")
|
||||
dic["Lens model"] = lensmodel
|
||||
}
|
||||
if let artist = tiffDict[kCGImagePropertyTIFFArtist as String] as? String , !artist.isEmpty {
|
||||
keys.append("Artist")
|
||||
dic["Artist"] = artist
|
||||
}
|
||||
if let cr = tiffDict[kCGImagePropertyTIFFCopyright as String] as? String , !cr.isEmpty {
|
||||
keys.append("Copyright")
|
||||
dic["Copyright"] = cr
|
||||
}
|
||||
if let date = tiffDict[kCGImagePropertyTIFFDateTime as String] as? String , !date.isEmpty {
|
||||
keys.append("Date taken")
|
||||
dic["Date taken"] = date
|
||||
}
|
||||
if let latitude = tiffDict[kCGImagePropertyGPSLatitude as String]?.doubleValue, let longitude = tiffDict[kCGImagePropertyGPSLongitude as String]?.doubleValue {
|
||||
keys.append("Location")
|
||||
dic["Location"] = "\(latitude), \(longitude)"
|
||||
}
|
||||
if let colorspace = imageDict[kCGImagePropertyColorModel as String] {
|
||||
keys.append("Color space")
|
||||
dic["Color space"] = colorspace
|
||||
}
|
||||
if let focallen = exifDict[kCGImagePropertyExifFocalLength as String] {
|
||||
keys.append("Focal length")
|
||||
dic["Focal length"] = focallen
|
||||
}
|
||||
if let fnum = exifDict[kCGImagePropertyExifFNumber as String] {
|
||||
keys.append("F number")
|
||||
dic["F number"] = fnum
|
||||
}
|
||||
if let expprog = exifDict[kCGImagePropertyExifExposureProgram as String] {
|
||||
keys.append("Exposure program")
|
||||
dic["Exposure program"] = expprog
|
||||
}
|
||||
if let exp = exifDict[kCGImagePropertyExifExposureTime as String]?.doubleValue {
|
||||
let expfrac = simplify(Int64(exp * 10_000_000_000_000), 10_000_000_000_000)
|
||||
keys.append("Exposure time")
|
||||
dic["Exposure time"] = "\(expfrac.newTop)/\(expfrac.newBottom)"
|
||||
}
|
||||
if let iso = exifDict[kCGImagePropertyExifISOSpeedRatings as String] as? NSArray , iso.count > 0 {
|
||||
keys.append("ISO speed")
|
||||
dic["ISO speed"] = iso[0]
|
||||
}
|
||||
return (dic, keys)
|
||||
}
|
||||
|
||||
static var audioProperties: ((_ fileURL: URL) -> (prop: [String: Any], keys: [String]))? = { fileURL in
|
||||
func makeDescription(_ key: String?) -> String? {
|
||||
guard let key = key else {
|
||||
return nil
|
||||
}
|
||||
guard let regex = try? NSRegularExpression(pattern: "([a-z])([A-Z])" , options: NSRegularExpression.Options()) else {
|
||||
return nil
|
||||
}
|
||||
let newKey = regex.stringByReplacingMatches(in: key, options: NSRegularExpression.MatchingOptions(), range: NSMakeRange(0, (key as NSString).length) , withTemplate: "$1 $2")
|
||||
return newKey.capitalized
|
||||
}
|
||||
|
||||
var dic = [String: Any]()
|
||||
var keys = [String]()
|
||||
if FileManager.default.fileExists(atPath: fileURL.path) {
|
||||
let playerItem = AVPlayerItem(url: fileURL)
|
||||
let metadataList = playerItem.asset.commonMetadata
|
||||
for item in metadataList {
|
||||
if let description = makeDescription(item.commonKey) {
|
||||
if let value = item.stringValue {
|
||||
keys.append(description)
|
||||
dic[description] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
if let ap = try? AVAudioPlayer(contentsOf: fileURL) {
|
||||
keys.append("Duration")
|
||||
dic["Duration"] = LocalFileProvider.formatshort(interval: ap.duration)
|
||||
if let bitRate = ap.settings[AVSampleRateKey] as? Int {
|
||||
keys.append("Bitrate")
|
||||
dic["Bitrate"] = bitRate
|
||||
}
|
||||
}
|
||||
}
|
||||
return (dic, keys)
|
||||
}
|
||||
|
||||
static public var videoProperties: ((_ fileURL: URL) -> (prop: [String: Any], keys: [String]))? = { fileURL in
|
||||
var dic = [String: Any]()
|
||||
var keys = [String]()
|
||||
if let audioprops = LocalFileInformationGenerator.audioProperties?(fileURL) {
|
||||
dic = audioprops.prop
|
||||
keys = audioprops.keys
|
||||
dic.removeValue(forKey: "Duration")
|
||||
if let index = keys.index(of: "Duration") {
|
||||
keys.remove(at: index)
|
||||
}
|
||||
}
|
||||
let asset = AVURLAsset(url: fileURL, options: nil)
|
||||
let videoTracks = asset.tracks(withMediaType: AVMediaTypeVideo)
|
||||
if videoTracks.count > 0 {
|
||||
var bitrate: Float = 0
|
||||
let width = Int(videoTracks[0].naturalSize.width)
|
||||
let height = Int(videoTracks[0].naturalSize.height)
|
||||
keys.append("Dimensions")
|
||||
dic["Dimensions"] = "\(width)x\(height)"
|
||||
var duration: Int64 = 0
|
||||
for track in videoTracks {
|
||||
duration += track.timeRange.duration.timescale > 0 ? track.timeRange.duration.value / Int64(track.timeRange.duration.timescale) : 0
|
||||
bitrate += track.estimatedDataRate
|
||||
}
|
||||
keys.append("Duration")
|
||||
dic["Duration"] = LocalFileProvider.formatshort(interval: TimeInterval(duration))
|
||||
keys.append("Video Bitrate")
|
||||
dic["Video Bitrate"] = "\(Int(ceil(bitrate / 1000))) kbps"
|
||||
}
|
||||
let audioTracks = asset.tracks(withMediaType: AVMediaTypeAudio)
|
||||
// dic["Audio channels"] = audioTracks.count
|
||||
var bitrate: Float = 0
|
||||
for track in audioTracks {
|
||||
bitrate += track.estimatedDataRate
|
||||
}
|
||||
keys.append("Audio Bitrate")
|
||||
dic["Audio Bitrate"] = "\(Int(ceil(bitrate / 1000))) kbps"
|
||||
return (dic, keys)
|
||||
}
|
||||
|
||||
static public var pdfProperties: ((_ fileURL: URL) -> (prop: [String: Any], keys: [String]))? = { fileURL in
|
||||
func getKey(_ key: String, from dict: CGPDFDictionaryRef) -> String? {
|
||||
var cfValue: CGPDFStringRef? = nil
|
||||
if (CGPDFDictionaryGetString(dict, key, &cfValue)), let value = CGPDFStringCopyTextString(cfValue!) {
|
||||
return value as String
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertDate(_ date: String) -> Date? {
|
||||
var dateStr = date
|
||||
if dateStr.hasPrefix("D:") {
|
||||
dateStr = date.substring(from: date.characters.index(date.startIndex, offsetBy: 2))
|
||||
}
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "yyyyMMddHHmmssTZD"
|
||||
if let result = dateFormatter.date(from: dateStr) {
|
||||
return result
|
||||
}
|
||||
dateFormatter.dateFormat = "yyyyMMddHHmmss"
|
||||
if let result = dateFormatter.date(from: dateStr) {
|
||||
return result
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var dic = [String: Any]()
|
||||
var keys = [String]()
|
||||
if let data = try? Data(contentsOf: fileURL), let provider = CGDataProvider(data: data as CFData), let reference = CGPDFDocument(provider), let dict = reference.info {
|
||||
if let title = getKey("Title", from: dict), !title.isEmpty {
|
||||
keys.append("Title")
|
||||
dic["Title"] = title
|
||||
}
|
||||
if let author = getKey("Author", from: dict), !author.isEmpty {
|
||||
keys.append("Author")
|
||||
dic["Author"] = author
|
||||
}
|
||||
if let subject = getKey("Subject", from: dict), !subject.isEmpty {
|
||||
keys.append("Subject")
|
||||
dic["Subject"] = subject
|
||||
}
|
||||
var majorVersion: Int32 = 0
|
||||
var minorVersion: Int32 = 0
|
||||
reference.getVersion(majorVersion: &majorVersion, minorVersion: &minorVersion)
|
||||
if majorVersion > 0 {
|
||||
keys.append("Version")
|
||||
dic["Version"] = String(majorVersion) + "." + String(minorVersion)
|
||||
}
|
||||
if reference.numberOfPages > 0 {
|
||||
keys.append("Pages")
|
||||
dic["Pages"] = reference.numberOfPages
|
||||
}
|
||||
|
||||
if reference.numberOfPages > 0, let pageRef = reference.page(at: 1) {
|
||||
let size = pageRef.getBoxRect(CGPDFBox.mediaBox).size
|
||||
keys.append("Resolution")
|
||||
dic["Resolution"] = "\(Int(size.width))x\(Int(size.height))"
|
||||
}
|
||||
if let creator = getKey("Creator", from: dict), !creator.isEmpty {
|
||||
keys.append("Content creator")
|
||||
dic["Content creator"] = creator
|
||||
}
|
||||
if let creationDateString = getKey("CreationDate", from: dict), let creationDate = convertDate(creationDateString) {
|
||||
keys.append("Creation date")
|
||||
dic["Creation date"] = creationDate
|
||||
}
|
||||
if let modifiedDateString = getKey("ModDate", from: dict), let modDate = convertDate(modifiedDateString) {
|
||||
keys.append("Modified date")
|
||||
dic["Modified date"] = modDate
|
||||
}
|
||||
keys.append("Security")
|
||||
dic["Security"] = reference.isEncrypted ? "Present" : "None"
|
||||
keys.append("Allows printing")
|
||||
dic["Allows printing"] = reference.allowsPrinting ? "Yes" : "No"
|
||||
keys.append("Allows copying")
|
||||
dic["Allows copying"] = reference.allowsCopying ? "Yes" : "No"
|
||||
}
|
||||
return (dic, keys)
|
||||
}
|
||||
|
||||
static public var archiveProperties: ((_ fileURL: URL) -> (prop: [String: Any], keys: [String]))? = nil
|
||||
|
||||
static public var officeProperties: ((_ fileURL: URL) -> (prop: [String: Any], keys: [String]))? = nil
|
||||
|
||||
static public var customProperties: ((_ fileURL: URL) -> (prop: [String: Any], keys: [String]))? = nil
|
||||
}
|
||||
|
||||
func ~=<T : Equatable>(array: [T], value: T) -> Bool {
|
||||
return array.contains(value)
|
||||
}
|
||||
@@ -401,8 +401,8 @@ open class FPSStreamTask: URLSessionTask, StreamDelegate {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
_underlyingTask!.startSecureConnection()
|
||||
} else {
|
||||
inputStream!.setProperty(StreamSocketSecurityLevel.negotiatedSSL.rawValue, forKey: Stream.PropertyKey.socketSecurityLevelKey)
|
||||
outputStream!.setProperty(StreamSocketSecurityLevel.negotiatedSSL.rawValue, forKey: Stream.PropertyKey.socketSecurityLevelKey)
|
||||
inputStream!.setProperty(StreamSocketSecurityLevel.negotiatedSSL.rawValue, forKey: .socketSecurityLevelKey)
|
||||
outputStream!.setProperty(StreamSocketSecurityLevel.negotiatedSSL.rawValue, forKey: .socketSecurityLevelKey)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,8 +414,8 @@ open class FPSStreamTask: URLSessionTask, StreamDelegate {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
_underlyingTask!.stopSecureConnection()
|
||||
} else {
|
||||
inputStream!.setProperty(StreamSocketSecurityLevel.none.rawValue, forKey: Stream.PropertyKey.socketSecurityLevelKey)
|
||||
outputStream!.setProperty(StreamSocketSecurityLevel.none.rawValue, forKey: Stream.PropertyKey.socketSecurityLevelKey)
|
||||
inputStream!.setProperty(StreamSocketSecurityLevel.none.rawValue, forKey: .socketSecurityLevelKey)
|
||||
outputStream!.setProperty(StreamSocketSecurityLevel.none.rawValue, forKey: .socketSecurityLevelKey)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -503,21 +503,7 @@ public protocol FPSStreamDelegate : URLSessionTaskDelegate {
|
||||
@objc optional func urlSession(_ session: URLSession, streamTask: FPSStreamTask, didBecome inputStream: InputStream, outputStream: OutputStream)
|
||||
}
|
||||
|
||||
private let ports = ["http": 80,
|
||||
"https": 443,
|
||||
"smb": 445,
|
||||
"ftp": 21,
|
||||
"sftp": 22,
|
||||
"sftp": 2121,
|
||||
"telnet": 23,
|
||||
"pop": 110,
|
||||
"smtp": 25,
|
||||
"imap": 143]
|
||||
private let securePorts = ["https": 443,
|
||||
"smb": 445,
|
||||
"sftp": 22,
|
||||
"sftp": 2121,
|
||||
"telnet": 992,
|
||||
"pop": 995,
|
||||
"smtp": 465,
|
||||
"imap": 993]
|
||||
private let ports: [String: Int] = ["http": 80, "https": 443, "smb": 445,"ftp": 21,"ftps": 22, "sftp": 2121,
|
||||
"telnet": 23, "pop": 110, "smtp": 25, "imap": 143]
|
||||
private let securePorts: [String: Int] = ["https": 443, "smb": 445, "ftps": 22, "sftp": 2121,
|
||||
"telnet": 992, "pop": 995, "smtp": 465, "imap": 993]
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
//
|
||||
// FileProvider.swift
|
||||
// FileProvider
|
||||
//
|
||||
// Created by Amir Abbas Mousavian.
|
||||
// Copyright © 2016 Mousavian. Distributed under MIT license.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Containts path and attributes of a file or resource.
|
||||
open class FileObject {
|
||||
open internal(set) var allValues: [String: Any]
|
||||
|
||||
internal init(allValues: [String: Any]) {
|
||||
self.allValues = allValues
|
||||
}
|
||||
|
||||
internal init(absoluteURL: URL? = nil, name: String, path: String) {
|
||||
self.allValues = [String: Any]()
|
||||
self.absoluteURL = absoluteURL
|
||||
self.name = name
|
||||
self.path = path
|
||||
}
|
||||
|
||||
/// url to access the resource, not supported by Dropbox provider
|
||||
open internal(set) var absoluteURL: URL? {
|
||||
get {
|
||||
return allValues["NSURLAbsoluteURLKey"] as? URL
|
||||
}
|
||||
set {
|
||||
allValues["NSURLAbsoluteURLKey"] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Name of the file, usually equals with the last path component
|
||||
open internal(set) var name: String {
|
||||
get {
|
||||
return allValues[URLResourceKey.nameKey.rawValue] as! String
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.nameKey.rawValue] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Relative path of file object
|
||||
open internal(set) var path: String {
|
||||
get {
|
||||
return allValues[URLResourceKey.pathKey.rawValue] as! String
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.pathKey.rawValue] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Size of file on disk, return -1 for directories.
|
||||
open internal(set) var size: Int64 {
|
||||
get {
|
||||
return allValues[URLResourceKey.fileSizeKey.rawValue] as? Int64 ?? -1
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.fileSizeKey.rawValue] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// The time contents of file has been created, returns nil if not set
|
||||
open internal(set) var creationDate: Date? {
|
||||
get {
|
||||
return allValues[URLResourceKey.creationDateKey.rawValue] as? Date
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.creationDateKey.rawValue] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// The time contents of file has been modified, returns nil if not set
|
||||
open internal(set) var modifiedDate: Date? {
|
||||
get {
|
||||
return allValues[URLResourceKey.contentModificationDateKey.rawValue] as? Date
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.contentModificationDateKey.rawValue] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// return resource type of file, usually directory, regular or symLink
|
||||
open internal(set) var type: URLFileResourceType? {
|
||||
get {
|
||||
return allValues[URLResourceKey.fileResourceTypeKey.rawValue] as? URLFileResourceType
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.fileResourceTypeKey.rawValue] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, deprecated, message: "Use FileObject.type property instead.")
|
||||
open var fileType: URLFileResourceType? {
|
||||
return self.type
|
||||
}
|
||||
|
||||
/// File is hidden either because begining with dot or filesystem flags
|
||||
/// Setting this value on a file begining with dot has no effect
|
||||
open internal(set) var isHidden: Bool {
|
||||
get {
|
||||
return allValues[URLResourceKey.isHiddenKey.rawValue] as? Bool ?? false
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.isHiddenKey.rawValue] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// File can not be written
|
||||
open internal(set) var isReadOnly: Bool {
|
||||
get {
|
||||
return !(allValues[URLResourceKey.isWritableKey.rawValue] as? Bool ?? true)
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.isWritableKey.rawValue] = !newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// File is a Directory
|
||||
open var isDirectory: Bool {
|
||||
return self.type == .directory
|
||||
}
|
||||
|
||||
/// File is a normal file
|
||||
open var isRegularFile: Bool {
|
||||
return self.type == .regular
|
||||
}
|
||||
|
||||
/// File is a Symbolic link
|
||||
open var isSymLink: Bool {
|
||||
return self.type == .symbolicLink
|
||||
}
|
||||
}
|
||||
|
||||
/// Sorting FileObject array by given criteria, not thread-safe
|
||||
public struct FileObjectSorting {
|
||||
|
||||
/// Determines sort kind by which item of File object
|
||||
public enum SortType {
|
||||
/// Sorting by default Finder (case-insensitive) behavior
|
||||
case name
|
||||
/// Sorting by case-sensitive form of file name
|
||||
case nameCaseSensitive
|
||||
/// Sorting by case-in sensitive form of file name
|
||||
case nameCaseInsensitive
|
||||
/// Sorting by file type
|
||||
case `extension`
|
||||
/// Sorting by file modified date
|
||||
case modifiedDate
|
||||
/// Sorting by file creation date
|
||||
case creationDate
|
||||
/// Sorting by file modified date
|
||||
case size
|
||||
|
||||
/// all sort types
|
||||
static var allItems: [SortType] {
|
||||
return [.name, .nameCaseSensitive, .nameCaseInsensitive, .extension,
|
||||
.modifiedDate,.creationDate, .size]
|
||||
}
|
||||
}
|
||||
|
||||
public let sortType: SortType
|
||||
/// puts A before Z, default is true
|
||||
public let ascending: Bool
|
||||
/// puts directories on top, regardless of other attributes, default is false
|
||||
public let isDirectoriesFirst: Bool
|
||||
|
||||
public static let nameAscending = FileObjectSorting(type: .name, ascending: true)
|
||||
public static let nameDesceding = FileObjectSorting(type: .name, ascending: false)
|
||||
public static let sizeAscending = FileObjectSorting(type: .size, ascending: true)
|
||||
public static let sizeDesceding = FileObjectSorting(type: .size, ascending: false)
|
||||
public static let extensionAscending = FileObjectSorting(type: .extension, ascending: true)
|
||||
public static let extensionDesceding = FileObjectSorting(type: .extension, ascending: false)
|
||||
public static let modifiedAscending = FileObjectSorting(type: .modifiedDate, ascending: true)
|
||||
public static let modifiedDesceding = FileObjectSorting(type: .modifiedDate, ascending: false)
|
||||
public static let createdAscending = FileObjectSorting(type: .creationDate, ascending: true)
|
||||
public static let createdDesceding = FileObjectSorting(type: .creationDate, ascending: false)
|
||||
|
||||
public init (type: SortType, ascending: Bool = true, isDirectoriesFirst: Bool = false) {
|
||||
self.sortType = type
|
||||
self.ascending = ascending
|
||||
self.isDirectoriesFirst = isDirectoriesFirst
|
||||
}
|
||||
|
||||
/// Sorts array of FileObjects by criterias set in properties
|
||||
public func sort(_ files: [FileObject]) -> [FileObject] {
|
||||
return files.sorted {
|
||||
if isDirectoriesFirst {
|
||||
if ($0.isDirectory) && !($1.isDirectory) {
|
||||
return true
|
||||
}
|
||||
if !($0.isDirectory) && ($1.isDirectory) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
switch sortType {
|
||||
case .name:
|
||||
return ($0.name).localizedStandardCompare($1.name) == (ascending ? .orderedAscending : .orderedDescending)
|
||||
case .nameCaseSensitive:
|
||||
return ($0.name).localizedCompare($1.name) == (ascending ? .orderedAscending : .orderedDescending)
|
||||
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
|
||||
return kind1.localizedCaseInsensitiveCompare(kind2) == (ascending ? .orderedAscending : .orderedDescending)
|
||||
case .modifiedDate:
|
||||
let fileMod1 = $0.modifiedDate ?? Date.distantPast
|
||||
let fileMod2 = $1.modifiedDate ?? Date.distantPast
|
||||
return ascending ? fileMod1 < fileMod2 : fileMod1 > fileMod2
|
||||
case .creationDate:
|
||||
let fileCreation1 = $0.creationDate ?? Date.distantPast
|
||||
let fileCreation2 = $1.creationDate ?? Date.distantPast
|
||||
return ascending ? fileCreation1 < fileCreation2 : fileCreation1 > fileCreation2
|
||||
case .size:
|
||||
return ascending ? $0.size < $1.size : $0.size > $1.size
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension URLFileResourceType {
|
||||
public init(fileTypeValue: FileAttributeType) {
|
||||
switch fileTypeValue {
|
||||
case FileAttributeType.typeCharacterSpecial: self = .characterSpecial
|
||||
case FileAttributeType.typeDirectory: self = .directory
|
||||
case FileAttributeType.typeBlockSpecial: self = .blockSpecial
|
||||
case FileAttributeType.typeRegular: self = .regular
|
||||
case FileAttributeType.typeSymbolicLink: self = .symbolicLink
|
||||
case FileAttributeType.typeSocket: self = .socket
|
||||
case FileAttributeType.typeUnknown: self = .unknown
|
||||
default: self = .unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal extension URL {
|
||||
var uw_scheme: String {
|
||||
return self.scheme ?? ""
|
||||
}
|
||||
}
|
||||
|
||||
internal func jsonToDictionary(_ jsonString: String) -> [String: AnyObject]? {
|
||||
guard let data = jsonString.data(using: .utf8) else {
|
||||
return nil
|
||||
}
|
||||
if let dic = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions()) as? [String: AnyObject] {
|
||||
return dic
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
internal func dictionaryToJSON(_ dictionary: [String: AnyObject]) -> String? {
|
||||
if let data = try? JSONSerialization.data(withJSONObject: dictionary, options: JSONSerialization.WritingOptions()) {
|
||||
return String(data: data, encoding: .utf8)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -5,6 +5,9 @@
|
||||
// Created by Amir Abbas Mousavian on 5/6/95.
|
||||
//
|
||||
//
|
||||
|
||||
#import <TargetConditionals.h>
|
||||
|
||||
#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
|
||||
#import <UIKit/UIKit.h>
|
||||
//! Project version number for FileProvider iOS.
|
||||
|
||||
+188
-153
@@ -98,29 +98,80 @@ public protocol FileProviderOperations: FileProviderBasic {
|
||||
@discardableResult
|
||||
func create(file: String, at: String, contents data: Data?, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
@discardableResult
|
||||
func moveItem(path: String, to: String, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
@discardableResult
|
||||
func moveItem(path: String, to: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
@discardableResult
|
||||
func copyItem(path: String, to: String, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
@discardableResult
|
||||
func copyItem(path: String, to: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
@discardableResult
|
||||
func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
|
||||
@discardableResult
|
||||
func copyItem(localFile: URL, to: String, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
@discardableResult
|
||||
func copyItem(localFile: URL, to: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
@discardableResult
|
||||
func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
}
|
||||
|
||||
extension FileProviderOperations {
|
||||
@discardableResult
|
||||
public func moveItem(path: String, to: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
return self.moveItem(path: path, to: to, overwrite: false, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func copyItem(localFile: URL, to: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
return self.copyItem(localFile: localFile, to: to, overwrite: false, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func copyItem(path: String, to: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
return self.copyItem(path: path, to: to, overwrite: false, completionHandler: completionHandler)
|
||||
}
|
||||
}
|
||||
|
||||
public protocol FileProviderReadWrite: FileProviderBasic {
|
||||
@discardableResult
|
||||
func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle?
|
||||
@discardableResult
|
||||
func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle?
|
||||
|
||||
@discardableResult
|
||||
func writeContents(path: String, contents: Data, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
@discardableResult
|
||||
func writeContents(path: String, contents: Data, atomically: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
@discardableResult
|
||||
func writeContents(path: String, contents: Data, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
@discardableResult
|
||||
func writeContents(path: String, contents: Data, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
|
||||
func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void))
|
||||
}
|
||||
|
||||
extension FileProviderReadWrite {
|
||||
@discardableResult
|
||||
public func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle?{
|
||||
return self.contents(path: path, offset: 0, length: -1, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func writeContents(path: String, contents: Data, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
return self.writeContents(path: path, contents: contents, atomically: false, overwrite: false, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func writeContents(path: String, contents: Data, atomically: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
return self.writeContents(path: path, contents: contents, atomically: atomically, overwrite: false, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func writeContents(path: String, contents: Data, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
return self.writeContents(path: path, contents: contents, atomically: false, overwrite: overwrite, completionHandler: completionHandler)
|
||||
}
|
||||
}
|
||||
|
||||
public protocol FileProviderMonitor: FileProviderBasic {
|
||||
func registerNotifcation(path: String, eventHandler: @escaping (() -> Void))
|
||||
func unregisterNotifcation(path: String)
|
||||
@@ -168,7 +219,7 @@ extension FileProviderBasic {
|
||||
guard let path = path else { return nil }
|
||||
var p = path.hasPrefix("/") ? path : "/" + path
|
||||
if p.hasSuffix("/") {
|
||||
p.remove(at: p.endIndex)
|
||||
p.remove(at: p.index(before:p.endIndex))
|
||||
}
|
||||
return p
|
||||
}
|
||||
@@ -246,15 +297,148 @@ extension FileProviderBasic {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public func string(from date:Date) -> String {
|
||||
let fm = DateFormatter()
|
||||
fm.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"
|
||||
fm.timeZone = TimeZone(identifier:"UTC")
|
||||
fm.locale = Locale(identifier:"en_US_POSIX")
|
||||
return fm.string(from:date)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public protocol ExtendedFileProvider: FileProvider {
|
||||
public protocol ExtendedFileProvider: FileProviderBasic {
|
||||
func thumbnailOfFileSupported(path: String) -> Bool
|
||||
func propertiesOfFileSupported(path: String) -> Bool
|
||||
func thumbnailOfFile(path: String, dimension: CGSize, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void))
|
||||
func thumbnailOfFile(path: String, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void))
|
||||
func thumbnailOfFile(path: String, dimension: CGSize?, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void))
|
||||
func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String: Any], _ keys: [String], _ error: Error?) -> Void))
|
||||
}
|
||||
|
||||
extension ExtendedFileProvider {
|
||||
public func thumbnailOfFile(path: String, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void)) {
|
||||
self.thumbnailOfFile(path: path, dimension: nil, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
internal static func formatshort(interval: TimeInterval) -> String {
|
||||
var result = "0:00"
|
||||
if interval < TimeInterval(Int32.max) {
|
||||
result = ""
|
||||
var time = DateComponents()
|
||||
time.hour = Int(interval / 3600)
|
||||
time.minute = Int((interval.truncatingRemainder(dividingBy: 3600)) / 60)
|
||||
time.second = Int(interval.truncatingRemainder(dividingBy: 60))
|
||||
let formatter = NumberFormatter()
|
||||
formatter.paddingCharacter = "0"
|
||||
formatter.minimumIntegerDigits = 2
|
||||
formatter.maximumFractionDigits = 0
|
||||
let formatterFirst = NumberFormatter()
|
||||
formatterFirst.maximumFractionDigits = 0
|
||||
if time.hour! > 0 {
|
||||
result = "\(formatterFirst.string(from: NSNumber(value: time.hour!))!):\(formatter.string(from: NSNumber(value: time.minute!))!):\(formatter.string(from: NSNumber(value: time.second!))!)"
|
||||
} else {
|
||||
result = "\(formatterFirst.string(from: NSNumber(value: time.minute!))!):\(formatter.string(from: NSNumber(value: time.second!))!)"
|
||||
}
|
||||
}
|
||||
result = result.trimmingCharacters(in: CharacterSet(charactersIn: ": "))
|
||||
return result
|
||||
}
|
||||
|
||||
internal static func dataIsPDF(_ data: Data) -> Bool {
|
||||
return data.count > 4 && data.scanString(length: 4, encoding: .ascii) == "%PDF"
|
||||
}
|
||||
|
||||
internal static func convertToImage(pdfData: Data?) -> 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: 1) {
|
||||
let frame = pageRef.getBoxRect(CGPDFBox.mediaBox)
|
||||
var size = frame.size
|
||||
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
|
||||
|
||||
#if os(OSX)
|
||||
let ppp = Int(NSScreen.main()?.backingScaleFactor ?? 1) // fetch device is retina or not
|
||||
|
||||
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: NSCalibratedRGBColorSpace,
|
||||
bytesPerRow: 0, bitsPerPixel: 0)
|
||||
|
||||
guard let context = NSGraphicsContext(bitmapImageRep: rep!) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
NSGraphicsContext.saveGraphicsState()
|
||||
NSGraphicsContext.setCurrent(context)
|
||||
|
||||
let transform = pageRef.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(pageRef)
|
||||
|
||||
let resultingImage = NSImage(size: size)
|
||||
resultingImage.addRepresentation(rep!)
|
||||
return resultingImage
|
||||
#else
|
||||
let ppp = Int(UIScreen.main.scale) // fetch device is retina or not
|
||||
guard let context = UIGraphicsGetCurrentContext() else {
|
||||
return nil
|
||||
}
|
||||
size.width *= CGFloat(ppp)
|
||||
size.height *= CGFloat(ppp)
|
||||
UIGraphicsBeginImageContext(size)
|
||||
|
||||
context.saveGState()
|
||||
let transform = pageRef.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.drawPDFPage(pageRef)
|
||||
|
||||
context.restoreGState()
|
||||
let resultingImage = UIGraphicsGetImageFromCurrentImageContext()
|
||||
UIGraphicsEndImageContext()
|
||||
return resultingImage
|
||||
#endif
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
} else {
|
||||
height = maxSize.height
|
||||
width = (image.size.width / image.size.height) * height
|
||||
}
|
||||
|
||||
let newSize = CGSize(width: width, height: height)
|
||||
|
||||
#if os(OSX)
|
||||
var imageRect = NSRect(origin: CGPoint.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)
|
||||
#else
|
||||
UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0)
|
||||
image.draw(in: CGRect(origin: CGPoint.zero, size: newSize))
|
||||
let newImage = UIGraphicsGetImageFromCurrentImageContext() ?? image
|
||||
UIGraphicsEndImageContext()
|
||||
return newImage
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public enum FileOperationType: CustomStringConvertible {
|
||||
case create (path: String)
|
||||
case copy (source: String, destination: String)
|
||||
@@ -300,116 +484,6 @@ public enum FileOperationType: CustomStringConvertible {
|
||||
}
|
||||
}
|
||||
|
||||
open class FileObject {
|
||||
open internal(set) var allValues: [String: Any]
|
||||
|
||||
internal init(allValues: [String: Any]) {
|
||||
self.allValues = allValues
|
||||
}
|
||||
|
||||
internal init(absoluteURL: URL? = nil, name: String, path: String) {
|
||||
self.allValues = [String: Any]()
|
||||
self.absoluteURL = absoluteURL
|
||||
self.name = name
|
||||
self.path = path
|
||||
}
|
||||
|
||||
open internal(set) var absoluteURL: URL? {
|
||||
get {
|
||||
return allValues["NSURLAbsoluteURLKey"] as? URL
|
||||
}
|
||||
set {
|
||||
allValues["NSURLAbsoluteURLKey"] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open internal(set) var name: String {
|
||||
get {
|
||||
return allValues[URLResourceKey.nameKey.rawValue] as! String
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.nameKey.rawValue] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open internal(set) var path: String {
|
||||
get {
|
||||
return allValues[URLResourceKey.pathKey.rawValue] as! String
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.pathKey.rawValue] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open internal(set) var size: Int64 {
|
||||
get {
|
||||
return allValues[URLResourceKey.fileSizeKey.rawValue] as? Int64 ?? -1
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.fileSizeKey.rawValue] = Int(exactly: newValue) ?? Int.max
|
||||
}
|
||||
}
|
||||
|
||||
open internal(set) var creationDate: Date? {
|
||||
get {
|
||||
return allValues[URLResourceKey.creationDateKey.rawValue] as? Date
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.creationDateKey.rawValue] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open internal(set) var modifiedDate: Date? {
|
||||
get {
|
||||
return allValues[URLResourceKey.contentModificationDateKey.rawValue] as? Date
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.contentModificationDateKey.rawValue] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open internal(set) var fileType: URLFileResourceType? {
|
||||
get {
|
||||
guard let typeString = allValues[URLResourceKey.fileResourceTypeKey.rawValue] as? String else {
|
||||
return nil
|
||||
}
|
||||
return URLFileResourceType(rawValue: typeString)
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.fileResourceTypeKey.rawValue] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open internal(set) var isHidden: Bool {
|
||||
get {
|
||||
return allValues[URLResourceKey.isHiddenKey.rawValue] as? Bool ?? false
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.isHiddenKey.rawValue] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open internal(set) var isReadOnly: Bool {
|
||||
get {
|
||||
return !(allValues[URLResourceKey.isWritableKey.rawValue] as? Bool ?? true)
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.isWritableKey.rawValue] = !newValue
|
||||
}
|
||||
}
|
||||
|
||||
open var isDirectory: Bool {
|
||||
return self.fileType == .directory
|
||||
}
|
||||
|
||||
open var isRegularFile: Bool {
|
||||
return self.fileType == .regular
|
||||
}
|
||||
|
||||
open var isSymLink: Bool {
|
||||
return self.fileType == .symbolicLink
|
||||
}
|
||||
}
|
||||
|
||||
public protocol OperationHandle {
|
||||
var operationType: FileOperationType { get }
|
||||
@@ -443,13 +517,6 @@ public protocol FileOperationDelegate: class {
|
||||
func fileProvider(_ fileProvider: FileProviderOperations, shouldProceedAfterError error: Error, operation: FileOperationType) -> Bool
|
||||
}
|
||||
|
||||
// THESE ARE METHODS TO PROVIDE COMPATIBILITY WITH SWIFT 2.3 SIMOULTANIOUSLY!
|
||||
internal extension URL {
|
||||
var uw_scheme: String {
|
||||
return self.scheme ?? ""
|
||||
}
|
||||
}
|
||||
|
||||
internal class Weak<T: AnyObject> {
|
||||
weak var value : T?
|
||||
init (_ value: T) {
|
||||
@@ -457,21 +524,6 @@ internal class Weak<T: AnyObject> {
|
||||
}
|
||||
}
|
||||
|
||||
extension URLFileResourceType {
|
||||
public init(fileTypeValue: FileAttributeType) {
|
||||
switch fileTypeValue {
|
||||
case FileAttributeType.typeCharacterSpecial: self = .characterSpecial
|
||||
case FileAttributeType.typeDirectory: self = .directory
|
||||
case FileAttributeType.typeBlockSpecial: self = .blockSpecial
|
||||
case FileAttributeType.typeRegular: self = .regular
|
||||
case FileAttributeType.typeSymbolicLink: self = .symbolicLink
|
||||
case FileAttributeType.typeSocket: self = .socket
|
||||
case FileAttributeType.typeUnknown: self = .unknown
|
||||
default: self = .unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public protocol FoundationErrorEnum {
|
||||
init? (rawValue: Int)
|
||||
var rawValue: Int { get }
|
||||
@@ -479,20 +531,3 @@ public protocol FoundationErrorEnum {
|
||||
|
||||
extension URLError.Code: FoundationErrorEnum {}
|
||||
extension CocoaError.Code: FoundationErrorEnum {}
|
||||
|
||||
internal func jsonToDictionary(_ jsonString: String) -> [String: AnyObject]? {
|
||||
guard let data = jsonString.data(using: .utf8) else {
|
||||
return nil
|
||||
}
|
||||
if let dic = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions()) as? [String: AnyObject] {
|
||||
return dic
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
internal func dictionaryToJSON(_ dictionary: [String: AnyObject]) -> String? {
|
||||
if let data = try? JSONSerialization.data(withJSONObject: dictionary, options: JSONSerialization.WritingOptions()) {
|
||||
return String(data: data, encoding: .utf8)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -182,7 +182,8 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor {
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open func copyItem(localFile: URL, to toPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
// TODO: Make use of overwrite parameter
|
||||
let opType = FileOperationType.copy(source: localFile.absoluteString, destination: toPath)
|
||||
operation_queue.async {
|
||||
do {
|
||||
@@ -232,6 +233,9 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor {
|
||||
|
||||
@discardableResult
|
||||
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
|
||||
if length < 0 {
|
||||
return self.contents(path: path, completionHandler: completionHandler)
|
||||
}
|
||||
let opType = FileOperationType.fetch(path: path)
|
||||
dispatch_queue.async {
|
||||
let aPath = self.absoluteURL(path).path
|
||||
@@ -255,8 +259,15 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor {
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open func writeContents(path: String, contents data: Data, atomically: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func writeContents(path: String, contents data: Data, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.modify(path: path)
|
||||
var options: Data.WritingOptions = []
|
||||
if atomically {
|
||||
options.insert(.atomic)
|
||||
}
|
||||
if overwrite {
|
||||
options.insert(.withoutOverwriting)
|
||||
}
|
||||
operation_queue.async {
|
||||
try? data.write(to: self.absoluteURL(path), options: atomically ? [.atomic] : [])
|
||||
DispatchQueue.main.async(execute: {
|
||||
|
||||
@@ -56,7 +56,7 @@ public final class LocalFileObject: FileObject {
|
||||
internal class LocalFolderMonitor {
|
||||
fileprivate let source: DispatchSourceFileSystemObject
|
||||
fileprivate let descriptor: CInt
|
||||
fileprivate let qq: DispatchQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.default)
|
||||
fileprivate let qq: DispatchQueue = DispatchQueue.global(qos: .default)
|
||||
fileprivate var state: Bool = false
|
||||
fileprivate var monitoredTime: TimeInterval = Date().timeIntervalSinceReferenceDate
|
||||
var url: URL
|
||||
|
||||
@@ -201,11 +201,11 @@ public enum FileProviderHTTPErrorCode: Int {
|
||||
case notExtended = 510
|
||||
case networkAuthenticationRequired = 511
|
||||
|
||||
fileprivate static let status1xx = [100: "Continue", 101: "Switching Protocols", 102: "Processing"]
|
||||
fileprivate static let status2xx = [200: "OK", 201: "Created", 202: "Accepted", 203: "Non-Authoritative Information", 204: "No Content", 205: "Reset Content", 206: "Partial Content", 207: "Multi-Status", 208: "Already Reported", 226: "IM Used"]
|
||||
fileprivate static let status3xx = [300: "Multiple Choices", 301: "Moved Permanently", 302: "Found", 303: "See Other", 304: "Not Modified", 305: "Use Proxy", 306: "Switch Proxy", 307: "Temporary Redirect", 308: "Permanent Redirect"]
|
||||
fileprivate static let status4xx = [400: "Bad Request", 401: "Unauthorized/Expired Session", 402: "Payment Required", 403: "Forbidden", 404: "Not Found", 405: "Method Not Allowed", 406: "Not Acceptable", 407: "Proxy Authentication Required", 408: "Request Timeout", 409: "Conflict", 410: "Gone", 411: "Length Required", 412: "Precondition Failed", 413: "Payload Too Large", 414: "URI Too Long", 415: "Unsupported Media Type", 416: "Range Not Satisfiable", 417: "Expectation Failed", 421: "Misdirected Request", 422: "Unprocessable Entity", 423: "Locked", 424: "Failed Dependency", 425: "Unordered Collection", 426: "Upgrade Required", 428: "Precondition Required", 429: "Too Many Requests", 431: "Request Header Fields Too Large", 451: "Unavailable For Legal Reasons"]
|
||||
fileprivate static let status5xx = [500: "Internal Server Error", 501: "Not Implemented", 502: "Bad Gateway", 503: "Service Unavailable", 504: "Gateway Timeout", 505: "HTTP Version Not Supported", 506: "Variant Also Negotiates", 507: "Insufficient Storage", 508: "Loop Detected", 509: "Bandwidth Limit Exceeded", 510: "Not Extended", 511: "Network Authentication Required"]
|
||||
fileprivate static let status1xx: [Int: String] = [100: "Continue", 101: "Switching Protocols", 102: "Processing"]
|
||||
fileprivate static let status2xx: [Int: String] = [200: "OK", 201: "Created", 202: "Accepted", 203: "Non-Authoritative Information", 204: "No Content", 205: "Reset Content", 206: "Partial Content", 207: "Multi-Status", 208: "Already Reported", 226: "IM Used"]
|
||||
fileprivate static let status3xx: [Int: String] = [300: "Multiple Choices", 301: "Moved Permanently", 302: "Found", 303: "See Other", 304: "Not Modified", 305: "Use Proxy", 306: "Switch Proxy", 307: "Temporary Redirect", 308: "Permanent Redirect"]
|
||||
fileprivate static let status4xx: [Int: String] = [400: "Bad Request", 401: "Unauthorized/Expired Session", 402: "Payment Required", 403: "Forbidden", 404: "Not Found", 405: "Method Not Allowed", 406: "Not Acceptable", 407: "Proxy Authentication Required", 408: "Request Timeout", 409: "Conflict", 410: "Gone", 411: "Length Required", 412: "Precondition Failed", 413: "Payload Too Large", 414: "URI Too Long", 415: "Unsupported Media Type", 416: "Range Not Satisfiable", 417: "Expectation Failed", 421: "Misdirected Request", 422: "Unprocessable Entity", 423: "Locked", 424: "Failed Dependency", 425: "Unordered Collection", 426: "Upgrade Required", 428: "Precondition Required", 429: "Too Many Requests", 431: "Request Header Fields Too Large", 451: "Unavailable For Legal Reasons"]
|
||||
fileprivate static let status5xx: [Int: String] = [500: "Internal Server Error", 501: "Not Implemented", 502: "Bad Gateway", 503: "Service Unavailable", 504: "Gateway Timeout", 505: "HTTP Version Not Supported", 506: "Variant Also Negotiates", 507: "Insufficient Storage", 508: "Loop Detected", 509: "Bandwidth Limit Exceeded", 510: "Not Extended", 511: "Network Authentication Required"]
|
||||
|
||||
public var description: String {
|
||||
switch self.rawValue {
|
||||
|
||||
@@ -68,7 +68,7 @@ class SMBFileProvider: FileProvider, FileProviderMonitor {
|
||||
return nil
|
||||
}
|
||||
|
||||
open func copyItem(localFile: URL, to toPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
NotImplemented()
|
||||
return nil
|
||||
}
|
||||
@@ -88,7 +88,7 @@ class SMBFileProvider: FileProvider, FileProviderMonitor {
|
||||
return nil
|
||||
}
|
||||
|
||||
open func writeContents(path: String, contents data: Data, atomically: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func writeContents(path: String, contents data: Data, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
NotImplemented()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -66,3 +66,32 @@ struct SMBTime {
|
||||
return Date(timeIntervalSince1970: Double(self.time) / 10000000 - 11644473600)
|
||||
}
|
||||
}
|
||||
|
||||
extension Data {
|
||||
init<T>(value: T) {
|
||||
var value = value
|
||||
self = Data(buffer: UnsafeBufferPointer(start: &value, count: 1))
|
||||
}
|
||||
|
||||
func scanValue<T>() -> T? {
|
||||
guard MemoryLayout<T>.size <= self.count else { return nil }
|
||||
return self.withUnsafeBytes { $0.pointee }
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
|
||||
func scanString(start: Int = 0, length: Int, encoding: String.Encoding) -> String? {
|
||||
guard self.count >= start + length else { return nil }
|
||||
return String(data: self.subdata(in: start..<start+length), encoding: encoding)
|
||||
}
|
||||
|
||||
static func mapMemory<T, U>(from: T) -> U? {
|
||||
guard MemoryLayout<T>.size >= MemoryLayout<U>.size else { return nil }
|
||||
let data = Data(value: from)
|
||||
return data.scanValue()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,9 +227,8 @@ extension SMB2 {
|
||||
}
|
||||
|
||||
init(name: UUID, data: Data) {
|
||||
var uuid = uuid_t(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)
|
||||
(name as NSUUID).getBytes(&uuid.0)
|
||||
var nameData = Data(bytes: &uuid.0, count: 16)
|
||||
let uuid = name.uuid
|
||||
var nameData = Data(value: uuid)
|
||||
self.header = CreateContext.Header(next: 0, nameOffset: 32, nameLength: UInt16(nameData.count), reserved: 0, dataOffset: UInt16(nameData.count), dataLength: UInt32(data.count))
|
||||
self.buffer = data
|
||||
}
|
||||
|
||||
@@ -8,35 +8,6 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Data {
|
||||
init<T>(value: T) {
|
||||
var value = value
|
||||
self = Data(buffer: UnsafeBufferPointer(start: &value, count: MemoryLayout.size(ofValue: value)))
|
||||
}
|
||||
|
||||
func scanValue<T>() -> T? {
|
||||
guard MemoryLayout<T>.size <= self.count else { return nil }
|
||||
return self.withUnsafeBytes { $0.pointee }
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
|
||||
func scanString(start: Int = 0, length: Int, encoding: String.Encoding) -> String? {
|
||||
guard self.count >= start + length else { return nil }
|
||||
return String(data: self.subdata(in: start..<start+length), encoding: encoding)
|
||||
}
|
||||
|
||||
static func mapMemory<T, U>(from: T) -> U? {
|
||||
guard MemoryLayout<T>.size >= MemoryLayout<U>.size else { return nil }
|
||||
let data = Data(value: from)
|
||||
return data.scanValue()
|
||||
}
|
||||
}
|
||||
|
||||
protocol FileProviderSMBHeader {
|
||||
var protocolID: UInt32 { get }
|
||||
static var protocolConst: UInt32 { get }
|
||||
|
||||
@@ -280,7 +280,8 @@ extension WebDAVFileProvider: FileProviderOperations {
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func copyItem(localFile: URL, to toPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
public func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
// TODO: Make use of overwrite parameter
|
||||
let opType = FileOperationType.copy(source: localFile.absoluteString, destination: toPath)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
@@ -332,11 +333,6 @@ extension WebDAVFileProvider: FileProviderOperations {
|
||||
}
|
||||
|
||||
extension WebDAVFileProvider: FileProviderReadWrite {
|
||||
@discardableResult
|
||||
public func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
|
||||
return self.contents(path: path, offset: 0, length: -1, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
|
||||
let opType = FileOperationType.fetch(path: path)
|
||||
@@ -360,7 +356,7 @@ extension WebDAVFileProvider: FileProviderReadWrite {
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func writeContents(path: String, contents data: Data, atomically: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
public func writeContents(path: String, contents data: Data, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.modify(path: path)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
@@ -369,6 +365,9 @@ extension WebDAVFileProvider: FileProviderReadWrite {
|
||||
let url = atomically ? absoluteURL(path).appendingPathExtension("tmp") : absoluteURL(path)
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "PUT"
|
||||
if !overwrite {
|
||||
request.setValue("F", forHTTPHeaderField: "Overwrite")
|
||||
}
|
||||
let task = session.uploadTask(with: request, from: data, completionHandler: { (data, response, error) in
|
||||
var responseError: FileProviderWebDavError?
|
||||
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
@@ -540,7 +539,7 @@ internal extension WebDAVFileProvider {
|
||||
fileObject.creationDate = self.resolve(dateString: davResponse.prop["creationdate"] ?? "")
|
||||
fileObject.modifiedDate = self.resolve(dateString: davResponse.prop["getlastmodified"] ?? "")
|
||||
fileObject.contentType = davResponse.prop["getcontenttype"] ?? "octet/stream"
|
||||
fileObject.fileType = fileObject.contentType == "httpd/unix-directory" ? .directory : .regular
|
||||
fileObject.type = fileObject.contentType == "httpd/unix-directory" ? .directory : .regular
|
||||
fileObject.entryTag = davResponse.prop["getetag"]
|
||||
return fileObject
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user