Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 10aac532f7 | |||
| 3aac0a88c4 | |||
| 24355a4c6c | |||
| d2cf657ab2 | |||
| f2d54c7f92 | |||
| 0e9a82a288 | |||
| ea7e089718 | |||
| 19ea14ebc1 | |||
| 47885c0a4e | |||
| ac44aa190f | |||
| 50f6a393a0 | |||
| 779b38f381 | |||
| a374841883 | |||
| 0123d8b117 | |||
| fdc4bb818d | |||
| a08a9fe7a0 | |||
| 792ac6b015 | |||
| 8bad5944bc | |||
| 6ef2ab11c4 | |||
| 7f27d46c70 | |||
| b1ec99b1b8 | |||
| c4b8065cd3 | |||
| 2d8454c711 | |||
| 3b35c066de | |||
| 7d8de9cefe | |||
| e84fef20ea | |||
| 4a9a3196a2 | |||
| f4e6d277ae | |||
| 1328a8e9e2 | |||
| 5bf620916f |
@@ -16,8 +16,8 @@ Pod::Spec.new do |s|
|
||||
#
|
||||
|
||||
s.name = "FileProvider"
|
||||
s.version = "0.7.1"
|
||||
s.summary = "FileManager replacement for Local and Remote (WebDAV/Dropbox/SMB2) files on iOS and macOS."
|
||||
s.version = "0.10.1"
|
||||
s.summary = "FileManager replacement for Local and Remote (WebDAV/Dropbox/OneDrive/SMB2) files on iOS and macOS."
|
||||
|
||||
# This description is used to generate tags and improve search results.
|
||||
# * Think: What does it do? Why did you write it? What is the focus?
|
||||
|
||||
@@ -32,6 +32,9 @@
|
||||
7924B1B11D89F7DF00589DB7 /* FPSStreamTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B1A81D89F79200589DB7 /* FPSStreamTask.swift */; };
|
||||
7924B1B21D89FCDA00589DB7 /* FPSStreamTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B1A81D89F79200589DB7 /* FPSStreamTask.swift */; };
|
||||
7924B1B31D89FD6400589DB7 /* SMBClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396971D48C02300086753 /* SMBClient.swift */; };
|
||||
792572411DF23BDA006A1526 /* LocalHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 792572401DF23BDA006A1526 /* LocalHelper.swift */; };
|
||||
792572421DF23BDA006A1526 /* LocalHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 792572401DF23BDA006A1526 /* LocalHelper.swift */; };
|
||||
792572431DF23BDA006A1526 /* LocalHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 792572401DF23BDA006A1526 /* LocalHelper.swift */; };
|
||||
794C21FE1D58912A00EC49B8 /* DropboxHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */; };
|
||||
794C21FF1D58912A00EC49B8 /* DropboxHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */; };
|
||||
794C22001D58912A00EC49B8 /* DropboxHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */; };
|
||||
@@ -89,6 +92,29 @@
|
||||
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 */; };
|
||||
79BD63B01E2CC3300035128C /* libxml2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63AF1E2CC3300035128C /* libxml2.tbd */; };
|
||||
79BD63B21E2CC3350035128C /* ImageIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63B11E2CC3350035128C /* ImageIO.framework */; };
|
||||
79BD63B61E2CC3860035128C /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63B51E2CC3860035128C /* CoreFoundation.framework */; };
|
||||
79BD63B81E2CC38D0035128C /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63B71E2CC38D0035128C /* AVFoundation.framework */; };
|
||||
79BD63BA1E2CC39B0035128C /* libxml2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63B91E2CC39B0035128C /* libxml2.tbd */; };
|
||||
79BD63BE1E2CC3C20035128C /* ImageIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63BD1E2CC3C20035128C /* ImageIO.framework */; };
|
||||
79BD63C01E2CC3CD0035128C /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63BF1E2CC3CD0035128C /* CoreGraphics.framework */; };
|
||||
79BD63C21E2CC3D30035128C /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63C11E2CC3D30035128C /* AVFoundation.framework */; };
|
||||
79BD63C51E2D17880035128C /* OneDriveFileProvide.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD63C31E2D17880035128C /* OneDriveFileProvide.swift */; };
|
||||
79BD63C61E2D17880035128C /* OneDriveFileProvide.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD63C31E2D17880035128C /* OneDriveFileProvide.swift */; };
|
||||
79BD63C71E2D17880035128C /* OneDriveFileProvide.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD63C31E2D17880035128C /* OneDriveFileProvide.swift */; };
|
||||
79BD63C81E2D17880035128C /* OneDriveHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD63C41E2D17880035128C /* OneDriveHelper.swift */; };
|
||||
79BD63C91E2D17880035128C /* OneDriveHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD63C41E2D17880035128C /* OneDriveHelper.swift */; };
|
||||
79BD63CA1E2D17880035128C /* OneDriveHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD63C41E2D17880035128C /* OneDriveHelper.swift */; };
|
||||
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 */
|
||||
@@ -100,6 +126,7 @@
|
||||
7924B1911D89DAE000589DB7 /* Options.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Options.swift; sourceTree = "<group>"; };
|
||||
7924B1921D89DAE000589DB7 /* Parser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Parser.swift; sourceTree = "<group>"; };
|
||||
7924B1A81D89F79200589DB7 /* FPSStreamTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FPSStreamTask.swift; sourceTree = "<group>"; };
|
||||
792572401DF23BDA006A1526 /* LocalHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalHelper.swift; sourceTree = "<group>"; };
|
||||
794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropboxHelper.swift; sourceTree = "<group>"; };
|
||||
794C22091D5893F800EC49B8 /* SMB2Notification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2Notification.swift; sourceTree = "<group>"; };
|
||||
794C220D1D591A4B00EC49B8 /* SMB2QueryTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2QueryTypes.swift; sourceTree = "<group>"; };
|
||||
@@ -127,6 +154,24 @@
|
||||
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; };
|
||||
79BD63C31E2D17880035128C /* OneDriveFileProvide.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OneDriveFileProvide.swift; sourceTree = "<group>"; };
|
||||
79BD63C41E2D17880035128C /* OneDriveHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OneDriveHelper.swift; sourceTree = "<group>"; };
|
||||
79F5745A1DFDB10A00179ABF /* FileObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileObject.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -134,6 +179,9 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
79BD63AA1E2CC2BB0035128C /* AVFoundation.framework in Frameworks */,
|
||||
79BD63AC1E2CC2C20035128C /* ImageIO.framework in Frameworks */,
|
||||
79BD63A81E2CC2940035128C /* CoreGraphics.framework in Frameworks */,
|
||||
791950F51DE58A5400B4426E /* libxml2.tbd in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -142,6 +190,10 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
79BD63B81E2CC38D0035128C /* AVFoundation.framework in Frameworks */,
|
||||
79BD63B61E2CC3860035128C /* CoreFoundation.framework in Frameworks */,
|
||||
79BD63B21E2CC3350035128C /* ImageIO.framework in Frameworks */,
|
||||
79BD63B01E2CC3300035128C /* libxml2.tbd in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -149,6 +201,10 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
79BD63C21E2CC3D30035128C /* AVFoundation.framework in Frameworks */,
|
||||
79BD63C01E2CC3CD0035128C /* CoreGraphics.framework in Frameworks */,
|
||||
79BD63BE1E2CC3C20035128C /* ImageIO.framework in Frameworks */,
|
||||
79BD63BA1E2CC39B0035128C /* libxml2.tbd in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -158,6 +214,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;
|
||||
@@ -178,6 +248,7 @@
|
||||
7993965B1D48B7BF00086753 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
79E34A101E2AC6C600E1293B /* Extra */,
|
||||
799396911D48C02300086753 /* Sources */,
|
||||
7993968A1D48B8C700086753 /* Pod */,
|
||||
799396681D48B7F600086753 /* Products */,
|
||||
@@ -210,12 +281,17 @@
|
||||
children = (
|
||||
7924B18B1D89DAE000589DB7 /* AEXML */,
|
||||
799396991D48C02300086753 /* SMBTypes */,
|
||||
799396931D48C02300086753 /* DropboxFileProvider.swift */,
|
||||
794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */,
|
||||
799396941D48C02300086753 /* FileProvider.h */,
|
||||
799396951D48C02300086753 /* FileProvider.swift */,
|
||||
79F5745A1DFDB10A00179ABF /* FileObject.swift */,
|
||||
799396961D48C02300086753 /* LocalFileProvider.swift */,
|
||||
792572401DF23BDA006A1526 /* LocalHelper.swift */,
|
||||
79BD638B1E2CC2300035128C /* ExtendedLocalFileProvider.swift */,
|
||||
7902C0851D61B56D00564440 /* RemoteSession.swift */,
|
||||
799396931D48C02300086753 /* DropboxFileProvider.swift */,
|
||||
794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */,
|
||||
79BD63C31E2D17880035128C /* OneDriveFileProvide.swift */,
|
||||
79BD63C41E2D17880035128C /* OneDriveHelper.swift */,
|
||||
7924B1A81D89F79200589DB7 /* FPSStreamTask.swift */,
|
||||
799396971D48C02300086753 /* SMBClient.swift */,
|
||||
799396981D48C02300086753 /* SMBFileProvider.swift */,
|
||||
@@ -244,6 +320,13 @@
|
||||
path = SMBTypes;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
79E34A101E2AC6C600E1293B /* Extra */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
name = Extra;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
@@ -396,11 +479,15 @@
|
||||
799396B31D48C02300086753 /* LocalFileProvider.swift in Sources */,
|
||||
799396D41D48C02300086753 /* SMB2Tree.swift in Sources */,
|
||||
7924B1A21D89DAE000589DB7 /* Options.swift in Sources */,
|
||||
792572411DF23BDA006A1526 /* LocalHelper.swift in Sources */,
|
||||
79F5745B1DFDB10B00179ABF /* FileObject.swift in Sources */,
|
||||
79BD63C51E2D17880035128C /* OneDriveFileProvide.swift in Sources */,
|
||||
7924B1991D89DAE000589DB7 /* Element.swift in Sources */,
|
||||
799396C81D48C02300086753 /* SMB2IOCtl.swift in Sources */,
|
||||
799396D71D48C02300086753 /* SMB2Types.swift in Sources */,
|
||||
7924B1B21D89FCDA00589DB7 /* FPSStreamTask.swift in Sources */,
|
||||
799396C51D48C02300086753 /* SMB2FileOperation.swift in Sources */,
|
||||
79BD63C81E2D17880035128C /* OneDriveHelper.swift in Sources */,
|
||||
799396BF1D48C02300086753 /* SMB2DataTypes.swift in Sources */,
|
||||
799396B91D48C02300086753 /* SMBFileProvider.swift in Sources */,
|
||||
794C220E1D591A4B00EC49B8 /* SMB2QueryTypes.swift in Sources */,
|
||||
@@ -416,6 +503,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 */,
|
||||
@@ -430,11 +518,15 @@
|
||||
799396B41D48C02300086753 /* LocalFileProvider.swift in Sources */,
|
||||
799396D51D48C02300086753 /* SMB2Tree.swift in Sources */,
|
||||
7924B1A31D89DAE000589DB7 /* Options.swift in Sources */,
|
||||
792572421DF23BDA006A1526 /* LocalHelper.swift in Sources */,
|
||||
79F5745C1DFDB10B00179ABF /* FileObject.swift in Sources */,
|
||||
79BD63C61E2D17880035128C /* OneDriveFileProvide.swift in Sources */,
|
||||
7924B1B01D89F7DE00589DB7 /* FPSStreamTask.swift in Sources */,
|
||||
7924B19A1D89DAE000589DB7 /* Element.swift in Sources */,
|
||||
799396C91D48C02300086753 /* SMB2IOCtl.swift in Sources */,
|
||||
799396D81D48C02300086753 /* SMB2Types.swift in Sources */,
|
||||
799396C61D48C02300086753 /* SMB2FileOperation.swift in Sources */,
|
||||
79BD63C91E2D17880035128C /* OneDriveHelper.swift in Sources */,
|
||||
799396C01D48C02300086753 /* SMB2DataTypes.swift in Sources */,
|
||||
799396BA1D48C02300086753 /* SMBFileProvider.swift in Sources */,
|
||||
794C220F1D591A4B00EC49B8 /* SMB2QueryTypes.swift in Sources */,
|
||||
@@ -450,6 +542,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 */,
|
||||
@@ -464,11 +557,15 @@
|
||||
799396B51D48C02300086753 /* LocalFileProvider.swift in Sources */,
|
||||
799396D61D48C02300086753 /* SMB2Tree.swift in Sources */,
|
||||
7924B1A41D89DAE000589DB7 /* Options.swift in Sources */,
|
||||
792572431DF23BDA006A1526 /* LocalHelper.swift in Sources */,
|
||||
79F5745D1DFDB10B00179ABF /* FileObject.swift in Sources */,
|
||||
79BD63C71E2D17880035128C /* OneDriveFileProvide.swift in Sources */,
|
||||
7924B1B11D89F7DF00589DB7 /* FPSStreamTask.swift in Sources */,
|
||||
7924B19B1D89DAE000589DB7 /* Element.swift in Sources */,
|
||||
799396CA1D48C02300086753 /* SMB2IOCtl.swift in Sources */,
|
||||
799396D91D48C02300086753 /* SMB2Types.swift in Sources */,
|
||||
799396C71D48C02300086753 /* SMB2FileOperation.swift in Sources */,
|
||||
79BD63CA1E2D17880035128C /* OneDriveHelper.swift in Sources */,
|
||||
799396C11D48C02300086753 /* SMB2DataTypes.swift in Sources */,
|
||||
799396BB1D48C02300086753 /* SMBFileProvider.swift in Sources */,
|
||||
794C22101D591A4B00EC49B8 /* SMB2QueryTypes.swift in Sources */,
|
||||
@@ -484,6 +581,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 */,
|
||||
@@ -497,7 +595,7 @@
|
||||
799396601D48B7BF00086753 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_VERSION_STRING = 0.7.0;
|
||||
BUNDLE_VERSION_STRING = 0.10.1;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
@@ -520,14 +618,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.10.1;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
@@ -548,7 +646,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;
|
||||
};
|
||||
@@ -557,7 +655,7 @@
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
BUNDLE_VERSION_STRING = 0.7.1;
|
||||
BUNDLE_VERSION_STRING = 0.8.2;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
@@ -610,7 +708,7 @@
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
BUNDLE_VERSION_STRING = 0.7.1;
|
||||
BUNDLE_VERSION_STRING = 0.8.2;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
@@ -678,6 +776,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;
|
||||
@@ -733,6 +832,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;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
[](https://github.com/Carthage/Carthage)
|
||||
--->
|
||||
|
||||
This library provides implementaion of WebDav and SMB2 (incomplete) and local files.
|
||||
This library provides implementaion of WebDav, Dropbox, OneDrive and SMB2 (incomplete) and local files.
|
||||
|
||||
All functions are async calls and it wont block your main thread.
|
||||
|
||||
@@ -27,6 +27,7 @@ Local and WebDAV providers are fully tested and can be used in production enviro
|
||||
- [x] **LocalFileProvider** a wrapper around `FileManager` with some additions like searching and reading a portion of file.
|
||||
- [x] **WebDAVFileProvider** WebDAV protocol is defacto file transmission standard, replaced FTP.
|
||||
- [x] **DropboxFileProvider** A wrapper around Dropbox Web API. For now it has limitation in uploading files up to 150MB.
|
||||
- [x] **OneDriveFileProvider** A wrapper around OneDrive Web API, works with `onedrive.com` and compatible servers. For now it has limitation in uploading files up to 100MB.
|
||||
- [ ] **SMBFileProvider** SMB2/3 introduced in 2006, which is a file and printer sharing protocol originated from Microsoft Windows and now is replacing AFP protocol on MacOS. I implemented data types and some basic functions but *main interface is not implemented yet!* SMB1/CIFS is depericated and very tricky to be implemented
|
||||
- [ ] **FTPFileProvider** while deprecated in 1990s, it's still in use on some Web hosts.
|
||||
- [ ] **AmazonS3FileProvider**
|
||||
@@ -43,27 +44,48 @@ Legacy version is available in swift-2 branch
|
||||
|
||||
### Cocoapods / Carthage / Swift Package Manager
|
||||
|
||||
FileProvider supports both CocoaPods.
|
||||
|
||||
Add this line to your pods file:
|
||||
|
||||
pod "FileProvider"
|
||||
```ruby
|
||||
pod "FileProvider"
|
||||
```
|
||||
|
||||
Or add this to Cartfile:
|
||||
|
||||
```
|
||||
github "amosavian/FileProvider"
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
### Git
|
||||
To have latest updates with ease, use this command on terminal to get a clone:
|
||||
|
||||
git clone https://github.com/amosavian/FileProvider FileProvider
|
||||
|
||||
```bash
|
||||
git clone https://github.com/amosavian/FileProvider
|
||||
```
|
||||
|
||||
You can update your library using this command in FileProvider folder:
|
||||
|
||||
git pull
|
||||
```bash
|
||||
git pull
|
||||
```
|
||||
|
||||
if you have a git based project, use this command in your projects directory to add this project as a submodule to your project:
|
||||
|
||||
git submodule add https://github.com/amosavian/FileProvider FileProvider
|
||||
```bash
|
||||
git submodule add https://github.com/amosavian/FileProvider
|
||||
```
|
||||
Then you can do either:
|
||||
|
||||
### Manually
|
||||
Copy Source folder to your project and Voila!
|
||||
* Copy Source folder to your project and Voila!
|
||||
|
||||
* Drop FileProvider.xcodeproj to you Xcode workspace and add the framework to your Embeded Binaries in target.
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -73,24 +95,30 @@ Each provider has a specific class which conforms to FileProvider protocol and s
|
||||
|
||||
For LocalFileProvider if you want to deal with `Documents` folder
|
||||
|
||||
let documentsProvider = LocalFileProvider()
|
||||
``` swift
|
||||
let documentsProvider = LocalFileProvider()
|
||||
```
|
||||
|
||||
is equal to:
|
||||
|
||||
let documentPath = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true);
|
||||
let documentsURL = URL(fileURLWithPath: documentPath);
|
||||
let documentsProvider = LocalFileProvider(baseURL: documentsURL)
|
||||
|
||||
``` swift
|
||||
let documentPath = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true);
|
||||
let documentsURL = URL(fileURLWithPath: documentPath);
|
||||
let documentsProvider = LocalFileProvider(baseURL: documentsURL)
|
||||
```
|
||||
|
||||
You can't change the base url later. and all paths are related to this base url by default.
|
||||
|
||||
For remote file providers authentication may be necessary:
|
||||
|
||||
let credential = URLCredential(user: "user", password: "pass", persistence: .permanent)
|
||||
let webdavProvider = WebDAVFileProvider(baseURL: URL(string: "http://www.example.com/dav")!, credential: credential)
|
||||
``` swift
|
||||
let credential = URLCredential(user: "user", password: "pass", persistence: .permanent)
|
||||
let webdavProvider = WebDAVFileProvider(baseURL: URL(string: "http://www.example.com/dav")!, credential: credential)
|
||||
```
|
||||
|
||||
* 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 & OneDrive, 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
|
||||
|
||||
@@ -104,40 +132,42 @@ It's simply three method which indicated whether the operation failed, succeed a
|
||||
|
||||
Your class should conforms `FileProviderDelegate` class:
|
||||
|
||||
override func viewDidLoad() {
|
||||
documentsProvider.delegate = self as FileProviderDelegate
|
||||
}
|
||||
```swift
|
||||
override func viewDidLoad() {
|
||||
documentsProvider.delegate = self as FileProviderDelegate
|
||||
}
|
||||
|
||||
func fileproviderSucceed(_ fileProvider: FileProviderOperations, operation: FileOperation) {
|
||||
switch operation {
|
||||
case .copy(source: let source, destination: let dest):
|
||||
print("\(source) copied to \(dest).")
|
||||
case .remove(path: let path):
|
||||
print("\(path) has been deleted.")
|
||||
default:
|
||||
print("\(operation.actionDescription) from \(operation.source ?? "") to \(operation.destination) succeed")
|
||||
}
|
||||
func fileproviderSucceed(_ fileProvider: FileProviderOperations, operation: FileOperation) {
|
||||
switch operation {
|
||||
case .copy(source: let source, destination: let dest):
|
||||
print("\(source) copied to \(dest).")
|
||||
case .remove(path: let path):
|
||||
print("\(path) has been deleted.")
|
||||
default:
|
||||
print("\(operation.actionDescription) from \(operation.source!) to \(operation.destination) succeed")
|
||||
}
|
||||
|
||||
func fileproviderFailed(_ fileProvider: FileProviderOperations, operation: FileOperation) {
|
||||
switch operation {
|
||||
case .copy(source: let source, destination: let dest):
|
||||
print("copy of \(source) failed.")
|
||||
case .remove(path: let path):
|
||||
print("\(path) can't be deleted.")
|
||||
default:
|
||||
print("\(operation.actionDescription) from \(operation.source ?? "") to \(operation.destination) failed")
|
||||
}
|
||||
}
|
||||
|
||||
func fileproviderProgress(_ fileProvider: FileProviderOperations, operation: FileOperation, progress: Float) {
|
||||
switch operation {
|
||||
case .copy(source: let source, destination: let dest):
|
||||
print("Copy\(source) to \(dest): \(progress * 100) completed.")
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func fileproviderFailed(_ fileProvider: FileProviderOperations, operation: FileOperation) {
|
||||
switch operation {
|
||||
case .copy(source: let source, destination: let dest):
|
||||
print("copy of \(source) failed.")
|
||||
case .remove:
|
||||
print("file can't be deleted.")
|
||||
default:
|
||||
print("\(operation.actionDescription) from \(operation.source!) to \(operation.destination) failed")
|
||||
}
|
||||
}
|
||||
|
||||
func fileproviderProgress(_ fileProvider: FileProviderOperations, operation: FileOperation, progress: Float) {
|
||||
switch operation {
|
||||
case .copy(source: let source, destination: let dest):
|
||||
print("Copy\(source) to \(dest): \(progress * 100) completed.")
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** `fileproviderProgress()` delegate method is not called by `LocalFileProvider` currently.
|
||||
|
||||
@@ -159,42 +189,50 @@ There is a `FileObject` class which holds file attributes like size and creation
|
||||
|
||||
For a single file:
|
||||
|
||||
documentsProvider.attributesOfItem(path: "/file.txt", completionHandler: {
|
||||
(attributes: LocalFileObject?, error: ErrorType?) -> Void in
|
||||
if let attributes = attributes {
|
||||
print("File Size: \(attributes.size)")
|
||||
print("Creation Date: \(attributes.createdDate)")
|
||||
print("Modification Date: \(attributes.modifiedDate)")
|
||||
print("Is Read Only: \(attributes.isReadOnly)")
|
||||
}
|
||||
})
|
||||
```swift
|
||||
documentsProvider.attributesOfItem(path: "/file.txt", completionHandler: {
|
||||
attributes, error in
|
||||
if let attributes = attributes {
|
||||
print("File Size: \(attributes.size)")
|
||||
print("Creation Date: \(attributes.creationDate)")
|
||||
print("Modification Date: \(attributes.modifiedDate)")
|
||||
print("Is Read Only: \(attributes.isReadOnly)")
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
To get list of files in a directory:
|
||||
|
||||
documentsProvider.contentsOfDirectory(path: "/", completionHandler: {
|
||||
(contents: [LocalFileObject], error: ErrorType?) -> Void in
|
||||
for file in contents {
|
||||
print("Name: \(attributes.name)")
|
||||
print("Size: \(attributes.size)")
|
||||
print("Creation Date: \(attributes.createdDate)")
|
||||
print("Modification Date: \(attributes.modifiedDate)")
|
||||
}
|
||||
})
|
||||
```swift
|
||||
documentsProvider.contentsOfDirectory(path: "/", completionHandler: {
|
||||
contents, error in
|
||||
for file in contents {
|
||||
print("Name: \(attributes.name)")
|
||||
print("Size: \(attributes.size)")
|
||||
print("Creation Date: \(attributes.creationDate)")
|
||||
print("Modification Date: \(attributes.modifiedDate)")
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
To get size of strage and used/free space:
|
||||
|
||||
func storageProperties(completionHandler: {(total: Int64, used: Int64) -> Void in
|
||||
print("Total Storage Space: \(total)")
|
||||
print("Used Space: \(used)")
|
||||
print("Free Space: \(total - used)")
|
||||
})
|
||||
```swift
|
||||
func storageProperties(completionHandler: { total, used in
|
||||
print("Total Storage Space: \(total)")
|
||||
print("Used Space: \(used)")
|
||||
print("Free Space: \(total - used)")
|
||||
})
|
||||
```
|
||||
|
||||
* if this function is unavailable on provider or an error has been occurred, total space will be reported `-1` and used space `0`
|
||||
|
||||
### Change current directory
|
||||
|
||||
documentsProvider.currentPath = "/New Folder"
|
||||
// now path is ~/Documents/New Folder
|
||||
```swift
|
||||
documentsProvider.currentPath = "/New Folder"
|
||||
// now path is ~/Documents/New Folder
|
||||
```
|
||||
|
||||
You can then pass "" (empty string) to `contentsOfDirectory` method to list files in current directory.
|
||||
|
||||
@@ -202,56 +240,71 @@ You can then pass "" (empty string) to `contentsOfDirectory` method to list file
|
||||
|
||||
Creating new directory:
|
||||
|
||||
documentsProvider.create(folder: "new folder", at: "/", completionHandler: nil)
|
||||
```swift
|
||||
documentsProvider.create(folder: "new folder", at: "/", completionHandler: nil)
|
||||
```
|
||||
|
||||
Creating new file from data stream:
|
||||
Creating new file from data:
|
||||
|
||||
let data = "hello world!".data(encoding: String.encoding.utf8)
|
||||
let file = FileObject(name: "old.txt", createdDate: Date(), modifiedDate: Date(), isHidden: false, isReadOnly: true)
|
||||
documentsProvider.create(file: file, at: "/", contents: data, completionHandler: nil)
|
||||
```swift
|
||||
let data = "hello world!".data(encoding: .utf8)
|
||||
documentsProvider.create(file: "newFile.txt", at: "/", contents: data, completionHandler: nil)
|
||||
```
|
||||
|
||||
### Copy and Move/Rename Files
|
||||
|
||||
Copy file old.txt to new.txt in current path:
|
||||
|
||||
documentsProvider.copyItem(path: "new folder/old.txt", to: "new.txt", overwrite: false, completionHandler: nil)
|
||||
```swift
|
||||
documentsProvider.copyItem(path: "new folder/old.txt", to: "new.txt", overwrite: false, completionHandler: nil)
|
||||
```
|
||||
|
||||
Move file old.txt to new.txt in current path:
|
||||
|
||||
documentsProvider.moveItem(path: "new folder/old.txt", to: "new.txt", overwrite: false, completionHandler: nil)
|
||||
```swift
|
||||
documentsProvider.moveItem(path: "new folder/old.txt", to: "new.txt", overwrite: false, completionHandler: nil)
|
||||
```
|
||||
|
||||
**Note:** To have a consistent behaviour, create intermediate directories first if necessary.
|
||||
**Note:** To have a consistent behavior, create intermediate directories first if necessary.
|
||||
|
||||
### Delete Files
|
||||
|
||||
documentsProvider.removeItem(path: "new.txt", completionHandler: nil)
|
||||
```swift
|
||||
documentsProvider.removeItem(path: "new.txt", completionHandler: nil)
|
||||
```
|
||||
|
||||
***Caution:*** This method will delete directories with all it's content recursively.
|
||||
***Caution:*** This method will delete directories with all it's contents recursively.
|
||||
|
||||
### Retrieve Content of File
|
||||
### Fetching Contents of File
|
||||
|
||||
There is two method for this purpose, one of them loads entire file into NSData and another can load a portion of file.
|
||||
|
||||
documentsProvider.contents(path: "old.txt", completionHandler: {
|
||||
(contents: Data?, error: ErrorType?) -> Void
|
||||
if let contents = contents {
|
||||
print(String(data: contents, encoding: String.encoding.utf8)) // "hello world!"
|
||||
}
|
||||
})
|
||||
```swift
|
||||
documentsProvider.contents(path: "old.txt", completionHandler: {
|
||||
contents, error in
|
||||
if let contents = contents {
|
||||
print(String(data: contents, encoding: .utf8)) // "hello world!"
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
If you want to retrieve a portion of file you can use `contents` method with offset and length arguments. Please note first byte of file has offset: 0.
|
||||
|
||||
documentsProvider.contents(path: "old.txt", offset: 2, length: 5, completionHandler: {
|
||||
(contents: Data?, error: ErrorType?) -> Void
|
||||
if let contents = contents {
|
||||
print(String(data: contents, encoding: String.encoding.utf8)) // "llo w"
|
||||
}
|
||||
})
|
||||
```swift
|
||||
documentsProvider.contents(path: "old.txt", offset: 2, length: 5, completionHandler: {
|
||||
contents, error in
|
||||
if let contents = contents {
|
||||
print(String(data: contents, encoding: .utf8)) // "llo w"
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Write Data To Files
|
||||
|
||||
let data = "What's up Newyork!".data(encoding: String.encoding.utf8)
|
||||
documentsProvider.writeContents(path: "old.txt", content: data, atomically: true, completionHandler: nil)
|
||||
```swift
|
||||
let data = "What's up Newyork!".data(encoding: .utf8)
|
||||
documentsProvider.writeContents(path: "old.txt", content: data, atomically: true, completionHandler: nil)
|
||||
```
|
||||
|
||||
### Operation Handle
|
||||
|
||||
@@ -263,16 +316,56 @@ It's not supported by native `(NS)FileManager` so `LocalFileProvider`, but this
|
||||
|
||||
You can monitor updates in some file system (Local and SMB2), there is three methods in supporting provider you can use to register a handler, to unregister and to check whether it's being monitored or not. It's useful to find out when new files added or removed from directory and update user interface. The handler will be dispatched to main threads to avoid UI bugs with a 0.25 sec delay.
|
||||
|
||||
documentsProvider.registerNotifcation(path: provider.currentPath)
|
||||
{
|
||||
// calling functions to update UI
|
||||
}
|
||||
```swift
|
||||
// to register a new notification handler
|
||||
documentsProvider.registerNotifcation(path: provider.currentPath)
|
||||
{
|
||||
// calling functions to update UI
|
||||
}
|
||||
|
||||
// To discontinue monitoring folders:
|
||||
documentsProvider.unregisterNotifcation(path: provider.currentPath)
|
||||
// To discontinue monitoring folders:
|
||||
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, OneDrive 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
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* Please note it won't cache generated images. if you don't do it yourself, it may hit you app's performance.
|
||||
|
||||
##### 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.
|
||||
|
||||
@@ -14,10 +14,14 @@ import CoreGraphics
|
||||
// in case of using this class with unencrypted HTTP connection.
|
||||
|
||||
open class DropboxFileProvider: NSObject, FileProviderBasicRemote {
|
||||
open static let type: String = "WebDAV"
|
||||
open static let type: String = "DropBox"
|
||||
open let isPathRelative: Bool = true
|
||||
open let baseURL: URL?
|
||||
open var currentPath: String = ""
|
||||
|
||||
open let apiURL = URL(string: "https://api.dropboxapi.com/2/")!
|
||||
open let contentURL = URL(string: "https://content.dropboxapi.com/2")!
|
||||
|
||||
open var dispatch_queue: DispatchQueue {
|
||||
willSet {
|
||||
assert(_session == nil, "It's not effective to change dispatch_queue property after session is initialized.")
|
||||
@@ -63,7 +67,7 @@ open class DropboxFileProvider: NSObject, FileProviderBasicRemote {
|
||||
}
|
||||
|
||||
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
|
||||
let url = URL(string: "https://api.dropboxapi.com/2/files/get_metadata")!
|
||||
let url = URL(string: "files/get_metadata", relativeTo: apiURL)!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
@@ -86,7 +90,7 @@ open class DropboxFileProvider: NSObject, FileProviderBasicRemote {
|
||||
}
|
||||
|
||||
open func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void)) {
|
||||
let url = URL(string: "https://api.dropboxapi.com/2/users/get_space_usage")!
|
||||
let url = URL(string: "users/get_space_usage", relativeTo: apiURL)!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
@@ -118,11 +122,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)
|
||||
}
|
||||
|
||||
@@ -139,17 +143,17 @@ extension DropboxFileProvider: FileProviderOperations {
|
||||
let destPath = operation.destination
|
||||
switch operation {
|
||||
case .create:
|
||||
url = "https://api.dropboxapi.com/2/files/create_folder"
|
||||
url = "files/create_folder"
|
||||
case .copy:
|
||||
url = "https://api.dropboxapi.com/2/files/copy"
|
||||
url = "files/copy"
|
||||
case .move:
|
||||
url = "https://api.dropboxapi.com/2/files/move"
|
||||
url = "files/move"
|
||||
case .remove:
|
||||
url = "https://api.dropboxapi.com/2/files/delete"
|
||||
url = "files/delete"
|
||||
default: // modify, link, fetch
|
||||
return nil
|
||||
}
|
||||
var request = URLRequest(url: URL(string: url)!)
|
||||
var request = URLRequest(url: URL(string: url, relativeTo: apiURL)!)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
@@ -174,17 +178,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? {
|
||||
@@ -192,17 +191,18 @@ extension DropboxFileProvider: FileProviderOperations {
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
let url = URL(string: "https://content.dropboxapi.com/2/files/download")!
|
||||
let url = URL(string: "files/download", relativeTo: contentURL)!
|
||||
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": 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,13 +220,9 @@ 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")!
|
||||
let url = URL(string: "files/download", relativeTo: contentURL)!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "GET"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
@@ -235,7 +231,7 @@ extension DropboxFileProvider: FileProviderReadWrite {
|
||||
} else if offset > 0 && length < 0 {
|
||||
request.setValue("bytes=\(offset)-", forHTTPHeaderField: "Range")
|
||||
}
|
||||
let requestDictionary = ["path": path]
|
||||
let requestDictionary = ["path": correctPath(path)! as NSString]
|
||||
request.setValue(dictionaryToJSON(requestDictionary as [String : AnyObject]), forHTTPHeaderField: "Dropbox-API-Arg")
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var dbError: FileProviderDropboxError?
|
||||
@@ -250,13 +246,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)) {
|
||||
@@ -282,7 +278,65 @@ extension DropboxFileProvider: FileProviderReadWrite {
|
||||
NotImplemented()
|
||||
}
|
||||
|
||||
// TODO: Implement /copy_reference, /get_temporary_link, /save_url, /get_account & /get_current_account
|
||||
// TODO: Implement /copy_reference, /get_account & /get_current_account
|
||||
}
|
||||
|
||||
extension DropboxFileProvider {
|
||||
open func temporaryLink(to path: String, completionHandler: @escaping ((_ link: URL?, _ attribute: DropboxFileObject?, _ error: Error?) -> Void)) {
|
||||
let url = URL(string: "files/get_temporary_link", relativeTo: apiURL)!
|
||||
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]
|
||||
request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: .utf8)
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var dbError: FileProviderDropboxError?
|
||||
var link: URL?
|
||||
var fileObject: DropboxFileObject?
|
||||
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) {
|
||||
if let linkStr = json["link"] as? String {
|
||||
link = URL(string: linkStr)
|
||||
}
|
||||
if let attribDic = json["metadata"] as? [String: AnyObject] {
|
||||
fileObject = self.mapToFileObject(attribDic)
|
||||
}
|
||||
}
|
||||
}
|
||||
completionHandler(link, fileObject, dbError ?? error)
|
||||
})
|
||||
task.resume()
|
||||
}
|
||||
|
||||
open func copyItem(path: String, toRemoteURL destURL: URL, completionHandler: @escaping ((_ jobId: String?, _ attribute: DropboxFileObject?, _ error: Error?) -> Void)) {
|
||||
let url = URL(string: "files/save_url", relativeTo: apiURL)!
|
||||
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, "url" : destURL.absoluteString as NSString]
|
||||
request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: .utf8)
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var dbError: FileProviderDropboxError?
|
||||
var jobId: String?
|
||||
var fileObject: DropboxFileObject?
|
||||
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) {
|
||||
jobId = json["async_job_id"] as? String
|
||||
if let attribDic = json["metadata"] as? [String: AnyObject] {
|
||||
fileObject = self.mapToFileObject(attribDic)
|
||||
}
|
||||
}
|
||||
}
|
||||
completionHandler(jobId, fileObject, dbError ?? error)
|
||||
})
|
||||
task.resume()
|
||||
}
|
||||
}
|
||||
|
||||
extension DropboxFileProvider: ExtendedFileProvider {
|
||||
@@ -290,32 +344,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":
|
||||
url = URL(string: "files/get_thumbnail", relativeTo: contentURL)!
|
||||
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: "files/get_preview", relativeTo: contentURL)!
|
||||
default:
|
||||
return
|
||||
}
|
||||
@@ -323,7 +388,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
|
||||
@@ -333,7 +400,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)
|
||||
})
|
||||
@@ -341,7 +414,27 @@ extension DropboxFileProvider: ExtendedFileProvider {
|
||||
}
|
||||
|
||||
public func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String : Any], _ keys: [String], _ error: Error?) -> Void)) {
|
||||
NotImplemented()
|
||||
let url = URL(string: "files/get_metadata", relativeTo: apiURL)!
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+105
-32
@@ -2,8 +2,8 @@
|
||||
// DropboxHelper.swift
|
||||
// FileProvider
|
||||
//
|
||||
// Created by Amir Abbas Mousavian on 5/18/95.
|
||||
//
|
||||
// Created by Amir Abbas Mousavian.
|
||||
// Copyright © 2016 Mousavian. Distributed under MIT license.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@@ -19,30 +19,48 @@ public struct FileProviderDropboxError: Error, CustomStringConvertible {
|
||||
}
|
||||
|
||||
public final class DropboxFileObject: FileObject {
|
||||
public let serverTime: Date?
|
||||
public let id: String?
|
||||
public let rev: String?
|
||||
|
||||
// codebeat:disable[ARITY]
|
||||
public init(name: String, path: String, size: Int64 = -1, serverTime: Date? = nil, modifiedDate: Date? = nil, fileType: FileType = .regular, isHidden: Bool = false, isReadOnly: Bool = false, id: String? = nil, rev: String? = nil) {
|
||||
self.serverTime = serverTime
|
||||
self.id = id
|
||||
self.rev = rev
|
||||
super.init(absoluteURL: URL(string: path), name: name, path: path, size: size, createdDate: nil, modifiedDate: modifiedDate, fileType: fileType, isHidden: isHidden, isReadOnly: isReadOnly)
|
||||
internal init(name: String, path: String) {
|
||||
super.init(absoluteURL: URL(string: path), name: name, path: path)
|
||||
}
|
||||
|
||||
open internal(set) var serverTime: Date? {
|
||||
get {
|
||||
return allValues["NSURLServerDateKey"] as? Date
|
||||
}
|
||||
set {
|
||||
allValues["NSURLServerDateKey"] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open internal(set) var id: String? {
|
||||
get {
|
||||
return allValues["NSURLDocumentIdentifyKey"] as? String
|
||||
}
|
||||
set {
|
||||
allValues["NSURLDocumentIdentifyKey"] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open internal(set) var rev: String? {
|
||||
get {
|
||||
return allValues[URLResourceKey.generationIdentifierKey.rawValue] as? String
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.generationIdentifierKey.rawValue] = newValue
|
||||
}
|
||||
}
|
||||
// codebeat:enable[ARITY]
|
||||
}
|
||||
|
||||
// codebeat:disable[ARITY]
|
||||
// codebeat:disable[ARITY]
|
||||
internal extension DropboxFileProvider {
|
||||
func list(_ path: String, cursor: String? = nil, prevContents: [DropboxFileObject] = [], recursive: Bool = false, completionHandler: @escaping ((_ contents: [FileObject], _ cursor: String?, _ error: Error?) -> Void)) {
|
||||
var requestDictionary = [String: AnyObject]()
|
||||
let url: URL
|
||||
if let cursor = cursor {
|
||||
url = URL(string: "https://api.dropboxapi.com/2/files/list_folder/continue")!
|
||||
url = URL(string: "files/list_folder/continue", relativeTo: apiURL)!
|
||||
requestDictionary["cursor"] = cursor as NSString?
|
||||
} else {
|
||||
url = URL(string: "https://api.dropboxapi.com/2/files/list_folder")!
|
||||
url = URL(string: "files/list_folder", relativeTo: apiURL)!
|
||||
requestDictionary["path"] = correctPath(path) as NSString?
|
||||
requestDictionary["recursive"] = recursive as NSNumber?
|
||||
}
|
||||
@@ -81,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")!
|
||||
url = URL(string: "files/upload", relativeTo: contentURL)!
|
||||
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?
|
||||
@@ -102,14 +118,39 @@ 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: "files/upload", relativeTo: contentURL)!
|
||||
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])
|
||||
}
|
||||
|
||||
func search(_ startPath: String = "", query: String, start: Int = 0, maxResultPerPage: Int = 25, maxResults: Int = -1, foundItem:@escaping ((_ file: DropboxFileObject) -> Void), completionHandler: @escaping ((_ error: Error?) -> Void)) {
|
||||
let url = URL(string: "https://api.dropboxapi.com/2/files/search")!
|
||||
let url = URL(string: "files/search", relativeTo: apiURL)!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
@@ -158,14 +199,46 @@ internal extension DropboxFileProvider {
|
||||
func mapToFileObject(_ json: [String: AnyObject]) -> DropboxFileObject? {
|
||||
guard let name = json["name"] as? String else { return nil }
|
||||
guard let path = json["path_display"] as? String else { return nil }
|
||||
let size = (json["size"] as? NSNumber)?.int64Value ?? -1
|
||||
let serverTime = resolve(dateString: json["server_modified"] as? String ?? "")
|
||||
let modifiedDate = resolve(dateString: json["client_modified"] as? String ?? "")
|
||||
let isDirectory = (json[".tag"] as? String) == "folder"
|
||||
let isReadonly = (json["sharing_info"]?["read_only"] as? NSNumber)?.boolValue ?? false
|
||||
let id = json["id"] as? String
|
||||
let rev = json["id"] as? String
|
||||
return DropboxFileObject(name: name, path: path, size: size, serverTime: serverTime, modifiedDate: modifiedDate, fileType: isDirectory ? .directory : .regular, isReadOnly: isReadonly, id: id, rev: rev)
|
||||
let fileObject = DropboxFileObject(name: name, path: path)
|
||||
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.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["rev"] 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?) {
|
||||
|
||||
@@ -0,0 +1,443 @@
|
||||
//
|
||||
// ExtendedLocalFileProvider.swift
|
||||
// FileProvider
|
||||
//
|
||||
// Created by Amir Abbas Mousavian.
|
||||
// Copyright © 2017 Mousavian. Distributed under MIT license.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import ImageIO
|
||||
import CoreGraphics
|
||||
import AVFoundation
|
||||
#if os(iOS) || os(tvOS)
|
||||
import UIKit
|
||||
#elseif os(macOS)
|
||||
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(macOS)
|
||||
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.
|
||||
|
||||
+220
-126
@@ -10,90 +10,11 @@ import Foundation
|
||||
#if os(iOS) || os(tvOS)
|
||||
import UIKit
|
||||
public typealias ImageClass = UIImage
|
||||
#elseif os(OSX)
|
||||
#elseif os(macOS)
|
||||
import Cocoa
|
||||
public typealias ImageClass = NSImage
|
||||
#endif
|
||||
|
||||
public enum FileType: String {
|
||||
case directory
|
||||
case regular
|
||||
case symbolicLink
|
||||
case socket
|
||||
case characterSpecial
|
||||
case blockSpecial
|
||||
case namedPipe
|
||||
case unknown
|
||||
|
||||
public init(urlResourceTypeValue: URLFileResourceType) {
|
||||
switch urlResourceTypeValue {
|
||||
case URLFileResourceType.namedPipe: self = .namedPipe
|
||||
case URLFileResourceType.characterSpecial: self = .characterSpecial
|
||||
case URLFileResourceType.directory: self = .directory
|
||||
case URLFileResourceType.blockSpecial: self = .blockSpecial
|
||||
case URLFileResourceType.regular: self = .regular
|
||||
case URLFileResourceType.symbolicLink: self = .symbolicLink
|
||||
case URLFileResourceType.socket: self = .socket
|
||||
case URLFileResourceType.unknown: self = .unknown
|
||||
default: self = .unknown
|
||||
}
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
|
||||
extension URLError.Code: FoundationErrorEnum {}
|
||||
extension CocoaError.Code: FoundationErrorEnum {}
|
||||
|
||||
open class FileObject {
|
||||
open let absoluteURL: URL?
|
||||
open let name: String
|
||||
open let path: String
|
||||
open let size: Int64
|
||||
open let createdDate: Date?
|
||||
open let modifiedDate: Date?
|
||||
open let fileType: FileType
|
||||
open let isHidden: Bool
|
||||
open let isReadOnly: Bool
|
||||
|
||||
public init(absoluteURL: URL? = nil, name: String, path: String, size: Int64 = -1, createdDate: Date? = nil, modifiedDate: Date? = nil, fileType: FileType = .regular, isHidden: Bool = false, isReadOnly: Bool = false) {
|
||||
self.absoluteURL = absoluteURL
|
||||
self.name = name
|
||||
self.path = path
|
||||
self.size = size
|
||||
self.createdDate = createdDate
|
||||
self.modifiedDate = modifiedDate
|
||||
self.fileType = fileType
|
||||
self.isHidden = isHidden
|
||||
self.isReadOnly = isReadOnly
|
||||
}
|
||||
|
||||
open var isDirectory: Bool {
|
||||
return self.fileType == .directory
|
||||
}
|
||||
|
||||
open var isSymLink: Bool {
|
||||
return self.fileType == .symbolicLink
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public typealias SimpleCompletionHandler = ((_ error: Error?) -> Void)?
|
||||
|
||||
public protocol FileProviderBasic: class {
|
||||
@@ -105,13 +26,12 @@ public protocol FileProviderBasic: class {
|
||||
var delegate: FileProviderDelegate? { get set }
|
||||
var credential: URLCredential? { get }
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void))
|
||||
func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void))
|
||||
|
||||
func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void))
|
||||
|
||||
func url(of path: String?) -> URL
|
||||
}
|
||||
|
||||
public protocol FileProviderBasicRemote: FileProviderBasic {
|
||||
@@ -177,29 +97,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)
|
||||
@@ -209,32 +180,39 @@ public protocol FileProviderMonitor: FileProviderBasic {
|
||||
public protocol FileProvider: FileProviderBasic, FileProviderOperations, FileProviderReadWrite, NSCopying {
|
||||
}
|
||||
|
||||
fileprivate let pathTrimSet = CharacterSet(charactersIn: " /")
|
||||
extension FileProviderBasic {
|
||||
public var type: String {
|
||||
return type(of: self).type
|
||||
return Self.type
|
||||
}
|
||||
|
||||
public var bareCurrentPath: String {
|
||||
return currentPath.trimmingCharacters(in: CharacterSet(charactersIn: ". /"))
|
||||
return currentPath.trimmingCharacters(in: pathTrimSet)
|
||||
}
|
||||
|
||||
func escaped(path: String) -> String {
|
||||
return path.trimmingCharacters(in: pathTrimSet).addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)!
|
||||
}
|
||||
|
||||
public func absoluteURL(_ path: String? = nil) -> URL {
|
||||
let rpath: String
|
||||
return url(of: path).absoluteURL
|
||||
}
|
||||
|
||||
public func url(of path: String? = nil) -> URL {
|
||||
var rpath: String
|
||||
if let path = path {
|
||||
rpath = path
|
||||
} else {
|
||||
rpath = self.currentPath
|
||||
}
|
||||
if isPathRelative, let baseURL = baseURL {
|
||||
if rpath.hasPrefix("/") && baseURL.absoluteString.hasSuffix("/") {
|
||||
var npath = rpath
|
||||
npath.remove(at: npath.startIndex)
|
||||
return baseURL.appendingPathComponent(npath)
|
||||
} else {
|
||||
return baseURL.appendingPathComponent(rpath)
|
||||
rpath = rpath.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? rpath
|
||||
if let baseURL = baseURL {
|
||||
if isPathRelative && rpath.hasPrefix("/") {
|
||||
rpath.remove(at: rpath.startIndex)
|
||||
}
|
||||
return URL(string: rpath, relativeTo: baseURL) ?? baseURL
|
||||
} else {
|
||||
return URL(fileURLWithPath: rpath).standardizedFileURL
|
||||
return URL(string: rpath)!
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,7 +225,7 @@ extension FileProviderBasic {
|
||||
guard let path = path else { return nil }
|
||||
var p = path.hasPrefix("/") ? path : "/" + path
|
||||
if p.hasSuffix("/") {
|
||||
p.remove(at: p.characters.index(before: p.endIndex))
|
||||
p.remove(at: p.index(before:p.endIndex))
|
||||
}
|
||||
return p
|
||||
}
|
||||
@@ -323,19 +301,150 @@ extension FileProviderBasic {
|
||||
if let isotime = dateFor.date(from: dateString) {
|
||||
return isotime
|
||||
}
|
||||
//self.init()
|
||||
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?, page: Int = 1) -> ImageClass? {
|
||||
guard let pdfData = pdfData else { return nil }
|
||||
|
||||
let cfPDFData: CFData = pdfData as CFData
|
||||
if let provider = CGDataProvider(data: cfPDFData), let reference = CGPDFDocument(provider), let pageRef = reference.page(at: page) {
|
||||
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(macOS)
|
||||
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(macOS)
|
||||
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)
|
||||
@@ -381,11 +490,13 @@ public enum FileOperationType: CustomStringConvertible {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public protocol OperationHandle {
|
||||
var operationType: FileOperationType { get }
|
||||
var bytesSoFar: Int64 { get }
|
||||
var totalBytes: Int64 { get }
|
||||
var inProgress: Bool { get }
|
||||
var progress: Float { get }
|
||||
func cancel() -> Bool
|
||||
}
|
||||
|
||||
@@ -397,13 +508,6 @@ public extension OperationHandle {
|
||||
}
|
||||
}
|
||||
|
||||
internal class Weak<T: AnyObject> {
|
||||
weak var value : T?
|
||||
init (_ value: T) {
|
||||
self.value = value
|
||||
}
|
||||
}
|
||||
|
||||
public protocol FileProviderDelegate: class {
|
||||
func fileproviderSucceed(_ fileProvider: FileProviderOperations, operation: FileOperationType)
|
||||
func fileproviderFailed(_ fileProvider: FileProviderOperations, operation: FileOperationType)
|
||||
@@ -419,27 +523,17 @@ 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) {
|
||||
self.value = value
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
public protocol FoundationErrorEnum {
|
||||
init? (rawValue: Int)
|
||||
var rawValue: Int { get }
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
extension URLError.Code: FoundationErrorEnum {}
|
||||
extension CocoaError.Code: FoundationErrorEnum {}
|
||||
|
||||
+32
-328
@@ -8,20 +8,10 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public final class LocalFileObject: FileObject {
|
||||
public let allocatedSize: Int64
|
||||
// codebeat:disable[ARITY]
|
||||
public init(absoluteURL: URL, name: String, path: String, size: Int64 = -1, allocatedSize: Int64 = 0, createdDate: Date? = nil, modifiedDate: Date? = nil, fileType: FileType = .regular, isHidden: Bool = false, isReadOnly: Bool = false) {
|
||||
self.allocatedSize = allocatedSize
|
||||
super.init(absoluteURL: absoluteURL, name: name, path: path, size: size, createdDate: createdDate, modifiedDate: modifiedDate, fileType: fileType, isHidden: isHidden, isReadOnly: isReadOnly)
|
||||
}
|
||||
// codebeat:enable[ARITY]
|
||||
}
|
||||
|
||||
open class LocalFileProvider: FileProvider, FileProviderMonitor {
|
||||
open static let type = "Local"
|
||||
open var isPathRelative: Bool = true
|
||||
open var baseURL: URL? = LocalFileProvider.defaultBaseURL()
|
||||
open private(set) var baseURL: URL? = LocalFileProvider.defaultBaseURL()
|
||||
open var currentPath: String = ""
|
||||
open var dispatch_queue: DispatchQueue
|
||||
open var operation_queue: DispatchQueue
|
||||
@@ -47,17 +37,18 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor {
|
||||
opFileManager.delegate = fileProviderManagerDelegate
|
||||
}
|
||||
|
||||
fileprivate static func defaultBaseURL() -> URL {
|
||||
let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true);
|
||||
open static func defaultBaseURL() -> URL {
|
||||
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true);
|
||||
return URL(fileURLWithPath: paths[0])
|
||||
}
|
||||
|
||||
open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {
|
||||
dispatch_queue.async {
|
||||
do {
|
||||
let contents = try self.fileManager.contentsOfDirectory(at: self.absoluteURL(path), includingPropertiesForKeys: [URLResourceKey.nameKey, URLResourceKey.fileSizeKey, URLResourceKey.fileAllocatedSizeKey, URLResourceKey.creationDateKey, URLResourceKey.contentModificationDateKey, URLResourceKey.isHiddenKey, URLResourceKey.volumeIsReadOnlyKey], options: FileManager.DirectoryEnumerationOptions.skipsSubdirectoryDescendants)
|
||||
let filesAttributes = contents.map({ (fileURL) -> LocalFileObject in
|
||||
return self.attributesOfItem(url: fileURL)
|
||||
let contents = try self.fileManager.contentsOfDirectory(at: self.absoluteURL(path), includingPropertiesForKeys: [.nameKey, .fileSizeKey, .fileAllocatedSizeKey, .creationDateKey, .contentModificationDateKey, .isHiddenKey, .volumeIsReadOnlyKey], options: .skipsSubdirectoryDescendants)
|
||||
let filesAttributes = contents.flatMap({ (fileURL) -> LocalFileObject? in
|
||||
let path = self.relativePathOf(url: fileURL)
|
||||
return LocalFileObject(fileWithPath: path, relativeTo: self.baseURL)
|
||||
})
|
||||
completionHandler(filesAttributes, nil)
|
||||
} catch let e as NSError {
|
||||
@@ -66,37 +57,16 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor {
|
||||
}
|
||||
}
|
||||
|
||||
internal func attributesOfItem(url fileURL: URL) -> LocalFileObject {
|
||||
var namev, sizev, allocated, filetypev, creationDatev, modifiedDatev, hiddenv, readonlyv: AnyObject?
|
||||
_ = try? (fileURL as NSURL).getResourceValue(&namev, forKey: URLResourceKey.nameKey)
|
||||
_ = try? (fileURL as NSURL).getResourceValue(&sizev, forKey: URLResourceKey.fileSizeKey)
|
||||
_ = try? (fileURL as NSURL).getResourceValue(&allocated, forKey: URLResourceKey.fileAllocatedSizeKey)
|
||||
_ = try? (fileURL as NSURL).getResourceValue(&creationDatev, forKey: URLResourceKey.creationDateKey)
|
||||
_ = try? (fileURL as NSURL).getResourceValue(&modifiedDatev, forKey: URLResourceKey.contentModificationDateKey)
|
||||
_ = try? (fileURL as NSURL).getResourceValue(&filetypev, forKey: URLResourceKey.fileResourceTypeKey)
|
||||
_ = try? (fileURL as NSURL).getResourceValue(&hiddenv, forKey: URLResourceKey.isHiddenKey)
|
||||
_ = try? (fileURL as NSURL).getResourceValue(&readonlyv, forKey: URLResourceKey.volumeIsReadOnlyKey)
|
||||
let path: String
|
||||
if isPathRelative {
|
||||
path = self.relativePathOf(url: fileURL)
|
||||
} else {
|
||||
path = fileURL.path
|
||||
}
|
||||
let filetype = URLFileResourceType(rawValue: filetypev as? String ?? "")
|
||||
let fileAttr = LocalFileObject(absoluteURL: fileURL, name: namev as! String, path: path, size: sizev?.int64Value ?? -1, allocatedSize: allocated?.int64Value ?? -1, createdDate: creationDatev as? Date, modifiedDate: modifiedDatev as? Date, fileType: FileType(urlResourceTypeValue: filetype), isHidden: hiddenv?.boolValue ?? false, isReadOnly: readonlyv?.boolValue ?? false)
|
||||
return fileAttr
|
||||
}
|
||||
|
||||
open func storageProperties(completionHandler: (@escaping (_ total: Int64, _ used: Int64) -> Void)) {
|
||||
let dict = (try? FileManager.default.attributesOfFileSystem(forPath: baseURL?.path ?? "/"))
|
||||
let totalSize = (dict?[FileAttributeKey.systemSize] as? NSNumber)?.int64Value ?? -1;
|
||||
let freeSize = (dict?[FileAttributeKey.systemFreeSize] as? NSNumber)?.int64Value ?? 0;
|
||||
let totalSize = (dict?[.systemSize] as? NSNumber)?.int64Value ?? -1;
|
||||
let freeSize = (dict?[.systemFreeSize] as? NSNumber)?.int64Value ?? 0;
|
||||
completionHandler(totalSize, totalSize - freeSize)
|
||||
}
|
||||
|
||||
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
|
||||
dispatch_queue.async {
|
||||
completionHandler(self.attributesOfItem(url: self.absoluteURL(path)), nil)
|
||||
completionHandler(LocalFileObject(fileWithPath: path, relativeTo: self.baseURL), nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,21 +97,8 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor {
|
||||
let opType = FileOperationType.create(path: (atPath as NSString).appendingPathComponent(fileName))
|
||||
operation_queue.async {
|
||||
let fileURL = self.absoluteURL(atPath).appendingPathComponent(fileName)
|
||||
/*var attributes = [String : Any]()
|
||||
if let createdDate = fileAttribs.createdDate {
|
||||
attributes[FileAttributeKey.creationDate.rawValue] = createdDate as NSDate
|
||||
}
|
||||
if let modDate = fileAttribs.modifiedDate {
|
||||
attributes[FileAttributeKey.modificationDate.rawValue] = modDate as NSDate
|
||||
}
|
||||
if fileAttribs.isReadOnly {
|
||||
attributes[FileAttributeKey.posixPermissions.rawValue] = NSNumber(value: 365 as Int16)
|
||||
}*/
|
||||
let success = self.opFileManager.createFile(atPath: fileURL.path, contents: data, attributes: nil/*attributes*/)
|
||||
let success = self.opFileManager.createFile(atPath: fileURL.path, contents: data, attributes: nil)
|
||||
if success {
|
||||
/*do {
|
||||
try (fileURL as NSURL).setResourceValue(fileAttribs.isHidden, forKey: URLResourceKey.isHiddenKey)
|
||||
} catch _ {}*/
|
||||
completionHandler?(nil)
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.delegate?.fileproviderSucceed(self, operation: opType)
|
||||
@@ -225,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 {
|
||||
@@ -275,10 +233,13 @@ 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
|
||||
guard !self.attributesOfItem(url: self.absoluteURL(path)).isDirectory && self.fileManager.fileExists(atPath: aPath) else {
|
||||
guard self.fileManager.fileExists(atPath: aPath) && !self.absoluteURL(path).fileIsDirectory else {
|
||||
completionHandler(nil, self.throwError(path, code: URLError.cannotOpenFile as FoundationErrorEnum))
|
||||
return
|
||||
}
|
||||
@@ -298,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: {
|
||||
@@ -318,9 +286,11 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor {
|
||||
var result = [LocalFileObject]()
|
||||
while let fileURL = iterator?.nextObject() as? URL {
|
||||
if fileURL.lastPathComponent.lowercased().contains(query.lowercased()) {
|
||||
let fileObject = self.attributesOfItem(url: fileURL)
|
||||
result.append(self.attributesOfItem(url: fileURL))
|
||||
foundItemHandler?(fileObject)
|
||||
let path = self.relativePathOf(url: fileURL)
|
||||
if let fileObject = LocalFileObject(fileWithPath: path, relativeTo: self.baseURL) {
|
||||
result.append(fileObject)
|
||||
foundItemHandler?(fileObject)
|
||||
}
|
||||
}
|
||||
}
|
||||
completionHandler(result, nil)
|
||||
@@ -332,12 +302,8 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor {
|
||||
open func registerNotifcation(path: String, eventHandler: @escaping (() -> Void)) {
|
||||
self.unregisterNotifcation(path: path)
|
||||
let absurl = self.absoluteURL(path)
|
||||
var isdirv: AnyObject?
|
||||
do {
|
||||
try (absurl as NSURL).getResourceValue(&isdirv, forKey: URLResourceKey.isDirectoryKey)
|
||||
} catch _ {
|
||||
}
|
||||
if !(isdirv?.boolValue ?? false) {
|
||||
let isdir = (try? absurl.resourceValues(forKeys: [.isDirectoryKey]).isDirectory ?? false) ?? false
|
||||
if !isdir {
|
||||
return
|
||||
}
|
||||
let monitor = LocalFolderMonitor(url: absurl) {
|
||||
@@ -390,265 +356,3 @@ public extension LocalFileProvider {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class LocalFileProviderManagerDelegate: NSObject, FileManagerDelegate {
|
||||
weak var provider: LocalFileProvider?
|
||||
|
||||
init(provider: LocalFileProvider) {
|
||||
self.provider = provider
|
||||
}
|
||||
|
||||
func fileManager(_ fileManager: FileManager, shouldCopyItemAt srcURL: URL, to dstURL: URL) -> Bool {
|
||||
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
|
||||
return true
|
||||
}
|
||||
let srcPath = provider.relativePathOf(url: srcURL)
|
||||
let dstPath = provider.relativePathOf(url: dstURL)
|
||||
return delegate.fileProvider(provider, shouldDoOperation: .copy(source: srcPath, destination: dstPath))
|
||||
}
|
||||
|
||||
func fileManager(_ fileManager: FileManager, shouldMoveItemAt srcURL: URL, to dstURL: URL) -> Bool {
|
||||
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
|
||||
return true
|
||||
}
|
||||
let srcPath = provider.relativePathOf(url: srcURL)
|
||||
let dstPath = provider.relativePathOf(url: dstURL)
|
||||
return delegate.fileProvider(provider, shouldDoOperation: .move(source: srcPath, destination: dstPath))
|
||||
}
|
||||
|
||||
func fileManager(_ fileManager: FileManager, shouldRemoveItemAt URL: URL) -> Bool {
|
||||
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
|
||||
return true
|
||||
}
|
||||
let path = provider.relativePathOf(url: URL)
|
||||
return delegate.fileProvider(provider, shouldDoOperation: .remove(path: path))
|
||||
}
|
||||
|
||||
func fileManager(_ fileManager: FileManager, shouldLinkItemAt srcURL: URL, to dstURL: URL) -> Bool {
|
||||
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
|
||||
return true
|
||||
}
|
||||
let srcPath = provider.relativePathOf(url: srcURL)
|
||||
let dstPath = provider.relativePathOf(url: dstURL)
|
||||
return delegate.fileProvider(provider, shouldDoOperation: .link(link: srcPath, target: dstPath))
|
||||
}
|
||||
|
||||
func fileManager(_ fileManager: FileManager, shouldProceedAfterError error: Error, copyingItemAt srcURL: URL, to dstURL: URL) -> Bool {
|
||||
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
|
||||
return false
|
||||
}
|
||||
let srcPath = provider.relativePathOf(url: srcURL)
|
||||
let dstPath = provider.relativePathOf(url: dstURL)
|
||||
return delegate.fileProvider(provider, shouldProceedAfterError: error, operation: .copy(source: srcPath, destination: dstPath))
|
||||
}
|
||||
|
||||
func fileManager(_ fileManager: FileManager, shouldProceedAfterError error: Error, movingItemAt srcURL: URL, to dstURL: URL) -> Bool {
|
||||
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
|
||||
return false
|
||||
}
|
||||
let srcPath = provider.relativePathOf(url: srcURL)
|
||||
let dstPath = provider.relativePathOf(url: dstURL)
|
||||
return delegate.fileProvider(provider, shouldProceedAfterError: error, operation: .move(source: srcPath, destination: dstPath))
|
||||
}
|
||||
|
||||
func fileManager(_ fileManager: FileManager, shouldProceedAfterError error: Error, removingItemAt URL: URL) -> Bool {
|
||||
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
|
||||
return false
|
||||
}
|
||||
let path = provider.relativePathOf(url: URL)
|
||||
return delegate.fileProvider(provider, shouldProceedAfterError: error, operation: .remove(path: path))
|
||||
}
|
||||
|
||||
func fileManager(_ fileManager: FileManager, shouldProceedAfterError error: Error, linkingItemAt srcURL: URL, to dstURL: URL) -> Bool {
|
||||
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
|
||||
return false
|
||||
}
|
||||
let srcPath = provider.relativePathOf(url: srcURL)
|
||||
let dstPath = provider.relativePathOf(url: dstURL)
|
||||
return delegate.fileProvider(provider, shouldProceedAfterError: error, operation: .link(link: srcPath, target: dstPath))
|
||||
}
|
||||
}
|
||||
|
||||
internal class LocalFolderMonitor {
|
||||
fileprivate let source: DispatchSourceFileSystemObject
|
||||
fileprivate let descriptor: CInt
|
||||
fileprivate let qq: DispatchQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.default)
|
||||
fileprivate var state: Bool = false
|
||||
fileprivate var monitoredTime: TimeInterval = Date().timeIntervalSinceReferenceDate
|
||||
var url: URL
|
||||
|
||||
/// Creates a folder monitor object with monitoring enabled.
|
||||
init(url: URL, handler: @escaping ()->Void) {
|
||||
self.url = url
|
||||
descriptor = open((url as NSURL).fileSystemRepresentation, O_EVTONLY)
|
||||
source = DispatchSource.makeFileSystemObjectSource(fileDescriptor: descriptor, eventMask: DispatchSource.FileSystemEvent.write, queue: qq)
|
||||
// Folder monitoring is recursive and deep. Monitoring a root folder may be very costly
|
||||
// We have a 0.2 second delay to ensure we wont call handler 1000s times when there is
|
||||
// a huge file operation. This ensures app will work smoothly while this 250 milisec won't
|
||||
// affect user experince much
|
||||
let main_handler: ()->Void = {
|
||||
if Date().timeIntervalSinceReferenceDate < self.monitoredTime + 0.2 {
|
||||
return
|
||||
}
|
||||
self.monitoredTime = Date().timeIntervalSinceReferenceDate
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.25, execute: {
|
||||
handler()
|
||||
})
|
||||
}
|
||||
source.setEventHandler(handler: main_handler)
|
||||
source.setCancelHandler {
|
||||
close(self.descriptor)
|
||||
}
|
||||
start()
|
||||
}
|
||||
|
||||
/// Starts sending notifications if currently stopped
|
||||
func start() {
|
||||
if !state {
|
||||
state = true
|
||||
source.resume()
|
||||
}
|
||||
}
|
||||
|
||||
/// Stops sending notifications if currently enabled
|
||||
func stop() {
|
||||
if state {
|
||||
state = false
|
||||
source.suspend()
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
source.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
open class LocalOperationHandle: OperationHandle {
|
||||
public let baseURL: URL
|
||||
public let operationType: FileOperationType
|
||||
|
||||
init (operationType: FileOperationType, baseURL: URL?) {
|
||||
self.baseURL = baseURL ?? LocalFileProvider.defaultBaseURL()
|
||||
self.operationType = operationType
|
||||
}
|
||||
|
||||
private var sourceURL: URL? {
|
||||
guard let source = operationType.source else { return nil }
|
||||
return source.hasPrefix("file://") ? URL(fileURLWithPath: source) : baseURL.appendingPathComponent(source)
|
||||
}
|
||||
|
||||
private var destURL: URL? {
|
||||
guard let dest = operationType.destination else { return nil }
|
||||
return dest.hasPrefix("file://") ? URL(fileURLWithPath: dest) : baseURL.appendingPathComponent(dest)
|
||||
}
|
||||
|
||||
/// Caution: may put pressure on CPU, may have latency
|
||||
open var bytesSoFar: Int64 {
|
||||
assert(!Thread.isMainThread, "Don't run \(#function) method on main thread")
|
||||
switch operationType {
|
||||
case .modify:
|
||||
guard let url = sourceURL, url.isFileURL else { return 0 }
|
||||
if url.fileIsDirectory {
|
||||
return iterateDirectory(url, deep: true).totalsize
|
||||
} else {
|
||||
return url.fileSize
|
||||
}
|
||||
case .copy, .move:
|
||||
guard let url = destURL, url.isFileURL else { return 0 }
|
||||
if url.fileIsDirectory {
|
||||
return iterateDirectory(url, deep: true).totalsize
|
||||
} else {
|
||||
return url.fileSize
|
||||
}
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Caution: may put pressure on CPU, may have latency
|
||||
open var totalBytes: Int64 {
|
||||
assert(!Thread.isMainThread, "Don't run \(#function) method on main thread")
|
||||
switch operationType {
|
||||
case .copy, .move:
|
||||
guard let url = sourceURL, url.isFileURL else { return 0 }
|
||||
if url.fileIsDirectory {
|
||||
return iterateDirectory(url, deep: true).totalsize
|
||||
} else {
|
||||
return url.fileSize
|
||||
}
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Not usable in local provider
|
||||
open var inProgress: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
/// Not usable in local provider
|
||||
open func cancel() -> Bool{
|
||||
return false
|
||||
}
|
||||
|
||||
func iterateDirectory(_ pathURL: URL, deep: Bool) -> (folders: Int, files: Int, totalsize: Int64) {
|
||||
var folders = 0
|
||||
var files = 0
|
||||
var totalsize: Int64 = 0
|
||||
let keys: [URLResourceKey] = [.isDirectoryKey, .fileSizeKey]
|
||||
let enumOpt: FileManager.DirectoryEnumerationOptions = !deep ? [.skipsSubdirectoryDescendants, .skipsPackageDescendants] : []
|
||||
|
||||
let fp = FileManager()
|
||||
let filesList = fp.enumerator(at: pathURL, includingPropertiesForKeys: keys, options: enumOpt, errorHandler: nil)
|
||||
while let fileURL = filesList?.nextObject() as? URL {
|
||||
var isdirv, sizev: AnyObject?
|
||||
do {
|
||||
try (fileURL as NSURL).getResourceValue(&isdirv, forKey: URLResourceKey.isDirectoryKey)
|
||||
} catch _ {
|
||||
}
|
||||
do {
|
||||
try (fileURL as NSURL).getResourceValue(&sizev, forKey: URLResourceKey.fileSizeKey)
|
||||
} catch _ {
|
||||
}
|
||||
let isdir = isdirv?.boolValue ?? false
|
||||
let size = sizev?.int64Value ?? 0
|
||||
if isdir {
|
||||
folders += 1
|
||||
} else {
|
||||
files += 1
|
||||
}
|
||||
totalsize += size
|
||||
}
|
||||
|
||||
return (folders, files, totalsize)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
internal extension URL {
|
||||
var fileIsDirectory: Bool {
|
||||
var isdirv: AnyObject?
|
||||
do {
|
||||
try (self as NSURL).getResourceValue(&isdirv, forKey: URLResourceKey.isDirectoryKey)
|
||||
} catch _ {
|
||||
}
|
||||
return isdirv?.boolValue ?? false
|
||||
}
|
||||
|
||||
var fileSize: Int64 {
|
||||
var sizev: AnyObject?
|
||||
do {
|
||||
try (self as NSURL).getResourceValue(&sizev, forKey: URLResourceKey.fileSizeKey)
|
||||
} catch _ {
|
||||
}
|
||||
return sizev?.int64Value ?? -1
|
||||
}
|
||||
|
||||
var fileExists: Bool {
|
||||
if self.isFileURL {
|
||||
return FileManager.default.fileExists(atPath: self.path)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
//
|
||||
// LocalFileProvider.swift
|
||||
// FileProvider
|
||||
//
|
||||
// Created by Amir Abbas Mousavian.
|
||||
// Copyright © 2016 Mousavian. Distributed under MIT license.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public final class LocalFileObject: FileObject {
|
||||
internal init(absoluteURL: URL, name: String, path: String) {
|
||||
super.init(absoluteURL: absoluteURL, name: name, path: path)
|
||||
}
|
||||
|
||||
public convenience init? (fileWithPath path: String, relativeTo relativeURL: URL?) {
|
||||
let fileURL: URL
|
||||
if let relativeURL = relativeURL {
|
||||
fileURL = relativeURL.appendingPathComponent(path)
|
||||
} else {
|
||||
fileURL = URL(fileURLWithPath: path)
|
||||
}
|
||||
do {
|
||||
let values = try fileURL.resourceValues(forKeys: [.nameKey, .fileSizeKey, .fileAllocatedSizeKey, .creationDateKey, .contentModificationDateKey, .fileResourceTypeKey, .isHiddenKey, .isWritableKey])
|
||||
self.init(absoluteURL: fileURL, name: values.name ?? fileURL.lastPathComponent, path: path)
|
||||
for (key, value) in values.allValues {
|
||||
self.allValues[key.rawValue] = value
|
||||
}
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public convenience init?(fileWithURL fileURL: URL) {
|
||||
do {
|
||||
let values = try fileURL.resourceValues(forKeys: [.nameKey, .fileSizeKey, .fileAllocatedSizeKey, .creationDateKey, .contentModificationDateKey, .fileResourceTypeKey, .isHiddenKey, .isWritableKey, .typeIdentifierKey])
|
||||
self.init(absoluteURL: fileURL, name: values.name ?? fileURL.lastPathComponent, path: fileURL.path)
|
||||
for (key, value) in values.allValues {
|
||||
self.allValues[key.rawValue] = value
|
||||
}
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
open internal(set) var allocatedSize: Int64 {
|
||||
get {
|
||||
return allValues[URLResourceKey.fileAllocatedSizeKey.rawValue] as? Int64 ?? 0
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.fileAllocatedSizeKey.rawValue] = Int(exactly: newValue) ?? Int.max
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class LocalFolderMonitor {
|
||||
fileprivate let source: DispatchSourceFileSystemObject
|
||||
fileprivate let descriptor: CInt
|
||||
fileprivate let qq: DispatchQueue = DispatchQueue.global(qos: .default)
|
||||
fileprivate var state: Bool = false
|
||||
fileprivate var monitoredTime: TimeInterval = Date().timeIntervalSinceReferenceDate
|
||||
var url: URL
|
||||
|
||||
/// Creates a folder monitor object with monitoring enabled.
|
||||
init(url: URL, handler: @escaping ()->Void) {
|
||||
self.url = url
|
||||
descriptor = open((url as NSURL).fileSystemRepresentation, O_EVTONLY)
|
||||
source = DispatchSource.makeFileSystemObjectSource(fileDescriptor: descriptor, eventMask: DispatchSource.FileSystemEvent.write, queue: qq)
|
||||
// Folder monitoring is recursive and deep. Monitoring a root folder may be very costly
|
||||
// We have a 0.2 second delay to ensure we wont call handler 1000s times when there is
|
||||
// a huge file operation. This ensures app will work smoothly while this 250 milisec won't
|
||||
// affect user experince much
|
||||
let main_handler: ()->Void = {
|
||||
if Date().timeIntervalSinceReferenceDate < self.monitoredTime + 0.2 {
|
||||
return
|
||||
}
|
||||
self.monitoredTime = Date().timeIntervalSinceReferenceDate
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.25, execute: {
|
||||
handler()
|
||||
})
|
||||
}
|
||||
source.setEventHandler(handler: main_handler)
|
||||
source.setCancelHandler {
|
||||
close(self.descriptor)
|
||||
}
|
||||
start()
|
||||
}
|
||||
|
||||
/// Starts sending notifications if currently stopped
|
||||
func start() {
|
||||
if !state {
|
||||
state = true
|
||||
source.resume()
|
||||
}
|
||||
}
|
||||
|
||||
/// Stops sending notifications if currently enabled
|
||||
func stop() {
|
||||
if state {
|
||||
state = false
|
||||
source.suspend()
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
source.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
internal class LocalFileProviderManagerDelegate: NSObject, FileManagerDelegate {
|
||||
weak var provider: LocalFileProvider?
|
||||
|
||||
init(provider: LocalFileProvider) {
|
||||
self.provider = provider
|
||||
}
|
||||
|
||||
func fileManager(_ fileManager: FileManager, shouldCopyItemAt srcURL: URL, to dstURL: URL) -> Bool {
|
||||
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
|
||||
return true
|
||||
}
|
||||
let srcPath = provider.relativePathOf(url: srcURL)
|
||||
let dstPath = provider.relativePathOf(url: dstURL)
|
||||
return delegate.fileProvider(provider, shouldDoOperation: .copy(source: srcPath, destination: dstPath))
|
||||
}
|
||||
|
||||
func fileManager(_ fileManager: FileManager, shouldMoveItemAt srcURL: URL, to dstURL: URL) -> Bool {
|
||||
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
|
||||
return true
|
||||
}
|
||||
let srcPath = provider.relativePathOf(url: srcURL)
|
||||
let dstPath = provider.relativePathOf(url: dstURL)
|
||||
return delegate.fileProvider(provider, shouldDoOperation: .move(source: srcPath, destination: dstPath))
|
||||
}
|
||||
|
||||
func fileManager(_ fileManager: FileManager, shouldRemoveItemAt URL: URL) -> Bool {
|
||||
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
|
||||
return true
|
||||
}
|
||||
let path = provider.relativePathOf(url: URL)
|
||||
return delegate.fileProvider(provider, shouldDoOperation: .remove(path: path))
|
||||
}
|
||||
|
||||
func fileManager(_ fileManager: FileManager, shouldLinkItemAt srcURL: URL, to dstURL: URL) -> Bool {
|
||||
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
|
||||
return true
|
||||
}
|
||||
let srcPath = provider.relativePathOf(url: srcURL)
|
||||
let dstPath = provider.relativePathOf(url: dstURL)
|
||||
return delegate.fileProvider(provider, shouldDoOperation: .link(link: srcPath, target: dstPath))
|
||||
}
|
||||
|
||||
func fileManager(_ fileManager: FileManager, shouldProceedAfterError error: Error, copyingItemAt srcURL: URL, to dstURL: URL) -> Bool {
|
||||
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
|
||||
return false
|
||||
}
|
||||
let srcPath = provider.relativePathOf(url: srcURL)
|
||||
let dstPath = provider.relativePathOf(url: dstURL)
|
||||
return delegate.fileProvider(provider, shouldProceedAfterError: error, operation: .copy(source: srcPath, destination: dstPath))
|
||||
}
|
||||
|
||||
func fileManager(_ fileManager: FileManager, shouldProceedAfterError error: Error, movingItemAt srcURL: URL, to dstURL: URL) -> Bool {
|
||||
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
|
||||
return false
|
||||
}
|
||||
let srcPath = provider.relativePathOf(url: srcURL)
|
||||
let dstPath = provider.relativePathOf(url: dstURL)
|
||||
return delegate.fileProvider(provider, shouldProceedAfterError: error, operation: .move(source: srcPath, destination: dstPath))
|
||||
}
|
||||
|
||||
func fileManager(_ fileManager: FileManager, shouldProceedAfterError error: Error, removingItemAt URL: URL) -> Bool {
|
||||
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
|
||||
return false
|
||||
}
|
||||
let path = provider.relativePathOf(url: URL)
|
||||
return delegate.fileProvider(provider, shouldProceedAfterError: error, operation: .remove(path: path))
|
||||
}
|
||||
|
||||
func fileManager(_ fileManager: FileManager, shouldProceedAfterError error: Error, linkingItemAt srcURL: URL, to dstURL: URL) -> Bool {
|
||||
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
|
||||
return false
|
||||
}
|
||||
let srcPath = provider.relativePathOf(url: srcURL)
|
||||
let dstPath = provider.relativePathOf(url: dstURL)
|
||||
return delegate.fileProvider(provider, shouldProceedAfterError: error, operation: .link(link: srcPath, target: dstPath))
|
||||
}
|
||||
}
|
||||
|
||||
open class LocalOperationHandle: OperationHandle {
|
||||
public let baseURL: URL
|
||||
public let operationType: FileOperationType
|
||||
|
||||
init (operationType: FileOperationType, baseURL: URL?) {
|
||||
self.baseURL = baseURL ?? LocalFileProvider.defaultBaseURL()
|
||||
self.operationType = operationType
|
||||
}
|
||||
|
||||
private var sourceURL: URL? {
|
||||
guard let source = operationType.source else { return nil }
|
||||
return source.hasPrefix("file://") ? URL(fileURLWithPath: source) : baseURL.appendingPathComponent(source)
|
||||
}
|
||||
|
||||
private var destURL: URL? {
|
||||
guard let dest = operationType.destination else { return nil }
|
||||
return dest.hasPrefix("file://") ? URL(fileURLWithPath: dest) : baseURL.appendingPathComponent(dest)
|
||||
}
|
||||
|
||||
/// Caution: may put pressure on CPU, may have latency
|
||||
open var bytesSoFar: Int64 {
|
||||
assert(!Thread.isMainThread, "Don't run \(#function) method on main thread")
|
||||
switch operationType {
|
||||
case .modify:
|
||||
guard let url = sourceURL, url.isFileURL else { return 0 }
|
||||
if url.fileIsDirectory {
|
||||
return iterateDirectory(url, deep: true).totalsize
|
||||
} else {
|
||||
return url.fileSize
|
||||
}
|
||||
case .copy, .move:
|
||||
guard let url = destURL, url.isFileURL else { return 0 }
|
||||
if url.fileIsDirectory {
|
||||
return iterateDirectory(url, deep: true).totalsize
|
||||
} else {
|
||||
return url.fileSize
|
||||
}
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Caution: may put pressure on CPU, may have latency
|
||||
open var totalBytes: Int64 {
|
||||
assert(!Thread.isMainThread, "Don't run \(#function) method on main thread")
|
||||
switch operationType {
|
||||
case .copy, .move:
|
||||
guard let url = sourceURL, url.isFileURL else { return 0 }
|
||||
if url.fileIsDirectory {
|
||||
return iterateDirectory(url, deep: true).totalsize
|
||||
} else {
|
||||
return url.fileSize
|
||||
}
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Not usable in local provider
|
||||
open var inProgress: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
/// Not usable in local provider
|
||||
open func cancel() -> Bool{
|
||||
return false
|
||||
}
|
||||
|
||||
func iterateDirectory(_ pathURL: URL, deep: Bool) -> (folders: Int, files: Int, totalsize: Int64) {
|
||||
var folders = 0
|
||||
var files = 0
|
||||
var totalsize: Int64 = 0
|
||||
let keys: [URLResourceKey] = [.isDirectoryKey, .fileSizeKey]
|
||||
let enumOpt: FileManager.DirectoryEnumerationOptions = !deep ? [.skipsSubdirectoryDescendants, .skipsPackageDescendants] : []
|
||||
|
||||
let fp = FileManager()
|
||||
let filesList = fp.enumerator(at: pathURL, includingPropertiesForKeys: keys, options: enumOpt, errorHandler: nil)
|
||||
while let fileURL = filesList?.nextObject() as? URL {
|
||||
do {
|
||||
let values = try fileURL.resourceValues(forKeys: [.isDirectoryKey, .fileSizeKey])
|
||||
let isdir = values.isDirectory ?? false
|
||||
let size = Int64(values.fileSize ?? 0)
|
||||
if isdir {
|
||||
folders += 1
|
||||
} else {
|
||||
files += 1
|
||||
}
|
||||
totalsize += size
|
||||
} catch _ {
|
||||
}
|
||||
}
|
||||
|
||||
return (folders, files, totalsize)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
internal extension URL {
|
||||
var fileIsDirectory: Bool {
|
||||
return (try? self.resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory ?? false
|
||||
}
|
||||
|
||||
var fileSize: Int64 {
|
||||
return Int64((try? self.resourceValues(forKeys: [.fileSizeKey]))?.fileSize ?? -1)
|
||||
}
|
||||
|
||||
var fileExists: Bool {
|
||||
return self.isFileURL && FileManager.default.fileExists(atPath: self.path)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,352 @@
|
||||
|
||||
//
|
||||
// OneDriveFileProvider.swift
|
||||
// FileProvider
|
||||
//
|
||||
// Created by Amir Abbas Mousavian.
|
||||
// Copyright © 2017 Mousavian. Distributed under MIT license.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreGraphics
|
||||
|
||||
// Because this class uses NSURLSession, it's necessary to disable App Transport Security
|
||||
// in case of using this class with unencrypted HTTP connection.
|
||||
|
||||
open class OneDriveFileProvider: NSObject, FileProviderBasicRemote {
|
||||
open static let type: String = "OneDrive"
|
||||
open let isPathRelative: Bool = true
|
||||
open let baseURL: URL?
|
||||
open var drive: String
|
||||
open var driveURL: URL {
|
||||
return URL(string: "/drive/root:/", relativeTo: baseURL)!
|
||||
}
|
||||
open var currentPath: String = ""
|
||||
|
||||
open var dispatch_queue: DispatchQueue {
|
||||
willSet {
|
||||
assert(_session == nil, "It's not effective to change dispatch_queue property after session is initialized.")
|
||||
}
|
||||
}
|
||||
open weak var delegate: FileProviderDelegate?
|
||||
open let credential: URLCredential?
|
||||
open private(set) var cache: URLCache?
|
||||
public var useCache: Bool = false
|
||||
public var validatingCache: Bool = true
|
||||
|
||||
fileprivate var _session: URLSession?
|
||||
fileprivate var sessionDelegate: SessionDelegate?
|
||||
public var session: URLSession {
|
||||
if _session == nil {
|
||||
self.sessionDelegate = SessionDelegate(fileProvider: self, credential: credential)
|
||||
let queue = OperationQueue()
|
||||
//queue.underlyingQueue = dispatch_queue
|
||||
let config = URLSessionConfiguration.default
|
||||
config.urlCache = cache
|
||||
config.requestCachePolicy = .returnCacheDataElseLoad
|
||||
_session = URLSession(configuration: config, delegate: sessionDelegate as URLSessionDelegate?, delegateQueue: queue)
|
||||
}
|
||||
return _session!
|
||||
}
|
||||
|
||||
public init? (baseURL: URL?, drive: String = "root", credential: URLCredential?, cache: URLCache? = nil) {
|
||||
self.baseURL = baseURL ?? URL(string: "https://api.onedrive.com")
|
||||
self.drive = drive
|
||||
dispatch_queue = DispatchQueue(label: "FileProvider.\(OneDriveFileProvider.type)", attributes: DispatchQueue.Attributes.concurrent)
|
||||
//let url = baseURL.uw_absoluteString
|
||||
self.credential = credential
|
||||
self.cache = cache
|
||||
}
|
||||
|
||||
deinit {
|
||||
_session?.invalidateAndCancel()
|
||||
}
|
||||
|
||||
open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {
|
||||
list(path) { (contents, cursor, error) in
|
||||
completionHandler(contents, error)
|
||||
}
|
||||
}
|
||||
|
||||
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
|
||||
let url = URL(string: escaped(path: path), relativeTo: driveURL)!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "GET"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var dbError: FileProviderOneDriveError?
|
||||
var fileObject: OneDriveFileObject?
|
||||
if let response = response as? HTTPURLResponse {
|
||||
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
|
||||
dbError = code != nil ? FileProviderOneDriveError(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 file = self.mapToFileObject(json) {
|
||||
fileObject = file
|
||||
}
|
||||
}
|
||||
completionHandler(fileObject, dbError ?? error)
|
||||
})
|
||||
task.resume()
|
||||
}
|
||||
|
||||
open func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void)) {
|
||||
let url = URL(string: "/drive/root", relativeTo: baseURL)!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "GET"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var totalSize: Int64 = -1
|
||||
var usedSize: Int64 = 0
|
||||
if let data = data, let jsonStr = String(data: data, encoding: .utf8), let json = jsonToDictionary(jsonStr) {
|
||||
totalSize = (json["total"] as? NSNumber)?.int64Value ?? -1
|
||||
usedSize = (json["used"] as? NSNumber)?.int64Value ?? 0
|
||||
}
|
||||
completionHandler(totalSize, usedSize)
|
||||
})
|
||||
task.resume()
|
||||
}
|
||||
|
||||
open weak var fileOperationDelegate: FileOperationDelegate?
|
||||
}
|
||||
|
||||
extension OneDriveFileProvider: FileProviderOperations {
|
||||
|
||||
|
||||
public func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let path = (atPath as NSString).appendingPathComponent(folderName) + "/"
|
||||
return doOperation(.create(path: path), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
public func create(file fileName: String, at path: String, contents data: Data?, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let filePath = (path as NSString).appendingPathComponent(fileName)
|
||||
return self.writeContents(path: filePath, contents: data ?? Data(), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
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, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
return doOperation(.copy(source: path, destination: toPath), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
public func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
return doOperation(.remove(path: path), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
fileprivate func doOperation(_ operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: operation) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
guard let sourcePath = operation.source else { return nil }
|
||||
let destPath = operation.destination
|
||||
var request = URLRequest(url: URL(string: sourcePath, relativeTo: driveURL)!)
|
||||
switch operation {
|
||||
case .create:
|
||||
request.httpMethod = "CREATE"
|
||||
case .copy:
|
||||
request.httpMethod = "POST"
|
||||
case .move:
|
||||
request.httpMethod = "PATCH"
|
||||
case .remove:
|
||||
request.httpMethod = "DELETE"
|
||||
default: // modify, link, fetch
|
||||
return nil
|
||||
}
|
||||
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
var requestDictionary = [String: AnyObject]()
|
||||
if let dest = correctPath(destPath) as NSString? {
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
requestDictionary["parentReference"] = ("/drive/\(drive):" + dest.deletingLastPathComponent) as NSString
|
||||
requestDictionary["name"] = dest.lastPathComponent as NSString
|
||||
request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: .utf8)
|
||||
}
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var dbError: FileProviderOneDriveError?
|
||||
if let response = response as? HTTPURLResponse, response.statusCode >= 300, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) {
|
||||
dbError = FileProviderOneDriveError(code: code, path: sourcePath, errorDescription: String(data: data ?? Data(), encoding: .utf8))
|
||||
}
|
||||
completionHandler?(dbError ?? error)
|
||||
self.delegateNotify(operation, error: dbError ?? error)
|
||||
})
|
||||
task.taskDescription = operation.json
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: operation, tasks: [task])
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
return upload_simple(toPath, localFile: localFile, overwrite: overwrite, operation: opType, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
public func copyItem(path: String, toLocalURL destURL: URL, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.copy(source: path, destination: destURL.absoluteString)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
let url = URL(string: escaped(path: path) + ":/content", relativeTo: driveURL)!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "GET"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
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 errorData : Data? = nil //Data(contentsOf:cacheURL) // TODO: Figure out how to get error response data for the error description
|
||||
let dbError : FileProviderOneDriveError? = code != nil ? FileProviderOneDriveError(code: code!, path: path, errorDescription: String(data: errorData ?? Data(), encoding: .utf8)) : nil
|
||||
completionHandler?(dbError ?? error)
|
||||
return
|
||||
}
|
||||
do {
|
||||
try FileManager.default.moveItem(at: cacheURL, to: destURL)
|
||||
completionHandler?(nil)
|
||||
} catch let e {
|
||||
completionHandler?(e)
|
||||
}
|
||||
})
|
||||
task.taskDescription = opType.json
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: opType, tasks: [task])
|
||||
}
|
||||
}
|
||||
|
||||
extension OneDriveFileProvider: FileProviderReadWrite {
|
||||
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: escaped(path: path) + ":/content", relativeTo: driveURL)!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "GET"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
if length > 0 {
|
||||
request.setValue("bytes=\(offset)-\(offset + length)", forHTTPHeaderField: "Range")
|
||||
} else if offset > 0 && length < 0 {
|
||||
request.setValue("bytes=\(offset)-", forHTTPHeaderField: "Range")
|
||||
}
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var dbError: FileProviderOneDriveError?
|
||||
if let httpResponse = response as? HTTPURLResponse , httpResponse.statusCode >= 300, let code = FileProviderHTTPErrorCode(rawValue: httpResponse.statusCode) {
|
||||
dbError = FileProviderOneDriveError(code: code, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8))
|
||||
}
|
||||
let filedata = dbError ?? error == nil ? data : nil
|
||||
completionHandler(filedata, dbError ?? error)
|
||||
})
|
||||
task.taskDescription = opType.json
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: opType, tasks: [task])
|
||||
}
|
||||
|
||||
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: overwrite, operation: opType, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
public func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
|
||||
var foundFiles = [OneDriveFileObject]()
|
||||
search(path, query: query, foundItem: { (file) in
|
||||
foundFiles.append(file)
|
||||
foundItemHandler?(file)
|
||||
}, completionHandler: { (error) in
|
||||
completionHandler(foundFiles, error)
|
||||
})
|
||||
}
|
||||
|
||||
fileprivate func registerNotifcation(path: String, eventHandler: (() -> Void)) {
|
||||
/* There is two ways to monitor folders changing in OneDrive. Either using webooks
|
||||
* which means you have to implement a server to translate it to push notifications
|
||||
* or using apiv2 list_folder/longpoll method. The second one is implemeted here.
|
||||
* Tough webhooks are much more efficient, longpoll is much simpler to implement!
|
||||
* You can implemnt your own webhook service and replace this method accordingly.
|
||||
*/
|
||||
NotImplemented()
|
||||
}
|
||||
fileprivate func unregisterNotifcation(path: String) {
|
||||
NotImplemented()
|
||||
}
|
||||
|
||||
// TODO: Implement /copy_reference, /get_account & /get_current_account
|
||||
}
|
||||
|
||||
|
||||
extension OneDriveFileProvider: ExtendedFileProvider {
|
||||
public func thumbnailOfFileSupported(path: String) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
public func propertiesOfFileSupported(path: String) -> Bool {
|
||||
let fileExt = (path as NSString).pathExtension.lowercased()
|
||||
switch fileExt {
|
||||
case "jpg", "jpeg", "bmp", "gif", "png", "tif", "tiff":
|
||||
return true
|
||||
case "mp3", "aac", "m4a", "wma":
|
||||
return true
|
||||
case "mp4", "mpg", "3gp", "mov", "avi", "wmv":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public func thumbnailOfFile(path: String, dimension: CGSize?, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void)) {
|
||||
let url: URL
|
||||
if let dimension = dimension {
|
||||
url = URL(string: escaped(path: path) + ":/thumbnails/0/=c\(dimension.width)x\(dimension.height)/content", relativeTo: driveURL)!
|
||||
} else {
|
||||
url = URL(string: escaped(path: path) + ":/thumbnails/0/small/content", relativeTo: driveURL)!
|
||||
}
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
let task = self.session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var image: ImageClass? = nil
|
||||
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
let responseError = FileProviderOneDriveError(code: rCode, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8))
|
||||
completionHandler(nil, responseError)
|
||||
return
|
||||
}
|
||||
if let data = data {
|
||||
image = ImageClass(data: data)
|
||||
}
|
||||
completionHandler(image, error)
|
||||
})
|
||||
task.resume()
|
||||
}
|
||||
|
||||
public func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String : Any], _ keys: [String], _ error: Error?) -> Void)) {
|
||||
let url = URL(string: escaped(path: path), relativeTo: driveURL)!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "GET"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var dbError: FileProviderOneDriveError?
|
||||
var dic = [String: Any]()
|
||||
var keys = [String]()
|
||||
if let response = response as? HTTPURLResponse {
|
||||
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
|
||||
dbError = code != nil ? FileProviderOneDriveError(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) {
|
||||
(dic, keys) = self.mapMediaInfo(json)
|
||||
}
|
||||
}
|
||||
completionHandler(dic, keys, dbError ?? error)
|
||||
})
|
||||
task.resume()
|
||||
}
|
||||
}
|
||||
|
||||
extension OneDriveFileProvider: FileProvider {
|
||||
open func copy(with zone: NSZone? = nil) -> Any {
|
||||
let copy = OneDriveFileProvider(baseURL: self.baseURL, drive: self.drive, credential: self.credential, cache: self.cache)!
|
||||
copy.currentPath = self.currentPath
|
||||
copy.delegate = self.delegate
|
||||
copy.fileOperationDelegate = self.fileOperationDelegate
|
||||
copy.useCache = self.useCache
|
||||
copy.validatingCache = self.validatingCache
|
||||
return copy
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
//
|
||||
// OneDriveHelper.swift
|
||||
// FileProvider
|
||||
//
|
||||
// Created by Amir Abbas Mousavian.
|
||||
// Copyright © 2017 Mousavian. Distributed under MIT license.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct FileProviderOneDriveError: Error, CustomStringConvertible {
|
||||
public let code: FileProviderHTTPErrorCode
|
||||
public let path: String
|
||||
public let errorDescription: String?
|
||||
|
||||
public var description: String {
|
||||
return code.description
|
||||
}
|
||||
}
|
||||
|
||||
public final class OneDriveFileObject: FileObject {
|
||||
internal init(name: String, path: String) {
|
||||
super.init(absoluteURL: URL(string: path), name: name, path: path)
|
||||
}
|
||||
|
||||
|
||||
open internal(set) var id: String? {
|
||||
get {
|
||||
return allValues["NSURLDocumentIdentifyKey"] as? String
|
||||
}
|
||||
set {
|
||||
allValues["NSURLDocumentIdentifyKey"] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open internal(set) var contentType: String {
|
||||
get {
|
||||
return allValues["NSURLContentTypeKey"] as? String ?? ""
|
||||
}
|
||||
set {
|
||||
allValues["NSURLContentTypeKey"] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open internal(set) var entryTag: String? {
|
||||
get {
|
||||
return allValues["NSURLEntryTagKey"] as? String
|
||||
}
|
||||
set {
|
||||
allValues["NSURLEntryTagKey"] = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// codebeat:disable[ARITY]
|
||||
internal extension OneDriveFileProvider {
|
||||
func list(_ path: String, cursor: String? = nil, prevContents: [OneDriveFileObject] = [], completionHandler: @escaping ((_ contents: [FileObject], _ cursor: String?, _ error: Error?) -> Void)) {
|
||||
let url: URL
|
||||
if let cursor = cursor {
|
||||
url = URL(string: cursor)!
|
||||
} else {
|
||||
url = URL(string: escaped(path: path), relativeTo: driveURL)!
|
||||
}
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "GET"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var responseError: FileProviderOneDriveError?
|
||||
var files = prevContents
|
||||
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = FileProviderOneDriveError(code: rCode, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8))
|
||||
}
|
||||
if let data = data, let jsonStr = String(data: data, encoding: .utf8) {
|
||||
let json = jsonToDictionary(jsonStr)
|
||||
if let entries = json?["value"] as? [AnyObject] , entries.count > 0 {
|
||||
for entry in entries {
|
||||
if let entry = entry as? [String: AnyObject], let file = self.mapToFileObject(entry) {
|
||||
files.append(file)
|
||||
}
|
||||
}
|
||||
let ncursor = json?["@odata.nextLink"] as? String
|
||||
let hasmore = ncursor != nil
|
||||
if hasmore {
|
||||
self.list(path, cursor: ncursor, prevContents: files, completionHandler: completionHandler)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
completionHandler(files, nil, responseError ?? error)
|
||||
})
|
||||
task.taskDescription = FileOperationType.fetch(path: path).json
|
||||
task.resume()
|
||||
}
|
||||
|
||||
func upload_simple(_ targetPath: String, data: Data, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
assert(data.count < 100*1024*1024, "Maximum size of allowed size to upload is 100MB")
|
||||
let queryStr = overwrite ? "" : "?@name.conflictBehavior=fail"
|
||||
let url = URL(string: escaped(path: targetPath) + ":/content" + queryStr, relativeTo: driveURL)!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "PUT"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
|
||||
request.httpBody = data
|
||||
let task = session.uploadTask(with: request, from: data, completionHandler: { (data, response, error) in
|
||||
var responseError: FileProviderOneDriveError?
|
||||
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = FileProviderOneDriveError(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])
|
||||
}
|
||||
|
||||
func upload_simple(_ targetPath: String, localFile: URL, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let queryStr = overwrite ? "" : "?@name.conflictBehavior=fail"
|
||||
let url = URL(string: escaped(path: targetPath) + ":/content" + queryStr, relativeTo: driveURL)!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "PUT"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
|
||||
let task = session.uploadTask(with: request, fromFile: localFile, completionHandler: { (data, response, error) in
|
||||
var responseError: FileProviderOneDriveError?
|
||||
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = FileProviderOneDriveError(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])
|
||||
}
|
||||
|
||||
func search(_ startPath: String = "", query: String, next: String? = nil, foundItem:@escaping ((_ file: OneDriveFileObject) -> Void), completionHandler: @escaping ((_ error: Error?) -> Void)) {
|
||||
let url: URL
|
||||
if let next = next {
|
||||
url = URL(string: next)!
|
||||
} else if self.escaped(path: startPath) == "" {
|
||||
url = URL(string: "/drive/\(drive)/view.search?q=\(query)", relativeTo: baseURL)!
|
||||
} else {
|
||||
url = URL(string: "\(escaped(path: startPath))/view.search?q=\(query)", relativeTo: driveURL)!
|
||||
}
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "GET"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var responseError: FileProviderOneDriveError?
|
||||
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = FileProviderOneDriveError(code: rCode, path: startPath, errorDescription: String(data: data ?? Data(), encoding: .utf8))
|
||||
}
|
||||
if let data = data, let jsonStr = String(data: data, encoding: .utf8) {
|
||||
let json = jsonToDictionary(jsonStr)
|
||||
if let entries = json?["value"] as? [AnyObject] , entries.count > 0 {
|
||||
for entry in entries {
|
||||
if let entry = entry as? [String: AnyObject], let file = self.mapToFileObject(entry) {
|
||||
foundItem(file)
|
||||
}
|
||||
}
|
||||
let next = json?["@odata.nextLink"] as? String
|
||||
let hasmore = next != nil
|
||||
if hasmore, let next = next {
|
||||
self.search(startPath, query: query, next: next, foundItem: foundItem, completionHandler: completionHandler)
|
||||
} else {
|
||||
completionHandler(responseError ?? error)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
completionHandler(responseError ?? error)
|
||||
})
|
||||
task.resume()
|
||||
}
|
||||
}
|
||||
// codebeat:enable[ARITY]
|
||||
|
||||
internal extension OneDriveFileProvider {
|
||||
func mapToFileObject(_ jsonStr: String) -> OneDriveFileObject? {
|
||||
guard let json = jsonToDictionary(jsonStr) else { return nil }
|
||||
return self.mapToFileObject(json)
|
||||
}
|
||||
|
||||
func mapToFileObject(_ json: [String: AnyObject]) -> OneDriveFileObject? {
|
||||
guard let name = json["name"] as? String else { return nil }
|
||||
guard let path = (json["parentReference"] as? NSDictionary)?["path"] as? String else { return nil }
|
||||
let lPath = path.replacingOccurrences(of: "/drive/\(drive):", with: "/", options: .anchored, range: nil)
|
||||
let fileObject = OneDriveFileObject(name: name, path: lPath)
|
||||
if let webURL = json["webUrl"] as? String, let absolluteURL = URL(string: webURL) {
|
||||
fileObject.absoluteURL = absolluteURL
|
||||
}
|
||||
fileObject.size = (json["size"] as? NSNumber)?.int64Value ?? -1
|
||||
fileObject.modifiedDate = resolve(dateString: json["lastModifiedDateTime"] as? String ?? "")
|
||||
fileObject.creationDate = resolve(dateString: json["createdDateTime"] as? String ?? "")
|
||||
fileObject.type = (json["folder"] as? String) != nil ? .directory : .regular
|
||||
fileObject.id = json["id"] as? String
|
||||
fileObject.entryTag = json["eTag"] as? String
|
||||
return fileObject
|
||||
}
|
||||
|
||||
static let dateFormatter = DateFormatter()
|
||||
static let decimalFormatter = NumberFormatter()
|
||||
|
||||
func mapMediaInfo(_ json: [String: Any]) -> (dictionary: [String: Any], keys: [String]) {
|
||||
|
||||
func spaceCamelCase(_ text: String) -> String {
|
||||
var newString: String = ""
|
||||
|
||||
let upperCase = CharacterSet.uppercaseLetters
|
||||
for scalar in text.unicodeScalars {
|
||||
if upperCase.contains(scalar) {
|
||||
newString.append(" ")
|
||||
}
|
||||
let character = Character(scalar)
|
||||
newString.append(character)
|
||||
}
|
||||
|
||||
return newString.capitalized
|
||||
}
|
||||
|
||||
var dic = [String: Any]()
|
||||
var keys = [String]()
|
||||
|
||||
if let parent = json["image"] as? [String: Any] ?? json["video"] as? [String: Any], let height = parent["height"] as? UInt64, let width = parent["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 {
|
||||
|
||||
OneDriveFileProvider.decimalFormatter.numberStyle = .decimal
|
||||
OneDriveFileProvider.decimalFormatter.maximumFractionDigits = 5
|
||||
keys.append("Location")
|
||||
let latStr = OneDriveFileProvider.decimalFormatter.string(from: NSNumber(value: latitude))
|
||||
let longStr = OneDriveFileProvider.decimalFormatter.string(from: NSNumber(value: longitude))
|
||||
dic["Location"] = "\(latStr), \(longStr)"
|
||||
}
|
||||
if let parent = json["image"] as? [String: Any] ?? json["video"] as? [String: Any], let duration = parent["duration"] as? UInt64 {
|
||||
keys.append("Duration")
|
||||
dic["Duration"] = OneDriveFileProvider.formatshort(interval: TimeInterval(duration) / 1000)
|
||||
}
|
||||
if let timeTakenStr = json["takenDateTime"] as? String, let timeTaken = self.resolve(dateString: timeTakenStr) {
|
||||
keys.append("Date taken")
|
||||
OneDriveFileProvider.dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
||||
dic["Date taken"] = OneDriveFileProvider.dateFormatter.string(from: timeTaken)
|
||||
}
|
||||
|
||||
if let photo = json["photo"] as? [String: Any] {
|
||||
if let devicemake = photo["cameraMake"] as? String {
|
||||
keys.append("Device make")
|
||||
dic["Device make"] = devicemake
|
||||
}
|
||||
if let devicemodel = photo["cameraModel"] as? String {
|
||||
keys.append("Device model")
|
||||
dic["Device model"] = devicemodel
|
||||
}
|
||||
if let focallen = photo["focalLength"] as? Double {
|
||||
keys.append("Focal length")
|
||||
dic["Focal length"] = focallen
|
||||
}
|
||||
if let fnum = photo["fNumber"] as? Double {
|
||||
keys.append("F number")
|
||||
dic["F number"] = fnum
|
||||
}
|
||||
if let expNom = photo["exposureNumerator"] as? Double, let expDen = photo["exposureDenominator"] as? Double {
|
||||
keys.append("Exposure time")
|
||||
dic["Exposure time"] = "\(Int(expNom))/\(Int(expDen))"
|
||||
}
|
||||
if let iso = photo["iso"] as? Int64 {
|
||||
keys.append("ISO speed")
|
||||
dic["ISO speed"] = iso
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if let audio = json["audio"] as? [String: Any] {
|
||||
for (key, value) in audio {
|
||||
if key == "bitrate" || key == "isVariableBitrate" { continue }
|
||||
let casedKey = spaceCamelCase(key)
|
||||
keys.append(casedKey)
|
||||
dic[casedKey] = value
|
||||
}
|
||||
}
|
||||
|
||||
if let video = json["video"] as? [String: Any] {
|
||||
if let bitRate = video["bitrate"] as? Int {
|
||||
keys.append("Bitrate")
|
||||
dic["Bitrate"] = bitRate
|
||||
}
|
||||
}
|
||||
|
||||
return (dic, keys)
|
||||
}
|
||||
|
||||
func delegateNotify(_ operation: FileOperationType, error: Error?) {
|
||||
DispatchQueue.main.async(execute: {
|
||||
if error == nil {
|
||||
self.delegate?.fileproviderSucceed(self, operation: operation)
|
||||
} else {
|
||||
self.delegate?.fileproviderFailed(self, operation: operation)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
|
||||
@@ -9,16 +9,27 @@
|
||||
import Foundation
|
||||
|
||||
public final class WebDavFileObject: FileObject {
|
||||
public let contentType: String
|
||||
public let entryTag: String?
|
||||
|
||||
// codebeat:disable[ARITY]
|
||||
public init(absoluteURL: URL, name: String, path: String, size: Int64 = -1, contentType: String = "", createdDate: Date? = nil, modifiedDate: Date? = nil, fileType: FileType = .regular, isHidden: Bool = false, isReadOnly: Bool = false, entryTag: String? = nil) {
|
||||
self.contentType = contentType
|
||||
self.entryTag = entryTag
|
||||
super.init(absoluteURL: absoluteURL, name: name, path: path, size: size, createdDate: createdDate, modifiedDate: modifiedDate, fileType: fileType, isHidden: isHidden, isReadOnly: isReadOnly)
|
||||
internal init(absoluteURL: URL, name: String, path: String) {
|
||||
super.init(absoluteURL: absoluteURL, name: name, path: path)
|
||||
}
|
||||
|
||||
open internal(set) var contentType: String {
|
||||
get {
|
||||
return allValues["NSURLContentTypeKey"] as? String ?? ""
|
||||
}
|
||||
set {
|
||||
allValues["NSURLContentTypeKey"] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open internal(set) var entryTag: String? {
|
||||
get {
|
||||
return allValues["NSURLEntryTagKey"] as? String
|
||||
}
|
||||
set {
|
||||
allValues["NSURLEntryTagKey"] = newValue
|
||||
}
|
||||
}
|
||||
// codebeat:enable[ARITY]
|
||||
}
|
||||
|
||||
/// Because this class uses NSURLSession, it's necessary to disable App Transport Security
|
||||
@@ -181,7 +192,7 @@ extension WebDAVFileProvider: FileProviderOperations {
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
let url = absoluteURL(path)
|
||||
let url = absoluteURL(path).appendingPathComponent(fileName)
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "PUT"
|
||||
let task = session.uploadTask(with: request, from: data, completionHandler: { (data, response, error) in
|
||||
@@ -203,7 +214,7 @@ extension WebDAVFileProvider: FileProviderOperations {
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
return self.copyMoveItem(operation: opType, overwrite: overwrite, completionHandler: completionHandler)
|
||||
return self.doOperation(operation: opType, overwrite: overwrite, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
@@ -212,17 +223,36 @@ extension WebDAVFileProvider: FileProviderOperations {
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
return self.copyMoveItem(operation: opType, overwrite: overwrite, completionHandler: completionHandler)
|
||||
return self.doOperation(operation: opType, overwrite: overwrite, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
fileprivate func copyMoveItem(operation opType: FileOperationType, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
guard let source = opType.source, let dest = opType.destination else { return nil }
|
||||
// Using switch is more readable than using reflect. Maybe some
|
||||
let sourceURL = absoluteURL(source)
|
||||
@discardableResult
|
||||
public func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.remove(path: path)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
return self.doOperation(operation: opType, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
func doOperation(operation opType: FileOperationType, overwrite: Bool? = nil, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let sourceURL = absoluteURL(opType.source!)
|
||||
var request = URLRequest(url: sourceURL)
|
||||
request.httpMethod = opType.description.uppercased() // "COPY" or "MOVE"
|
||||
request.setValue(absoluteURL(dest).absoluteString, forHTTPHeaderField: "Destination")
|
||||
if !overwrite {
|
||||
if let dest = opType.destination {
|
||||
request.setValue(absoluteURL(dest).absoluteString, forHTTPHeaderField: "Destination")
|
||||
}
|
||||
switch opType {
|
||||
case .copy:
|
||||
request.httpMethod = "COPY"
|
||||
case .move:
|
||||
request.httpMethod = "MOVE"
|
||||
case .remove:
|
||||
request.httpMethod = "DELETE"
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
if let overwrite = overwrite, !overwrite {
|
||||
request.setValue("F", forHTTPHeaderField: "Overwrite")
|
||||
}
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
@@ -250,39 +280,8 @@ extension WebDAVFileProvider: FileProviderOperations {
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.remove(path: path)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
let url = absoluteURL(path)
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "DELETE"
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var responseError: FileProviderWebDavError?
|
||||
if let response = response as? HTTPURLResponse, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) {
|
||||
if response.statusCode >= 300 {
|
||||
responseError = FileProviderWebDavError(code: code, url: url)
|
||||
}
|
||||
if code == .multiStatus, let data = data {
|
||||
let xresponses = self.parseXMLResponse(data)
|
||||
for xresponse in xresponses where (xresponse.status ?? 0) >= 300 {
|
||||
completionHandler?(FileProviderWebDavError(code: code, url: url))
|
||||
}
|
||||
}
|
||||
}
|
||||
if (response as? HTTPURLResponse)?.statusCode ?? 0 != FileProviderHTTPErrorCode.multiStatus.rawValue {
|
||||
completionHandler?(responseError ?? error)
|
||||
}
|
||||
self.delegateNotify(opType, error: responseError ?? error)
|
||||
})
|
||||
task.taskDescription = opType.json
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: opType, tasks: [task])
|
||||
}
|
||||
|
||||
@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
|
||||
@@ -334,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)
|
||||
@@ -362,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
|
||||
@@ -371,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) {
|
||||
@@ -537,13 +534,14 @@ internal extension WebDAVFileProvider {
|
||||
href = absoluteURL(href.path)
|
||||
}
|
||||
let name = davResponse.prop["displayname"] ?? (davResponse.hrefString.removingPercentEncoding! as NSString).lastPathComponent
|
||||
let size = Int64(davResponse.prop["getcontentlength"] ?? "-1") ?? NSURLSessionTransferSizeUnknown
|
||||
let createdDate = self.resolve(dateString: davResponse.prop["creationdate"] ?? "")
|
||||
let modifiedDate = self.resolve(dateString: davResponse.prop["getlastmodified"] ?? "")
|
||||
let contentType = davResponse.prop["getcontenttype"] ?? "octet/stream"
|
||||
let isDirectory = contentType == "httpd/unix-directory"
|
||||
let entryTag = davResponse.prop["getetag"]
|
||||
return WebDavFileObject(absoluteURL: href, name: name, path: href.path, size: size, contentType: contentType, createdDate: createdDate, modifiedDate: modifiedDate, fileType: isDirectory ? .directory : .regular, isHidden: false, isReadOnly: false, entryTag: entryTag)
|
||||
let fileObject = WebDavFileObject(absoluteURL: href, name: name, path: href.path)
|
||||
fileObject.size = Int64(davResponse.prop["getcontentlength"] ?? "-1") ?? NSURLSessionTransferSizeUnknown
|
||||
fileObject.creationDate = self.resolve(dateString: davResponse.prop["creationdate"] ?? "")
|
||||
fileObject.modifiedDate = self.resolve(dateString: davResponse.prop["getlastmodified"] ?? "")
|
||||
fileObject.contentType = davResponse.prop["getcontenttype"] ?? "octet/stream"
|
||||
fileObject.type = fileObject.contentType == "httpd/unix-directory" ? .directory : .regular
|
||||
fileObject.entryTag = davResponse.prop["getetag"]
|
||||
return fileObject
|
||||
}
|
||||
|
||||
fileprivate func delegateNotify(_ operation: FileOperationType, error: Error?) {
|
||||
|
||||
Reference in New Issue
Block a user