Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b35c066de | |||
| 7d8de9cefe | |||
| e84fef20ea | |||
| 4a9a3196a2 | |||
| f4e6d277ae | |||
| 1328a8e9e2 | |||
| 5bf620916f | |||
| 4f56e20441 | |||
| 9dda618b73 | |||
| da60c05188 | |||
| 4366855d54 | |||
| fe05fd83fe | |||
| 826d207e6b | |||
| 66fc1e1284 | |||
| a1d489f5a5 | |||
| ad2699cd19 | |||
| 97ae86cedb | |||
| 71b07cac1b | |||
| a15f8f3809 | |||
| dc1270d8d1 | |||
| 68b1e23be3 | |||
| 057c9fd940 | |||
| 6d63322779 | |||
| fc6b46d17a | |||
| bb9c08e309 |
@@ -0,0 +1 @@
|
||||
3.0
|
||||
+18
-1
@@ -1,10 +1,27 @@
|
||||
language: objective-c
|
||||
osx_image: xcode8
|
||||
xcode_project: FileProvider.xcodeproj
|
||||
env:
|
||||
global:
|
||||
- FRAMEWORK_NAME=FileProvider.framework
|
||||
before_install:
|
||||
- gem install xcpretty --no-rdoc --no-ri --no-document --quiet
|
||||
- brew update
|
||||
- brew outdated carthage || brew upgrade carthage
|
||||
script:
|
||||
- set pipefail
|
||||
- xcodebuild -version
|
||||
- xcodebuild -project FileProvider.xcodeproj -scheme "FileProvider OSX" -sdk macosx | xcpretty
|
||||
- xcodebuild -project FileProvider.xcodeproj -scheme "FileProvider iOS" -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO | xcpretty
|
||||
- xcodebuild -project FileProvider.xcodeproj -scheme "FileProvider tvOS" -sdk appletvsimulator ONLY_ACTIVE_ARCH=NO | xcpretty
|
||||
- pod lib lint --quick
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
before_deploy:
|
||||
- carthage build --no-skip-current
|
||||
- carthage archive FileProvider
|
||||
deploy:
|
||||
file: FileProvider.framework.zip
|
||||
skip_cleanup: true
|
||||
on:
|
||||
tags: true
|
||||
@@ -16,8 +16,8 @@ Pod::Spec.new do |s|
|
||||
#
|
||||
|
||||
s.name = "FileProvider"
|
||||
s.version = "0.5.0"
|
||||
s.summary = "FileManager replacement for Local and Remote (WebDAV/Dropbox/SMB2) files on iOS and MacOS."
|
||||
s.version = "0.8.0"
|
||||
s.summary = "FileManager replacement for Local and Remote (WebDAV/Dropbox/SMB2) files on iOS and macOS."
|
||||
|
||||
# This description is used to generate tags and improve search results.
|
||||
# * Think: What does it do? Why did you write it? What is the focus?
|
||||
@@ -26,8 +26,8 @@ Pod::Spec.new do |s|
|
||||
# * Finally, don't worry about the indent, CocoaPods strips it!
|
||||
s.description = <<-DESC
|
||||
This Swift library provide a swifty way to deal with local and remote files
|
||||
and directories in same way. For now Local and WebDAV providers are ready to use
|
||||
and SMB2, Dropbox, FTP and AmazonS3 is planned for future.
|
||||
and directories in same way. For now Local, WebDAV and Dropbox providers are ready to use.
|
||||
SMB2, FTP and AmazonS3 is planned for future.
|
||||
DESC
|
||||
|
||||
s.homepage = "https://github.com/amosavian/FileProvider"
|
||||
|
||||
@@ -7,12 +7,10 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
7902C0861D61B56D00564440 /* SessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7902C0851D61B56D00564440 /* SessionDelegate.swift */; };
|
||||
7902C0871D61B67100564440 /* SessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7902C0851D61B56D00564440 /* SessionDelegate.swift */; };
|
||||
7902C0881D61B67100564440 /* SessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7902C0851D61B56D00564440 /* SessionDelegate.swift */; };
|
||||
7924B1931D89DAE000589DB7 /* AEXML.h in Headers */ = {isa = PBXBuildFile; fileRef = 7924B18C1D89DAE000589DB7 /* AEXML.h */; };
|
||||
7924B1941D89DAE000589DB7 /* AEXML.h in Headers */ = {isa = PBXBuildFile; fileRef = 7924B18C1D89DAE000589DB7 /* AEXML.h */; };
|
||||
7924B1951D89DAE000589DB7 /* AEXML.h in Headers */ = {isa = PBXBuildFile; fileRef = 7924B18C1D89DAE000589DB7 /* AEXML.h */; };
|
||||
7902C0861D61B56D00564440 /* RemoteSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7902C0851D61B56D00564440 /* RemoteSession.swift */; };
|
||||
7902C0871D61B67100564440 /* RemoteSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7902C0851D61B56D00564440 /* RemoteSession.swift */; };
|
||||
7902C0881D61B67100564440 /* RemoteSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7902C0851D61B56D00564440 /* RemoteSession.swift */; };
|
||||
791950F51DE58A5400B4426E /* libxml2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 791950F41DE58A5400B4426E /* libxml2.tbd */; };
|
||||
7924B1961D89DAE000589DB7 /* Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B18D1D89DAE000589DB7 /* Document.swift */; };
|
||||
7924B1971D89DAE000589DB7 /* Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B18D1D89DAE000589DB7 /* Document.swift */; };
|
||||
7924B1981D89DAE000589DB7 /* Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B18D1D89DAE000589DB7 /* Document.swift */; };
|
||||
@@ -22,9 +20,6 @@
|
||||
7924B19C1D89DAE000589DB7 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B18F1D89DAE000589DB7 /* Error.swift */; };
|
||||
7924B19D1D89DAE000589DB7 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B18F1D89DAE000589DB7 /* Error.swift */; };
|
||||
7924B19E1D89DAE000589DB7 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B18F1D89DAE000589DB7 /* Error.swift */; };
|
||||
7924B19F1D89DAE000589DB7 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7924B1901D89DAE000589DB7 /* Info.plist */; };
|
||||
7924B1A01D89DAE000589DB7 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7924B1901D89DAE000589DB7 /* Info.plist */; };
|
||||
7924B1A11D89DAE000589DB7 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7924B1901D89DAE000589DB7 /* Info.plist */; };
|
||||
7924B1A21D89DAE000589DB7 /* Options.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B1911D89DAE000589DB7 /* Options.swift */; };
|
||||
7924B1A31D89DAE000589DB7 /* Options.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B1911D89DAE000589DB7 /* Options.swift */; };
|
||||
7924B1A41D89DAE000589DB7 /* Options.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B1911D89DAE000589DB7 /* Options.swift */; };
|
||||
@@ -37,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 */; };
|
||||
@@ -97,15 +95,15 @@
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
7902C0851D61B56D00564440 /* SessionDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionDelegate.swift; sourceTree = "<group>"; };
|
||||
7924B18C1D89DAE000589DB7 /* AEXML.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AEXML.h; sourceTree = "<group>"; };
|
||||
7902C0851D61B56D00564440 /* RemoteSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteSession.swift; sourceTree = "<group>"; };
|
||||
791950F41DE58A5400B4426E /* libxml2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libxml2.tbd; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.1.sdk/usr/lib/libxml2.tbd; sourceTree = DEVELOPER_DIR; };
|
||||
7924B18D1D89DAE000589DB7 /* Document.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Document.swift; sourceTree = "<group>"; };
|
||||
7924B18E1D89DAE000589DB7 /* Element.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Element.swift; sourceTree = "<group>"; };
|
||||
7924B18F1D89DAE000589DB7 /* Error.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = "<group>"; };
|
||||
7924B1901D89DAE000589DB7 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
@@ -140,6 +138,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
791950F51DE58A5400B4426E /* libxml2.tbd in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -160,14 +159,20 @@
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
791950F31DE58A5300B4426E /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
791950F41DE58A5400B4426E /* libxml2.tbd */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7924B18B1D89DAE000589DB7 /* AEXML */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7924B18C1D89DAE000589DB7 /* AEXML.h */,
|
||||
7924B18D1D89DAE000589DB7 /* Document.swift */,
|
||||
7924B18E1D89DAE000589DB7 /* Element.swift */,
|
||||
7924B18F1D89DAE000589DB7 /* Error.swift */,
|
||||
7924B1901D89DAE000589DB7 /* Info.plist */,
|
||||
7924B1911D89DAE000589DB7 /* Options.swift */,
|
||||
7924B1921D89DAE000589DB7 /* Parser.swift */,
|
||||
);
|
||||
@@ -180,6 +185,7 @@
|
||||
799396911D48C02300086753 /* Sources */,
|
||||
7993968A1D48B8C700086753 /* Pod */,
|
||||
799396681D48B7F600086753 /* Products */,
|
||||
791950F31DE58A5300B4426E /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -206,14 +212,15 @@
|
||||
799396911D48C02300086753 /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7924B18B1D89DAE000589DB7 /* AEXML */,
|
||||
799396991D48C02300086753 /* SMBTypes */,
|
||||
799396931D48C02300086753 /* DropboxFileProvider.swift */,
|
||||
7924B18B1D89DAE000589DB7 /* AEXML */,
|
||||
794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */,
|
||||
799396941D48C02300086753 /* FileProvider.h */,
|
||||
799396951D48C02300086753 /* FileProvider.swift */,
|
||||
799396961D48C02300086753 /* LocalFileProvider.swift */,
|
||||
7902C0851D61B56D00564440 /* SessionDelegate.swift */,
|
||||
792572401DF23BDA006A1526 /* LocalHelper.swift */,
|
||||
7902C0851D61B56D00564440 /* RemoteSession.swift */,
|
||||
7924B1A81D89F79200589DB7 /* FPSStreamTask.swift */,
|
||||
799396971D48C02300086753 /* SMBClient.swift */,
|
||||
799396981D48C02300086753 /* SMBFileProvider.swift */,
|
||||
@@ -249,7 +256,6 @@
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
7924B1931D89DAE000589DB7 /* AEXML.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -257,7 +263,6 @@
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
7924B1941D89DAE000589DB7 /* AEXML.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -265,7 +270,6 @@
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
7924B1951D89DAE000589DB7 /* AEXML.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -332,7 +336,7 @@
|
||||
7993965C1D48B7BF00086753 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 0800;
|
||||
LastUpgradeCheck = 0810;
|
||||
TargetAttributes = {
|
||||
799396661D48B7F600086753 = {
|
||||
CreatedOnToolsVersion = 7.3.1;
|
||||
@@ -370,7 +374,6 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
7924B19F1D89DAE000589DB7 /* Info.plist in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -378,7 +381,6 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
7924B1A01D89DAE000589DB7 /* Info.plist in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -386,7 +388,6 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
7924B1A11D89DAE000589DB7 /* Info.plist in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -400,6 +401,7 @@
|
||||
799396B31D48C02300086753 /* LocalFileProvider.swift in Sources */,
|
||||
799396D41D48C02300086753 /* SMB2Tree.swift in Sources */,
|
||||
7924B1A21D89DAE000589DB7 /* Options.swift in Sources */,
|
||||
792572411DF23BDA006A1526 /* LocalHelper.swift in Sources */,
|
||||
7924B1991D89DAE000589DB7 /* Element.swift in Sources */,
|
||||
799396C81D48C02300086753 /* SMB2IOCtl.swift in Sources */,
|
||||
799396D71D48C02300086753 /* SMB2Types.swift in Sources */,
|
||||
@@ -413,7 +415,7 @@
|
||||
794C220A1D5893F800EC49B8 /* SMB2Notification.swift in Sources */,
|
||||
799396D11D48C02300086753 /* SMB2SetInfo.swift in Sources */,
|
||||
7924B1961D89DAE000589DB7 /* Document.swift in Sources */,
|
||||
7902C0861D61B56D00564440 /* SessionDelegate.swift in Sources */,
|
||||
7902C0861D61B56D00564440 /* RemoteSession.swift in Sources */,
|
||||
799396CE1D48C02300086753 /* SMB2Session.swift in Sources */,
|
||||
799396E01D48C02300086753 /* WebDAVFileProvider.swift in Sources */,
|
||||
799396DA1D48C02300086753 /* SMBErrorType.swift in Sources */,
|
||||
@@ -434,6 +436,7 @@
|
||||
799396B41D48C02300086753 /* LocalFileProvider.swift in Sources */,
|
||||
799396D51D48C02300086753 /* SMB2Tree.swift in Sources */,
|
||||
7924B1A31D89DAE000589DB7 /* Options.swift in Sources */,
|
||||
792572421DF23BDA006A1526 /* LocalHelper.swift in Sources */,
|
||||
7924B1B01D89F7DE00589DB7 /* FPSStreamTask.swift in Sources */,
|
||||
7924B19A1D89DAE000589DB7 /* Element.swift in Sources */,
|
||||
799396C91D48C02300086753 /* SMB2IOCtl.swift in Sources */,
|
||||
@@ -447,7 +450,7 @@
|
||||
794C220B1D5893F800EC49B8 /* SMB2Notification.swift in Sources */,
|
||||
799396D21D48C02300086753 /* SMB2SetInfo.swift in Sources */,
|
||||
7924B1971D89DAE000589DB7 /* Document.swift in Sources */,
|
||||
7902C0871D61B67100564440 /* SessionDelegate.swift in Sources */,
|
||||
7902C0871D61B67100564440 /* RemoteSession.swift in Sources */,
|
||||
799396CF1D48C02300086753 /* SMB2Session.swift in Sources */,
|
||||
799396E11D48C02300086753 /* WebDAVFileProvider.swift in Sources */,
|
||||
799396DB1D48C02300086753 /* SMBErrorType.swift in Sources */,
|
||||
@@ -468,6 +471,7 @@
|
||||
799396B51D48C02300086753 /* LocalFileProvider.swift in Sources */,
|
||||
799396D61D48C02300086753 /* SMB2Tree.swift in Sources */,
|
||||
7924B1A41D89DAE000589DB7 /* Options.swift in Sources */,
|
||||
792572431DF23BDA006A1526 /* LocalHelper.swift in Sources */,
|
||||
7924B1B11D89F7DF00589DB7 /* FPSStreamTask.swift in Sources */,
|
||||
7924B19B1D89DAE000589DB7 /* Element.swift in Sources */,
|
||||
799396CA1D48C02300086753 /* SMB2IOCtl.swift in Sources */,
|
||||
@@ -481,7 +485,7 @@
|
||||
794C220C1D5893F800EC49B8 /* SMB2Notification.swift in Sources */,
|
||||
799396D31D48C02300086753 /* SMB2SetInfo.swift in Sources */,
|
||||
7924B1981D89DAE000589DB7 /* Document.swift in Sources */,
|
||||
7902C0881D61B67100564440 /* SessionDelegate.swift in Sources */,
|
||||
7902C0881D61B67100564440 /* RemoteSession.swift in Sources */,
|
||||
799396D01D48C02300086753 /* SMB2Session.swift in Sources */,
|
||||
799396E21D48C02300086753 /* WebDAVFileProvider.swift in Sources */,
|
||||
799396DC1D48C02300086753 /* SMBErrorType.swift in Sources */,
|
||||
@@ -501,6 +505,7 @@
|
||||
799396601D48B7BF00086753 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_VERSION_STRING = 0.7.0;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
@@ -510,9 +515,12 @@
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_BITCODE = YES;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
@@ -520,12 +528,14 @@
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SWIFT_VERSION = 3.0.1;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
799396611D48B7BF00086753 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_VERSION_STRING = 0.7.0;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
@@ -535,6 +545,8 @@
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_BITCODE = YES;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
@@ -544,6 +556,7 @@
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_VERSION = 3.0.1;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -552,9 +565,8 @@
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
BUNDLE_VERSION_STRING = 0.8.0;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
@@ -566,20 +578,15 @@
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
@@ -595,13 +602,11 @@
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.mousavian.FileProvider-iOS";
|
||||
PRODUCT_NAME = FileProvider;
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 3.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
@@ -613,9 +618,8 @@
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
BUNDLE_VERSION_STRING = 0.8.0;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
@@ -627,17 +631,13 @@
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
@@ -654,7 +654,6 @@
|
||||
PRODUCT_NAME = FileProvider;
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 3.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
@@ -681,11 +680,8 @@
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
@@ -693,10 +689,8 @@
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
FRAMEWORK_VERSION = A;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
@@ -712,7 +706,6 @@
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.mousavian.FileProvider-OSX";
|
||||
PRODUCT_NAME = FileProvider;
|
||||
SDKROOT = macosx;
|
||||
@@ -742,11 +735,8 @@
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
@@ -754,7 +744,6 @@
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
FRAMEWORK_VERSION = A;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
@@ -796,19 +785,15 @@
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
@@ -823,14 +808,13 @@
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.mousavian.FileProvider-tvOS";
|
||||
PRODUCT_NAME = FileProvider;
|
||||
SDKROOT = appletvos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
TARGETED_DEVICE_FAMILY = 3;
|
||||
TVOS_DEPLOYMENT_TARGET = 9.0;
|
||||
TVOS_DEPLOYMENT_TARGET = 10.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
@@ -856,16 +840,13 @@
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
@@ -882,7 +863,7 @@
|
||||
SDKROOT = appletvos;
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = 3;
|
||||
TVOS_DEPLOYMENT_TARGET = 9.0;
|
||||
TVOS_DEPLOYMENT_TARGET = 10.0;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0800"
|
||||
LastUpgradeVersion = "0810"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0800"
|
||||
LastUpgradeVersion = "0810"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0800"
|
||||
LastUpgradeVersion = "0810"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.3.0</string>
|
||||
<string>${BUNDLE_VERSION_STRING}</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.3.0</string>
|
||||
<string>${BUNDLE_VERSION_STRING}</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.3.0</string>
|
||||
<string>${BUNDLE_VERSION_STRING}</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# FileProvider (experimental)
|
||||
# FileProvider
|
||||
|
||||
>This Swift library provide a swifty way to deal with local and remote files and directories in a unified way.
|
||||
|
||||
[![Swift Version][swift-image]][swift-url]
|
||||
[![License][license-image]][license-url]
|
||||
[]()
|
||||
[]()
|
||||
|
||||
[![Build Status][travis-image]][travis-url]
|
||||
[](https://cocoapods.org/pods/FileProvider)
|
||||
@@ -16,7 +16,7 @@
|
||||
[](https://github.com/Carthage/Carthage)
|
||||
--->
|
||||
|
||||
This library provides implementaion of WebDav and SMB/CIFS (incomplete) and local files.
|
||||
This library provides implementaion of WebDav and SMB2 (incomplete) and local files.
|
||||
|
||||
All functions are async calls and it wont block your main thread.
|
||||
|
||||
@@ -27,7 +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.
|
||||
- [ ] **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
|
||||
- [ ] **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**
|
||||
|
||||
@@ -47,23 +47,39 @@ FileProvider supports both CocoaPods.
|
||||
|
||||
Add this line to your pods file:
|
||||
|
||||
pod "FileProvider"
|
||||
```ruby
|
||||
pod "FileProvider"
|
||||
```
|
||||
|
||||
Or add this to cartfile:
|
||||
|
||||
```
|
||||
github "amosavian/FileProvider"
|
||||
```
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
### Manually
|
||||
Copy Source folder to your project and Voila!
|
||||
**First way:** Copy Source folder to your project and Voila!
|
||||
|
||||
**Second way:** Drop FileProvider.xcodeproj to you Xcode workspace and add the framework to your Embeded Binaries in target.
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -73,20 +89,26 @@ 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: URLCredentialPersistence.Permanent)
|
||||
let webdavProvider = WebDAVFileProvider(baseURL: NSURL(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)
|
||||
|
||||
@@ -94,52 +116,54 @@ For remote file providers authentication may be necessary:
|
||||
|
||||
For interaction with UI, set delegate variable of `FileProvider` object
|
||||
|
||||
You can use `absoluteURL()` method if provider to get direct access url (local or remote files) for some file systems which allows to do so (Dropbox doesn't support and returns path simply)
|
||||
You can use `absoluteURL()` method if provider to get direct access url (local or remote files) for some file systems which allows to do so (Dropbox doesn't support and returns path simply wrapped in URL)
|
||||
|
||||
### Delegates
|
||||
|
||||
For updating User interface please consider using delegate method instead of completion handlers. Delegate methods are guaranteed to run in main thread to avoid bugs.
|
||||
|
||||
It's simply tree method which indicated whether the operation failed, succeed and how much of operation has been done (suitable for uploading and downloading operations).
|
||||
It's simply three method which indicated whether the operation failed, succeed and how much of operation has been done (suitable for uploading and downloading operations).
|
||||
|
||||
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:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
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:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
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 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")
|
||||
}
|
||||
}
|
||||
|
||||
**Note:** `fileproviderProgress()` delegate method is not called by `LocalFileProvider`.
|
||||
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.
|
||||
|
||||
It's recommended to use completion handlers for error handling or result processing.
|
||||
|
||||
@@ -147,9 +171,9 @@ It's recommended to use completion handlers for error handling or result process
|
||||
|
||||
You can also implement `FileOperationDelegate` protocol to control behaviour of file operation (copy, move/rename, remove and linking), and decide which files should be removed for example and which won't.
|
||||
|
||||
`fileProvider:shouldDoOperation:` method is called before doing a operation. You sould return `true` if you want to do operation or `false` if you want to stop that operation.
|
||||
`fileProvider(shouldDoOperation:)` method is called before doing a operation. You sould return `true` if you want to do operation or `false` if you want to stop that operation.
|
||||
|
||||
`fileProvider:shouldProceedAfterError:operation:` will be called if an error occured during file operations. Return `true` if you want to continue operation on next files or `false` if you want stop operation further. Default value is false if you don't implement delegate.
|
||||
`fileProvider(shouldProceedAfterError:, operation:)` will be called if an error occured during file operations. Return `true` if you want to continue operation on next files or `false` if you want stop operation further. Default value is false if you don't implement delegate.
|
||||
|
||||
**Note: these methods will be called for files in a directory and its subfolders recursively.**
|
||||
|
||||
@@ -159,109 +183,143 @@ 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: \(modifiedDate)")
|
||||
print("Is Read Only: \(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: \(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 - frees)")
|
||||
})
|
||||
```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"
|
||||
* 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 contentsOfDirectoryAtPath method to list files in current directory.
|
||||
You can then pass "" (empty string) to `contentsOfDirectory` method to list files in current directory.
|
||||
|
||||
### Creating File and Folders
|
||||
|
||||
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 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 should can `contentsAtPath` method with offset and length arguments. Please note first byte of file has offset: 0.
|
||||
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
|
||||
|
||||
Creating/Copying/Deleting functions return a `OperationHandle` for remote operations. It provides operation type, progress and a `.cancel()` method which allows you to cancel operation in midst.
|
||||
|
||||
It's not supported by native `(NS)FileManager` so `LocalFileProvider`, but this functionality will be added to future `PosixFileProvider` class.
|
||||
|
||||
### Monitoring FIle Changes
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ 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 DropboxFileProvider: NSObject, FileProviderBasic {
|
||||
open let type: String = "WebDAV"
|
||||
open class DropboxFileProvider: NSObject, FileProviderBasicRemote {
|
||||
open static let type: String = "WebDAV"
|
||||
open let isPathRelative: Bool = true
|
||||
open let baseURL: URL?
|
||||
open var currentPath: String = ""
|
||||
@@ -25,24 +25,31 @@ open class DropboxFileProvider: NSObject, FileProviderBasic {
|
||||
}
|
||||
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?
|
||||
internal var session: URLSession {
|
||||
public var session: URLSession {
|
||||
if _session == nil {
|
||||
self.sessionDelegate = SessionDelegate(fileProvider: self, credential: credential)
|
||||
let queue = OperationQueue()
|
||||
//queue.underlyingQueue = dispatch_queue
|
||||
_session = URLSession(configuration: URLSessionConfiguration.default, delegate: sessionDelegate as URLSessionDelegate?, delegateQueue: 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? (credential: URLCredential?) {
|
||||
public init? (credential: URLCredential?, cache: URLCache? = nil) {
|
||||
self.baseURL = nil
|
||||
dispatch_queue = DispatchQueue(label: "FileProvider.\(type)", attributes: DispatchQueue.Attributes.concurrent)
|
||||
dispatch_queue = DispatchQueue(label: "FileProvider.\(DropboxFileProvider.type)", attributes: DispatchQueue.Attributes.concurrent)
|
||||
//let url = baseURL.uw_absoluteString
|
||||
self.credential = credential
|
||||
self.cache = cache
|
||||
}
|
||||
|
||||
deinit {
|
||||
@@ -62,22 +69,18 @@ open class DropboxFileProvider: NSObject, FileProviderBasic {
|
||||
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: String.Encoding.utf8)
|
||||
request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: .utf8)
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var dbError: FileProviderDropboxError?
|
||||
var fileObject: DropboxFileObject?
|
||||
if let response = response as? HTTPURLResponse {
|
||||
defer {
|
||||
self.delegateNotify(FileOperation.create(path: path), error: error)
|
||||
}
|
||||
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
|
||||
let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: String(data: data ?? Data(), encoding: String.Encoding.utf8)) : nil
|
||||
if let data = data, let jsonStr = String(data: data, encoding: String.Encoding.utf8), let json = jsonToDictionary(jsonStr), let file = self.mapToFileObject(json) {
|
||||
completionHandler(file, dbError)
|
||||
return
|
||||
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 file = self.mapToFileObject(json) {
|
||||
fileObject = file
|
||||
}
|
||||
completionHandler(nil, dbError)
|
||||
return
|
||||
}
|
||||
completionHandler(nil, error)
|
||||
completionHandler(fileObject, dbError ?? error)
|
||||
})
|
||||
task.resume()
|
||||
}
|
||||
@@ -88,13 +91,13 @@ open class DropboxFileProvider: NSObject, FileProviderBasic {
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
if let data = data, let jsonStr = String(data: data, encoding: String.Encoding.utf8), let json = jsonToDictionary(jsonStr) {
|
||||
let totalSize = ((json["allocation"] as? NSDictionary)?["allocated"] as? NSNumber)?.int64Value ?? -1
|
||||
let usedSize = (json["used"] as? NSNumber)?.int64Value ?? 0
|
||||
completionHandler(totalSize, usedSize)
|
||||
return
|
||||
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["allocation"] as? NSDictionary)?["allocated"] as? NSNumber)?.int64Value ?? -1
|
||||
usedSize = (json["used"] as? NSNumber)?.int64Value ?? 0
|
||||
}
|
||||
completionHandler(-1, 0)
|
||||
completionHandler(totalSize, usedSize)
|
||||
})
|
||||
task.resume()
|
||||
}
|
||||
@@ -104,94 +107,97 @@ open class DropboxFileProvider: NSObject, FileProviderBasic {
|
||||
|
||||
extension DropboxFileProvider: FileProviderOperations {
|
||||
|
||||
|
||||
public func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) {
|
||||
|
||||
public func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let path = (atPath as NSString).appendingPathComponent(folderName) + "/"
|
||||
doOperation(.create(path: path), completionHandler: completionHandler)
|
||||
return doOperation(.create(path: path), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
public func create(file fileAttribs: FileObject, at path: String, contents data: Data?, completionHandler: SimpleCompletionHandler) {
|
||||
self.writeContents(path: path, contents: data ?? Data(), 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 = false, completionHandler: SimpleCompletionHandler) {
|
||||
doOperation(.move(source: path, destination: toPath), completionHandler: completionHandler)
|
||||
public func moveItem(path: String, to toPath: String, overwrite: Bool = false, 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) {
|
||||
doOperation(.copy(source: path, destination: toPath), completionHandler: completionHandler)
|
||||
public func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
return doOperation(.copy(source: path, destination: toPath), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
public func removeItem(path: String, completionHandler: SimpleCompletionHandler) {
|
||||
doOperation(.remove(path: path), completionHandler: completionHandler)
|
||||
public func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
return doOperation(.remove(path: path), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
fileprivate func doOperation(_ operation: FileOperation, completionHandler: SimpleCompletionHandler) {
|
||||
fileprivate func doOperation(_ operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: operation) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
let url: String
|
||||
var path: String?, fromPath: String?, toPath: String?
|
||||
guard let sourcePath = operation.source else { return nil }
|
||||
let destPath = operation.destination
|
||||
switch operation {
|
||||
case .create(path: let p):
|
||||
case .create:
|
||||
url = "https://api.dropboxapi.com/2/files/create_folder"
|
||||
path = p
|
||||
case .copy(source: let fp, destination: let tp):
|
||||
case .copy:
|
||||
url = "https://api.dropboxapi.com/2/files/copy"
|
||||
fromPath = fp
|
||||
toPath = tp
|
||||
case .move(source: let fp, destination: let tp):
|
||||
case .move:
|
||||
url = "https://api.dropboxapi.com/2/files/move"
|
||||
fromPath = fp
|
||||
toPath = tp
|
||||
case .modify(path: let p):
|
||||
return
|
||||
case .remove(path: let p):
|
||||
case .remove:
|
||||
url = "https://api.dropboxapi.com/2/files/delete"
|
||||
path = p
|
||||
case .link(link: _, target: _):
|
||||
return
|
||||
default: // modify, link, fetch
|
||||
return nil
|
||||
}
|
||||
var request = URLRequest(url: URL(string: url)!)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
var requestDictionary = [String: AnyObject]()
|
||||
requestDictionary["path"] = correctPath(path) as NSString?
|
||||
requestDictionary["from_path"] = correctPath(fromPath) as NSString?
|
||||
requestDictionary["to_path"] = correctPath(toPath) as NSString?
|
||||
request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: String.Encoding.utf8)
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
if let response = response as? HTTPURLResponse {
|
||||
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
|
||||
let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path ?? fromPath ?? "", errorDescription: String(data: data ?? Data(), encoding: String.Encoding.utf8)) : nil
|
||||
defer {
|
||||
self.delegateNotify(operation, error: error ?? dbError)
|
||||
}
|
||||
/*if let data = data, let jsonStr = String(data: data, encoding: NSUTF8StringEncoding) {
|
||||
let json = self.jsonToDictionary(jsonStr)
|
||||
}*/
|
||||
completionHandler?(dbError)
|
||||
return
|
||||
}
|
||||
completionHandler?(error)
|
||||
})
|
||||
task.resume()
|
||||
}
|
||||
|
||||
public func copyItem(localFile: URL, to toPath: String, completionHandler: SimpleCompletionHandler) {
|
||||
guard let data = try? Data(contentsOf: localFile) else {
|
||||
let error = throwError(localFile.uw_absoluteString, code: URLError.fileDoesNotExist as FoundationErrorEnum)
|
||||
completionHandler?(error)
|
||||
return
|
||||
if let dest = correctPath(destPath) as NSString? {
|
||||
requestDictionary["from_path"] = correctPath(sourcePath) as NSString?
|
||||
requestDictionary["to_path"] = dest
|
||||
} else {
|
||||
requestDictionary["path"] = correctPath(sourcePath) as NSString?
|
||||
}
|
||||
upload_simple(toPath, data: data, overwrite: true, operation: .copy(source: localFile.uw_absoluteString, destination: toPath), completionHandler: completionHandler)
|
||||
request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: .utf8)
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var dbError: FileProviderDropboxError?
|
||||
if let response = response as? HTTPURLResponse, response.statusCode >= 300, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) {
|
||||
dbError = FileProviderDropboxError(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(path: String, toLocalURL destURL: URL, completionHandler: SimpleCompletionHandler) {
|
||||
public func copyItem(localFile: URL, to toPath: String, 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)
|
||||
}
|
||||
|
||||
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: "https://content.dropboxapi.com/2/files/download")!
|
||||
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]
|
||||
let requestDictionary = ["path": correctPath(path)! as NSString]
|
||||
request.setValue(dictionaryToJSON(requestDictionary), 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 {
|
||||
@@ -207,17 +213,19 @@ extension DropboxFileProvider: FileProviderOperations {
|
||||
completionHandler?(e)
|
||||
}
|
||||
})
|
||||
task.taskDescription = dictionaryToJSON(["type": "Copy" as NSString, "source": path as NSString, "dest": destURL.uw_absoluteString as NSString])
|
||||
task.taskDescription = opType.json
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: opType, tasks: [task])
|
||||
}
|
||||
}
|
||||
|
||||
extension DropboxFileProvider: FileProviderReadWrite {
|
||||
public func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) {
|
||||
self.contents(path: path, offset: 0, length: -1, completionHandler: completionHandler)
|
||||
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)) {
|
||||
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")!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "GET"
|
||||
@@ -227,23 +235,28 @@ 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: { (datam, response, error) in
|
||||
guard let data = datam, 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: String(data: datam ?? Data(), encoding: String.Encoding.utf8)) : nil
|
||||
completionHandler(nil, dbError ?? error)
|
||||
return
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var dbError: FileProviderDropboxError?
|
||||
if let httpResponse = response as? HTTPURLResponse , httpResponse.statusCode >= 300, let code = FileProviderHTTPErrorCode(rawValue: httpResponse.statusCode) {
|
||||
dbError = FileProviderDropboxError(code: code, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8))
|
||||
}
|
||||
completionHandler(data, error)
|
||||
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 = false, completionHandler: SimpleCompletionHandler) {
|
||||
public func writeContents(path: String, contents data: Data, atomically: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.modify(path: path)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
// FIXME: remove 150MB restriction
|
||||
upload_simple(path, data: data, overwrite: true, operation: .modify(path: path), completionHandler: completionHandler)
|
||||
return upload_simple(path, data: data, overwrite: true, operation: opType, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
public func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
|
||||
@@ -269,7 +282,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: "https://api.dropboxapi.com/2/files/get_temporary_link")!
|
||||
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: "https://api.dropboxapi.com/2/files/save_url")!
|
||||
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 {
|
||||
@@ -312,7 +383,7 @@ extension DropboxFileProvider: ExtendedFileProvider {
|
||||
requestDictionary["format"] = "jpeg" as NSString
|
||||
requestDictionary["size"] = "w\(Int(dimension.width))h\(Int(dimension.height))" as NSString
|
||||
request.setValue(dictionaryToJSON(requestDictionary), forHTTPHeaderField: "Dropbox-API-Arg")
|
||||
self.session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
let task = self.session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var image: ImageClass? = nil
|
||||
if let r = response as? HTTPURLResponse, let result = r.allHeaderFields["Dropbox-API-Result"] as? String, let jsonResult = jsonToDictionary(result) {
|
||||
if jsonResult["error"] != nil {
|
||||
@@ -323,7 +394,8 @@ extension DropboxFileProvider: ExtendedFileProvider {
|
||||
image = ImageClass(data: data)
|
||||
}
|
||||
completionHandler(image, error)
|
||||
})
|
||||
})
|
||||
task.resume()
|
||||
}
|
||||
|
||||
public func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String : Any], _ keys: [String], _ error: Error?) -> Void)) {
|
||||
@@ -331,4 +403,14 @@ extension DropboxFileProvider: ExtendedFileProvider {
|
||||
}
|
||||
}
|
||||
|
||||
extension DropboxFileProvider: FileProvider {}
|
||||
extension DropboxFileProvider: FileProvider {
|
||||
open func copy(with zone: NSZone? = nil) -> Any {
|
||||
let copy = DropboxFileProvider(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
|
||||
}
|
||||
}
|
||||
|
||||
+56
-54
@@ -19,21 +19,39 @@ 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["NSURLDropboxDocumentIdentifyKey"] as? String
|
||||
}
|
||||
set {
|
||||
allValues["NSURLDropboxDocumentIdentifyKey"] = 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]()
|
||||
@@ -50,16 +68,16 @@ internal extension DropboxFileProvider {
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: String.Encoding.utf8)
|
||||
request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: .utf8)
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var responseError: FileProviderDropboxError?
|
||||
var files = prevContents
|
||||
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = FileProviderDropboxError(code: rCode, path: path, errorDescription: String(data: data ?? Data(), encoding: String.Encoding.utf8))
|
||||
responseError = FileProviderDropboxError(code: rCode, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8))
|
||||
}
|
||||
if let data = data, let jsonStr = String(data: data, encoding: String.Encoding.utf8) {
|
||||
if let data = data, let jsonStr = String(data: data, encoding: .utf8) {
|
||||
let json = jsonToDictionary(jsonStr)
|
||||
if let entries = json?["entries"] as? [AnyObject] , entries.count > 0 {
|
||||
var files = prevContents
|
||||
for entry in entries {
|
||||
if let entry = entry as? [String: AnyObject], let file = self.mapToFileObject(entry) {
|
||||
files.append(file)
|
||||
@@ -69,18 +87,17 @@ internal extension DropboxFileProvider {
|
||||
let hasmore = (json?["has_more"] as? NSNumber)?.boolValue ?? false
|
||||
if hasmore {
|
||||
self.list(path, cursor: ncursor, prevContents: files, completionHandler: completionHandler)
|
||||
} else {
|
||||
completionHandler(files, ncursor, responseError ?? error)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
completionHandler([], nil, responseError ?? error)
|
||||
})
|
||||
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: FileOperation, completionHandler: SimpleCompletionHandler) {
|
||||
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]()
|
||||
let url: URL
|
||||
@@ -99,30 +116,14 @@ internal extension DropboxFileProvider {
|
||||
let task = session.uploadTask(with: request, from: data, 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: String.Encoding.utf8))
|
||||
}
|
||||
defer {
|
||||
self.delegateNotify(.create(path: targetPath), error: responseError ?? error)
|
||||
responseError = FileProviderDropboxError(code: rCode, path: targetPath, errorDescription: String(data: data ?? Data(), encoding: .utf8))
|
||||
}
|
||||
completionHandler?(responseError ?? error)
|
||||
self.delegateNotify(.create(path: targetPath), error: responseError ?? error)
|
||||
})
|
||||
var dic: [String: AnyObject] = ["type": operation.description as NSString]
|
||||
switch operation {
|
||||
case .create(path: let s):
|
||||
dic["source"] = s as NSString
|
||||
case .copy(source: let s, destination: let d):
|
||||
dic["source"] = s as NSString
|
||||
dic["dest"] = d as NSString
|
||||
case .modify(path: let s):
|
||||
dic["source"] = s as NSString
|
||||
case .move(source: let s, destination: let d):
|
||||
dic["source"] = s as NSString
|
||||
dic["dest"] = d as NSString
|
||||
default:
|
||||
break
|
||||
}
|
||||
task.taskDescription = dictionaryToJSON(dic)
|
||||
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)) {
|
||||
@@ -135,13 +136,13 @@ internal extension DropboxFileProvider {
|
||||
requestDictionary["query"] = query as NSString
|
||||
requestDictionary["start"] = start as NSNumber
|
||||
requestDictionary["max_results"] = maxResultPerPage as NSNumber
|
||||
request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: String.Encoding.utf8)
|
||||
request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: .utf8)
|
||||
let task = session.dataTask(with: request, 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: startPath, errorDescription: String(data: data ?? Data(), encoding: String.Encoding.utf8))
|
||||
responseError = FileProviderDropboxError(code: rCode, path: startPath, errorDescription: String(data: data ?? Data(), encoding: .utf8))
|
||||
}
|
||||
if let data = data, let jsonStr = String(data: data, encoding: String.Encoding.utf8) {
|
||||
if let data = data, let jsonStr = String(data: data, encoding: .utf8) {
|
||||
let json = jsonToDictionary(jsonStr)
|
||||
if let entries = json?["matches"] as? [AnyObject] , entries.count > 0 {
|
||||
for entry in entries {
|
||||
@@ -175,17 +176,18 @@ 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.fileType = (json[".tag"] as? String) == "folder" ? .directory : .regular
|
||||
fileObject.isReadOnly = (json["sharing_info"]?["read_only"] as? NSNumber)?.boolValue ?? false
|
||||
fileObject.id = json["id"] as? String
|
||||
fileObject.rev = json["id"] as? String
|
||||
return fileObject
|
||||
}
|
||||
|
||||
func delegateNotify(_ operation: FileOperation, error: Error?) {
|
||||
func delegateNotify(_ operation: FileOperationType, error: Error?) {
|
||||
DispatchQueue.main.async(execute: {
|
||||
if error == nil {
|
||||
self.delegate?.fileproviderSucceed(self, operation: operation)
|
||||
|
||||
+283
-142
@@ -15,89 +15,10 @@ 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 {
|
||||
var type: String { get }
|
||||
static var type: String { get }
|
||||
var isPathRelative: Bool { get }
|
||||
var baseURL: URL? { get }
|
||||
var currentPath: String { get set }
|
||||
@@ -114,23 +35,88 @@ public protocol FileProviderBasic: class {
|
||||
func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void))
|
||||
}
|
||||
|
||||
public protocol FileProviderBasicRemote: FileProviderBasic {
|
||||
var session: URLSession { get }
|
||||
var cache: URLCache? { get }
|
||||
var useCache: Bool { get set }
|
||||
var validatingCache: Bool { get set }
|
||||
}
|
||||
|
||||
internal extension FileProviderBasicRemote {
|
||||
func returnCachedDate(with request: URLRequest, validatingCache: Bool, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Swift.Void) -> Bool {
|
||||
guard let cache = self.cache else { return false }
|
||||
if let response = cache.cachedResponse(for: request) {
|
||||
var validatedCache = !validatingCache
|
||||
let lastModifiedDate = (response.response as? HTTPURLResponse)?.allHeaderFields["Last-Modified"] as? String
|
||||
let eTag = (response.response as? HTTPURLResponse)?.allHeaderFields["ETag"] as? String
|
||||
if lastModifiedDate == nil && eTag == nil, validatingCache {
|
||||
var validateRequest = request
|
||||
validateRequest.httpMethod = "HEAD"
|
||||
let group = DispatchGroup()
|
||||
group.enter()
|
||||
self.session.dataTask(with: validateRequest, completionHandler: { (_, response, e) in
|
||||
if let httpResponse = response as? HTTPURLResponse {
|
||||
let currentETag = httpResponse.allHeaderFields["ETag"] as? String
|
||||
let currentLastModifiedDate = httpResponse.allHeaderFields["ETag"] as? String ?? "nonvalidetag"
|
||||
validatedCache = (eTag != nil && currentETag == eTag)
|
||||
|| (lastModifiedDate != nil && currentLastModifiedDate == lastModifiedDate)
|
||||
}
|
||||
group.leave()
|
||||
}).resume()
|
||||
_ = group.wait(timeout: DispatchTime.now() + self.session.configuration.timeoutIntervalForRequest)
|
||||
}
|
||||
if validatedCache {
|
||||
completionHandler(response.data, response.response, nil)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func runDataTask(with request: URLRequest, operationHandle: RemoteOperationHandle? = nil, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Swift.Void) {
|
||||
let useCache = self.useCache
|
||||
let validatingCache = self.validatingCache
|
||||
dispatch_queue.async {
|
||||
if useCache {
|
||||
if self.returnCachedDate(with: request, validatingCache: validatingCache, completionHandler: completionHandler) {
|
||||
return
|
||||
}
|
||||
}
|
||||
let task = self.session.dataTask(with: request, completionHandler: completionHandler)
|
||||
task.taskDescription = operationHandle?.operationType.json
|
||||
operationHandle?.add(task: task)
|
||||
task.resume()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public protocol FileProviderOperations: FileProviderBasic {
|
||||
var fileOperationDelegate : FileOperationDelegate? { get set }
|
||||
|
||||
func create(folder: String, at: String, completionHandler: SimpleCompletionHandler)
|
||||
func create(file: FileObject, at: String, contents data: Data?, completionHandler: SimpleCompletionHandler)
|
||||
func moveItem(path: String, to: String, overwrite: Bool, completionHandler: SimpleCompletionHandler)
|
||||
func copyItem(path: String, to: String, overwrite: Bool, completionHandler: SimpleCompletionHandler)
|
||||
func removeItem(path: String, completionHandler: SimpleCompletionHandler)
|
||||
@discardableResult
|
||||
func create(folder: String, at: String, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
@discardableResult
|
||||
func create(file: String, at: String, contents data: Data?, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
@discardableResult
|
||||
func moveItem(path: String, to: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
@discardableResult
|
||||
func copyItem(path: String, to: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
@discardableResult
|
||||
func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
|
||||
func copyItem(localFile: URL, to: String, completionHandler: SimpleCompletionHandler)
|
||||
func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler)
|
||||
@discardableResult
|
||||
func copyItem(localFile: URL, to: String, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
@discardableResult
|
||||
func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
}
|
||||
|
||||
public protocol FileProviderReadWrite: FileProviderBasic {
|
||||
func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void))
|
||||
func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void))
|
||||
func writeContents(path: String, contents: Data, atomically: Bool, completionHandler: SimpleCompletionHandler)
|
||||
@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, atomically: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
|
||||
func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void))
|
||||
}
|
||||
@@ -141,11 +127,14 @@ public protocol FileProviderMonitor: FileProviderBasic {
|
||||
func isRegisteredForNotification(path: String) -> Bool
|
||||
}
|
||||
|
||||
public protocol FileProvider: FileProviderBasic, FileProviderOperations, FileProviderReadWrite {
|
||||
|
||||
public protocol FileProvider: FileProviderBasic, FileProviderOperations, FileProviderReadWrite, NSCopying {
|
||||
}
|
||||
|
||||
extension FileProviderBasic {
|
||||
public var type: String {
|
||||
return Self.type
|
||||
}
|
||||
|
||||
public var bareCurrentPath: String {
|
||||
return currentPath.trimmingCharacters(in: CharacterSet(charactersIn: ". /"))
|
||||
}
|
||||
@@ -158,12 +147,12 @@ extension FileProviderBasic {
|
||||
rpath = self.currentPath
|
||||
}
|
||||
if isPathRelative, let baseURL = baseURL {
|
||||
if rpath.hasPrefix("/") && baseURL.uw_absoluteString.hasSuffix("/") {
|
||||
if rpath.hasPrefix("/") && baseURL.absoluteString.hasSuffix("/") {
|
||||
var npath = rpath
|
||||
npath.remove(at: npath.startIndex)
|
||||
return baseURL.uw_URLByAppendingPathComponent(npath)
|
||||
return baseURL.appendingPathComponent(npath)
|
||||
} else {
|
||||
return baseURL.uw_URLByAppendingPathComponent(rpath)
|
||||
return baseURL.appendingPathComponent(rpath)
|
||||
}
|
||||
} else {
|
||||
return URL(fileURLWithPath: rpath).standardizedFileURL
|
||||
@@ -171,15 +160,15 @@ extension FileProviderBasic {
|
||||
}
|
||||
|
||||
public func relativePathOf(url: URL) -> String {
|
||||
guard let baseURL = self.baseURL else { return url.uw_absoluteString }
|
||||
return url.standardizedFileURL.uw_absoluteString.replacingOccurrences(of: baseURL.uw_absoluteString, with: "/").removingPercentEncoding!
|
||||
guard let baseURL = self.baseURL else { return url.absoluteString }
|
||||
return url.standardizedFileURL.absoluteString.replacingOccurrences(of: baseURL.absoluteString, with: "/").removingPercentEncoding!
|
||||
}
|
||||
|
||||
internal func correctPath(_ path: String?) -> String? {
|
||||
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.endIndex)
|
||||
}
|
||||
return p
|
||||
}
|
||||
@@ -207,7 +196,7 @@ extension FileProviderBasic {
|
||||
let similiar = contents.map {
|
||||
$0.absoluteURL?.lastPathComponent ?? $0.name
|
||||
}.filter {
|
||||
$0.hasPrefix(result) && (!fileExt.isEmpty && $0.hasSuffix("." + fileExt))
|
||||
$0.hasPrefix(result)
|
||||
}
|
||||
while similiar.contains(result + (!fileExt.isEmpty ? "." + fileExt : "")) {
|
||||
result = "\(bareFileName) \(i)"
|
||||
@@ -229,7 +218,7 @@ extension FileProviderBasic {
|
||||
default:
|
||||
domain = NSCocoaErrorDomain
|
||||
}
|
||||
return NSError(domain: domain, code: code.rawValue, userInfo: [NSURLErrorFailingURLErrorKey: fileURL, NSURLErrorFailingURLStringErrorKey: fileURL.uw_absoluteString])
|
||||
return NSError(domain: domain, code: code.rawValue, userInfo: [NSURLErrorFailingURLErrorKey: fileURL, NSURLErrorFailingURLStringErrorKey: fileURL.absoluteString])
|
||||
}
|
||||
|
||||
internal func NotImplemented() {
|
||||
@@ -255,12 +244,10 @@ extension FileProviderBasic {
|
||||
if let isotime = dateFor.date(from: dateString) {
|
||||
return isotime
|
||||
}
|
||||
//self.init()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public protocol ExtendedFileProvider: FileProvider {
|
||||
func thumbnailOfFileSupported(path: String) -> Bool
|
||||
func propertiesOfFileSupported(path: String) -> Bool
|
||||
@@ -268,79 +255,233 @@ public protocol ExtendedFileProvider: FileProvider {
|
||||
func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String: Any], _ keys: [String], _ error: Error?) -> Void))
|
||||
}
|
||||
|
||||
public enum FileOperation: CustomStringConvertible {
|
||||
public enum FileOperationType: CustomStringConvertible {
|
||||
case create (path: String)
|
||||
case copy (source: String, destination: String)
|
||||
case move (source: String, destination: String)
|
||||
case modify (path: String)
|
||||
case remove (path: String)
|
||||
case link (link: String, target: String)
|
||||
case fetch (path: String)
|
||||
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .create(path: _): return "Create"
|
||||
case .copy(source: _, destination: _): return "Copy"
|
||||
case .move(source: _, destination: _): return "Move"
|
||||
case .modify(path: _): return "Modify"
|
||||
case .remove(path: _): return "Remove"
|
||||
case .link(link: _, target: _): return "Link"
|
||||
case .create: return "Create"
|
||||
case .copy: return "Copy"
|
||||
case .move: return "Move"
|
||||
case .modify: return "Modify"
|
||||
case .remove: return "Remove"
|
||||
case .link: return "Link"
|
||||
case .fetch: return "Fetch"
|
||||
}
|
||||
}
|
||||
|
||||
internal var actionDescription: String {
|
||||
switch self {
|
||||
case .create(path: _): return "Creating"
|
||||
case .copy(source: _, destination: _): return "Copying"
|
||||
case .move(source: _, destination: _): return "Moving"
|
||||
case .modify(path: _): return "Modifying"
|
||||
case .remove(path: _): return "Removing"
|
||||
case .link(link: _, target: _): return "Linking"
|
||||
public var actionDescription: String {
|
||||
return description.trimmingCharacters(in: CharacterSet(charactersIn: "e")) + "ing"
|
||||
}
|
||||
|
||||
public var source: String? {
|
||||
guard let reflect = Mirror(reflecting: self).children.first?.value else { return nil }
|
||||
let mirror = Mirror(reflecting: reflect)
|
||||
return reflect as? String ?? mirror.children.first?.value as? String
|
||||
}
|
||||
|
||||
public var destination: String? {
|
||||
guard let reflect = Mirror(reflecting: self).children.first?.value else { return nil }
|
||||
let mirror = Mirror(reflecting: reflect)
|
||||
return mirror.children.dropFirst().first?.value as? String
|
||||
}
|
||||
|
||||
internal var json: String? {
|
||||
var dictionary: [String: AnyObject] = ["type": self.description as NSString]
|
||||
dictionary["source"] = source as NSString?
|
||||
dictionary["dest"] = destination as NSString?
|
||||
return dictionaryToJSON(dictionary)
|
||||
}
|
||||
}
|
||||
|
||||
open class FileObject {
|
||||
open internal(set) var allValues: [String: Any]
|
||||
|
||||
internal init(allValues: [String: Any]) {
|
||||
self.allValues = allValues
|
||||
}
|
||||
|
||||
internal init(absoluteURL: URL? = nil, name: String, path: String) {
|
||||
self.allValues = [String: Any]()
|
||||
self.absoluteURL = absoluteURL
|
||||
self.name = name
|
||||
self.path = path
|
||||
}
|
||||
|
||||
open internal(set) var absoluteURL: URL? {
|
||||
get {
|
||||
return allValues["NSURLAbsoluteURLKey"] as? URL
|
||||
}
|
||||
set {
|
||||
allValues["NSURLAbsoluteURLKey"] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open internal(set) var name: String {
|
||||
get {
|
||||
return allValues[URLResourceKey.nameKey.rawValue] as! String
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.nameKey.rawValue] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open internal(set) var path: String {
|
||||
get {
|
||||
return allValues[URLResourceKey.pathKey.rawValue] as! String
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.pathKey.rawValue] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open internal(set) var size: Int64 {
|
||||
get {
|
||||
return allValues[URLResourceKey.fileSizeKey.rawValue] as? Int64 ?? -1
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.fileSizeKey.rawValue] = Int(exactly: newValue) ?? Int.max
|
||||
}
|
||||
}
|
||||
|
||||
open internal(set) var creationDate: Date? {
|
||||
get {
|
||||
return allValues[URLResourceKey.creationDateKey.rawValue] as? Date
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.creationDateKey.rawValue] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open internal(set) var modifiedDate: Date? {
|
||||
get {
|
||||
return allValues[URLResourceKey.contentModificationDateKey.rawValue] as? Date
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.contentModificationDateKey.rawValue] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open internal(set) var fileType: URLFileResourceType? {
|
||||
get {
|
||||
guard let typeString = allValues[URLResourceKey.fileResourceTypeKey.rawValue] as? String else {
|
||||
return nil
|
||||
}
|
||||
return URLFileResourceType(rawValue: typeString)
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.fileResourceTypeKey.rawValue] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open internal(set) var isHidden: Bool {
|
||||
get {
|
||||
return allValues[URLResourceKey.isHiddenKey.rawValue] as? Bool ?? false
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.isHiddenKey.rawValue] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open internal(set) var isReadOnly: Bool {
|
||||
get {
|
||||
return !(allValues[URLResourceKey.isWritableKey.rawValue] as? Bool ?? true)
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.isWritableKey.rawValue] = !newValue
|
||||
}
|
||||
}
|
||||
|
||||
open var isDirectory: Bool {
|
||||
return self.fileType == .directory
|
||||
}
|
||||
|
||||
open var isRegularFile: Bool {
|
||||
return self.fileType == .regular
|
||||
}
|
||||
|
||||
open var isSymLink: Bool {
|
||||
return self.fileType == .symbolicLink
|
||||
}
|
||||
}
|
||||
|
||||
public protocol OperationHandle {
|
||||
var operationType: FileOperationType { get }
|
||||
var bytesSoFar: Int64 { get }
|
||||
var totalBytes: Int64 { get }
|
||||
var inProgress: Bool { get }
|
||||
var progress: Float { get }
|
||||
func cancel() -> Bool
|
||||
}
|
||||
|
||||
public extension OperationHandle {
|
||||
public var progress: Float {
|
||||
let bytesSoFar = self.bytesSoFar
|
||||
let totalBytes = self.totalBytes
|
||||
return totalBytes > 0 ? Float(Double(bytesSoFar) / Double(totalBytes)) : Float.nan
|
||||
}
|
||||
}
|
||||
|
||||
public protocol FileProviderDelegate: class {
|
||||
func fileproviderSucceed(_ fileProvider: FileProviderOperations, operation: FileOperation)
|
||||
func fileproviderFailed(_ fileProvider: FileProviderOperations, operation: FileOperation)
|
||||
func fileproviderProgress(_ fileProvider: FileProviderOperations, operation: FileOperation, progress: Float)
|
||||
func fileproviderSucceed(_ fileProvider: FileProviderOperations, operation: FileOperationType)
|
||||
func fileproviderFailed(_ fileProvider: FileProviderOperations, operation: FileOperationType)
|
||||
func fileproviderProgress(_ fileProvider: FileProviderOperations, operation: FileOperationType, progress: Float)
|
||||
}
|
||||
|
||||
public protocol FileOperationDelegate: class {
|
||||
|
||||
/// fileProvider(_:shouldOperate:) gives the delegate an opportunity to filter the file operation. Returning true from this method will allow the copy to happen. Returning false from this method causes the item in question to be skipped. If the item skipped was a directory, no children of that directory will be subject of the operation, nor will the delegate be notified of those children.
|
||||
func fileProvider(_ fileProvider: FileProviderOperations, shouldDoOperation operation: FileOperation) -> Bool
|
||||
func fileProvider(_ fileProvider: FileProviderOperations, shouldDoOperation operation: FileOperationType) -> Bool
|
||||
|
||||
/// fileProvider(_:shouldProceedAfterError:copyingItemAtPath:toPath:) gives the delegate an opportunity to recover from or continue copying after an error. If an error occurs, the error object will contain an ErrorType indicating the problem. The source path and destination paths are also provided. If this method returns true, the FileProvider instance will continue as if the error had not occurred. If this method returns false, the NSFileManager instance will stop copying, return false from copyItemAtPath:toPath:error: and the error will be provied there.
|
||||
func fileProvider(_ fileProvider: FileProviderOperations, shouldProceedAfterError error: Error, operation: FileOperation) -> Bool
|
||||
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 {
|
||||
#if swift(>=2.3)
|
||||
return self.scheme ?? ""
|
||||
#else
|
||||
return self.scheme
|
||||
#endif
|
||||
}
|
||||
|
||||
var uw_absoluteString: String {
|
||||
return self.absoluteString
|
||||
}
|
||||
|
||||
func uw_URLByAppendingPathComponent(_ pathComponent: String) -> URL {
|
||||
return self.appendingPathComponent(pathComponent)
|
||||
}
|
||||
|
||||
func uw_URLByAppendingPathExtension(_ pathExtension: String) -> URL {
|
||||
return self.appendingPathExtension(pathExtension)
|
||||
return self.scheme ?? ""
|
||||
}
|
||||
}
|
||||
|
||||
internal class Weak<T: AnyObject> {
|
||||
weak var value : T?
|
||||
init (_ value: T) {
|
||||
self.value = value
|
||||
}
|
||||
}
|
||||
|
||||
extension URLFileResourceType {
|
||||
public init(fileTypeValue: FileAttributeType) {
|
||||
switch fileTypeValue {
|
||||
case FileAttributeType.typeCharacterSpecial: self = .characterSpecial
|
||||
case FileAttributeType.typeDirectory: self = .directory
|
||||
case FileAttributeType.typeBlockSpecial: self = .blockSpecial
|
||||
case FileAttributeType.typeRegular: self = .regular
|
||||
case FileAttributeType.typeSymbolicLink: self = .symbolicLink
|
||||
case FileAttributeType.typeSocket: self = .socket
|
||||
case FileAttributeType.typeUnknown: self = .unknown
|
||||
default: self = .unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public protocol FoundationErrorEnum {
|
||||
init? (rawValue: Int)
|
||||
var rawValue: Int { get }
|
||||
}
|
||||
|
||||
extension URLError.Code: FoundationErrorEnum {}
|
||||
extension CocoaError.Code: FoundationErrorEnum {}
|
||||
|
||||
internal func jsonToDictionary(_ jsonString: String) -> [String: AnyObject]? {
|
||||
guard let data = jsonString.data(using: String.Encoding.utf8) else {
|
||||
guard let data = jsonString.data(using: .utf8) else {
|
||||
return nil
|
||||
}
|
||||
if let dic = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions()) as? [String: AnyObject] {
|
||||
@@ -351,7 +492,7 @@ internal func jsonToDictionary(_ jsonString: String) -> [String: AnyObject]? {
|
||||
|
||||
internal func dictionaryToJSON(_ dictionary: [String: AnyObject]) -> String? {
|
||||
if let data = try? JSONSerialization.data(withJSONObject: dictionary, options: JSONSerialization.WritingOptions()) {
|
||||
return String(data: data, encoding: String.Encoding.utf8)
|
||||
return String(data: data, encoding: .utf8)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+99
-248
@@ -8,56 +8,47 @@
|
||||
|
||||
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 let type = "Local"
|
||||
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
|
||||
open weak var delegate: FileProviderDelegate?
|
||||
open let credential: URLCredential? = nil
|
||||
|
||||
open let fileManager = FileManager()
|
||||
open let opFileManager = FileManager()
|
||||
open private(set) var fileManager = FileManager()
|
||||
open private(set) var opFileManager = FileManager()
|
||||
fileprivate var fileProviderManagerDelegate: LocalFileProviderManagerDelegate? = nil
|
||||
|
||||
public init () {
|
||||
dispatch_queue = DispatchQueue(label: "FileProvider.\(type)", attributes: DispatchQueue.Attributes.concurrent)
|
||||
operation_queue = DispatchQueue(label: "FileProvider.\(type).Operation", attributes: [])
|
||||
dispatch_queue = DispatchQueue(label: "FileProvider.\(LocalFileProvider.type)", attributes: DispatchQueue.Attributes.concurrent)
|
||||
operation_queue = DispatchQueue(label: "FileProvider.\(LocalFileProvider.type).Operation", attributes: [])
|
||||
fileProviderManagerDelegate = LocalFileProviderManagerDelegate(provider: self)
|
||||
opFileManager.delegate = fileProviderManagerDelegate
|
||||
}
|
||||
|
||||
public init (baseURL: URL) {
|
||||
self.baseURL = baseURL
|
||||
dispatch_queue = DispatchQueue(label: "FileProvider.\(type)", attributes: DispatchQueue.Attributes.concurrent)
|
||||
operation_queue = DispatchQueue(label: "FileProvider.\(type).Operation", attributes: [])
|
||||
dispatch_queue = DispatchQueue(label: "FileProvider.\(LocalFileProvider.type)", attributes: DispatchQueue.Attributes.concurrent)
|
||||
operation_queue = DispatchQueue(label: "FileProvider.\(LocalFileProvider.type).Operation", attributes: [])
|
||||
fileProviderManagerDelegate = LocalFileProviderManagerDelegate(provider: self)
|
||||
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,92 +57,65 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
open weak var fileOperationDelegate : FileOperationDelegate?
|
||||
|
||||
@objc(createWithFolder:at:completionHandler:) open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) {
|
||||
@discardableResult
|
||||
open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.create(path: (atPath as NSString).appendingPathComponent(folderName) + "/")
|
||||
operation_queue.async {
|
||||
do {
|
||||
try self.opFileManager.createDirectory(at: self.absoluteURL(atPath).uw_URLByAppendingPathComponent(folderName), withIntermediateDirectories: true, attributes: [:])
|
||||
try self.opFileManager.createDirectory(at: self.absoluteURL(atPath).appendingPathComponent(folderName), withIntermediateDirectories: true, attributes: [:])
|
||||
completionHandler?(nil)
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.delegate?.fileproviderSucceed(self, operation: .create(path: (atPath as NSString).appendingPathComponent(folderName) + "/"))
|
||||
self.delegate?.fileproviderSucceed(self, operation: opType)
|
||||
})
|
||||
} catch let e as NSError {
|
||||
completionHandler?(e)
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.delegate?.fileproviderFailed(self, operation: .create(path: (atPath as NSString).appendingPathComponent(folderName) + "/"))
|
||||
self.delegate?.fileproviderFailed(self, operation: opType)
|
||||
})
|
||||
}
|
||||
}
|
||||
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
|
||||
}
|
||||
|
||||
open func create(file fileAttribs: FileObject, at atPath: String, contents data: Data?, completionHandler: SimpleCompletionHandler) {
|
||||
@discardableResult
|
||||
open func create(file fileName: String, at atPath: String, contents data: Data?, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.create(path: (atPath as NSString).appendingPathComponent(fileName))
|
||||
operation_queue.async {
|
||||
let fileURL = self.absoluteURL(atPath).uw_URLByAppendingPathComponent(fileAttribs.name)
|
||||
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: attributes)
|
||||
let fileURL = self.absoluteURL(atPath).appendingPathComponent(fileName)
|
||||
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: .create(path: (atPath as NSString).appendingPathComponent(fileAttribs.name)))
|
||||
self.delegate?.fileproviderSucceed(self, operation: opType)
|
||||
})
|
||||
} else {
|
||||
completionHandler?(self.throwError(atPath, code: URLError.cannotCreateFile as FoundationErrorEnum))
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.delegate?.fileproviderFailed(self, operation: .create(path: (atPath as NSString).appendingPathComponent(fileAttribs.name)))
|
||||
self.delegate?.fileproviderFailed(self, operation: opType)
|
||||
})
|
||||
}
|
||||
}
|
||||
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
|
||||
}
|
||||
|
||||
open func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
|
||||
// FIXME: progress
|
||||
@discardableResult
|
||||
open func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.move(source: path, destination: toPath)
|
||||
operation_queue.async {
|
||||
if !overwrite && self.fileManager.fileExists(atPath: self.absoluteURL(toPath).path) {
|
||||
completionHandler?(self.throwError(toPath, code: URLError.cannotMoveFile as FoundationErrorEnum))
|
||||
@@ -161,19 +125,21 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor {
|
||||
try self.opFileManager.moveItem(at: self.absoluteURL(path), to: self.absoluteURL(toPath))
|
||||
completionHandler?(nil)
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.delegate?.fileproviderSucceed(self, operation: .move(source: path, destination: toPath))
|
||||
self.delegate?.fileproviderSucceed(self, operation: opType)
|
||||
})
|
||||
} catch let e as NSError {
|
||||
completionHandler?(e)
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.delegate?.fileproviderFailed(self, operation: .move(source: path, destination: toPath))
|
||||
self.delegate?.fileproviderFailed(self, operation: opType)
|
||||
})
|
||||
}
|
||||
}
|
||||
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
|
||||
}
|
||||
|
||||
open func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
|
||||
// FIXME: progress, for files > 100mb, monitor file by another thread, for dirs check copied items count
|
||||
@discardableResult
|
||||
open func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.copy(source: path, destination: toPath)
|
||||
operation_queue.async {
|
||||
if !overwrite && self.fileManager.fileExists(atPath: self.absoluteURL(toPath).path) {
|
||||
completionHandler?(self.throwError(toPath, code: URLError.cannotWriteToFile as FoundationErrorEnum))
|
||||
@@ -183,125 +149,137 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor {
|
||||
try self.opFileManager.copyItem(at: self.absoluteURL(path), to: self.absoluteURL(toPath))
|
||||
completionHandler?(nil)
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.delegate?.fileproviderSucceed(self, operation: .copy(source: path, destination: toPath))
|
||||
self.delegate?.fileproviderSucceed(self, operation: opType)
|
||||
})
|
||||
} catch let e as NSError {
|
||||
completionHandler?(e)
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.delegate?.fileproviderFailed(self, operation: .copy(source: path, destination: toPath))
|
||||
self.delegate?.fileproviderFailed(self, operation: opType)
|
||||
})
|
||||
}
|
||||
}
|
||||
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
|
||||
}
|
||||
|
||||
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) {
|
||||
@discardableResult
|
||||
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.remove(path: path)
|
||||
operation_queue.async {
|
||||
do {
|
||||
try self.opFileManager.removeItem(at: self.absoluteURL(path))
|
||||
completionHandler?(nil)
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.delegate?.fileproviderSucceed(self, operation: .remove(path: path))
|
||||
self.delegate?.fileproviderSucceed(self, operation: opType)
|
||||
})
|
||||
} catch let e as NSError {
|
||||
completionHandler?(e)
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.delegate?.fileproviderFailed(self, operation: .remove(path: path))
|
||||
self.delegate?.fileproviderFailed(self, operation: opType)
|
||||
})
|
||||
}
|
||||
}
|
||||
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
|
||||
}
|
||||
|
||||
open func copyItem(localFile: URL, to toPath: String, completionHandler: SimpleCompletionHandler) {
|
||||
@discardableResult
|
||||
open func copyItem(localFile: URL, to toPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.copy(source: localFile.absoluteString, destination: toPath)
|
||||
operation_queue.async {
|
||||
do {
|
||||
try self.opFileManager.copyItem(at: localFile, to: self.absoluteURL(toPath))
|
||||
completionHandler?(nil)
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.delegate?.fileproviderSucceed(self, operation: .copy(source: localFile.uw_absoluteString, destination: toPath))
|
||||
self.delegate?.fileproviderSucceed(self, operation: opType)
|
||||
})
|
||||
} catch let e as NSError {
|
||||
completionHandler?(e)
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.delegate?.fileproviderFailed(self, operation: .copy(source: localFile.uw_absoluteString, destination: toPath))
|
||||
self.delegate?.fileproviderFailed(self, operation: opType)
|
||||
})
|
||||
}
|
||||
}
|
||||
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
|
||||
}
|
||||
|
||||
open func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) {
|
||||
@discardableResult
|
||||
open func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.copy(source: path, destination: toLocalURL.absoluteString)
|
||||
operation_queue.async {
|
||||
do {
|
||||
try self.opFileManager.copyItem(at: self.absoluteURL(path), to: toLocalURL)
|
||||
completionHandler?(nil)
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.delegate?.fileproviderSucceed(self, operation: .copy(source: path, destination: toLocalURL.uw_absoluteString))
|
||||
self.delegate?.fileproviderSucceed(self, operation: opType)
|
||||
})
|
||||
} catch let e as NSError {
|
||||
completionHandler?(e)
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.delegate?.fileproviderFailed(self, operation: .copy(source: path, destination: toLocalURL.uw_absoluteString))
|
||||
self.delegate?.fileproviderFailed(self, operation: opType)
|
||||
})
|
||||
}
|
||||
}
|
||||
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
|
||||
}
|
||||
|
||||
open func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) {
|
||||
@discardableResult
|
||||
open func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
|
||||
dispatch_queue.async {
|
||||
let data = self.fileManager.contents(atPath: self.absoluteURL(path).path)
|
||||
completionHandler(data, nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) {
|
||||
// Unfortunatlely there is no method provided in NSFileManager to read a segment of file.
|
||||
// So we have to fallback to POSIX provided methods
|
||||
@discardableResult
|
||||
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
|
||||
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
|
||||
}
|
||||
let fd_from = open(aPath, O_RDONLY)
|
||||
if fd_from < 0 {
|
||||
guard let handle = FileHandle(forReadingAtPath: aPath) else {
|
||||
completionHandler(nil, self.throwError(path, code: URLError.cannotOpenFile as FoundationErrorEnum))
|
||||
return
|
||||
}
|
||||
defer { precondition(close(fd_from) >= 0) }
|
||||
lseek(fd_from, offset, SEEK_SET)
|
||||
var buf = [UInt8](repeating: 0, count: length)
|
||||
let nread = read(fd_from, &buf, buf.count)
|
||||
if nread < 0 {
|
||||
completionHandler(nil, self.throwError(path, code: URLError.noPermissionsToReadFile as FoundationErrorEnum))
|
||||
} else if nread == 0 {
|
||||
completionHandler(nil, nil)
|
||||
} else {
|
||||
let data = Data(bytesNoCopy: UnsafeMutablePointer<UInt8>(&buf), count: nread, deallocator: .free)
|
||||
completionHandler(data, nil)
|
||||
defer {
|
||||
handle.closeFile()
|
||||
}
|
||||
handle.seek(toFileOffset: UInt64(offset))
|
||||
let data = handle.readData(ofLength: length)
|
||||
completionHandler(data, nil)
|
||||
|
||||
}
|
||||
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
|
||||
}
|
||||
|
||||
open func writeContents(path: String, contents data: Data, atomically: Bool, completionHandler: SimpleCompletionHandler) {
|
||||
@discardableResult
|
||||
open func writeContents(path: String, contents data: Data, atomically: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.modify(path: path)
|
||||
operation_queue.async {
|
||||
try? data.write(to: self.absoluteURL(path), options: atomically ? [.atomic] : [])
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.delegate?.fileproviderSucceed(self, operation: .modify(path: path))
|
||||
self.delegate?.fileproviderSucceed(self, operation: opType)
|
||||
})
|
||||
}
|
||||
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
|
||||
}
|
||||
|
||||
open func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
|
||||
dispatch_queue.async {
|
||||
let iterator = self.fileManager.enumerator(at: self.absoluteURL(path), includingPropertiesForKeys: nil, options: recursive ? FileManager.DirectoryEnumerationOptions() : .skipsSubdirectoryDescendants) { (url, e) -> Bool in
|
||||
let iterator = self.fileManager.enumerator(at: self.absoluteURL(path), includingPropertiesForKeys: nil, options: recursive ? [] : [.skipsSubdirectoryDescendants, .skipsPackageDescendants]) { (url, e) -> Bool in
|
||||
completionHandler([], e)
|
||||
return true
|
||||
}
|
||||
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)
|
||||
@@ -313,12 +291,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) {
|
||||
@@ -342,6 +316,15 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor {
|
||||
open func isRegisteredForNotification(path: String) -> Bool {
|
||||
return monitors.map( { self.relativePathOf(url: $0.url) } ).contains(path)
|
||||
}
|
||||
|
||||
open func copy(with zone: NSZone? = nil) -> Any {
|
||||
let copy = LocalFileProvider(baseURL: self.baseURL!)
|
||||
copy.currentPath = self.currentPath
|
||||
copy.delegate = self.delegate
|
||||
copy.fileOperationDelegate = self.fileOperationDelegate
|
||||
copy.isPathRelative = self.isPathRelative
|
||||
return copy
|
||||
}
|
||||
}
|
||||
|
||||
public extension LocalFileProvider {
|
||||
@@ -362,135 +345,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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: 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()
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,59 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
open class RemoteOperationHandle: OperationHandle {
|
||||
|
||||
internal var tasks: [Weak<URLSessionTask>]
|
||||
|
||||
open private(set) var operationType: FileOperationType
|
||||
|
||||
init(operationType: FileOperationType, tasks: [URLSessionTask]) {
|
||||
self.operationType = operationType
|
||||
self.tasks = tasks.map { Weak<URLSessionTask>($0) }
|
||||
}
|
||||
|
||||
internal func add(task: URLSessionTask) {
|
||||
tasks.append(Weak<URLSessionTask>(task))
|
||||
}
|
||||
|
||||
private func reape() {
|
||||
self.tasks = tasks.filter { $0.value != nil }
|
||||
}
|
||||
|
||||
open var bytesSoFar: Int64 {
|
||||
return tasks.reduce(0) {
|
||||
if let task = $1.value as? URLSessionUploadTask {
|
||||
return $0 + task.countOfBytesSent
|
||||
} else {
|
||||
return $0 + ($1.value?.countOfBytesReceived ?? 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open var totalBytes: Int64 {
|
||||
return tasks.reduce(0) {
|
||||
if let task = $1.value as? URLSessionUploadTask {
|
||||
return $0 + task.countOfBytesExpectedToSend
|
||||
} else {
|
||||
return $0 + ($1.value?.countOfBytesExpectedToSend ?? 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open func cancel() -> Bool {
|
||||
var canceled = false
|
||||
for taskbox in tasks {
|
||||
taskbox.value?.cancel()
|
||||
canceled = true
|
||||
}
|
||||
return canceled
|
||||
}
|
||||
|
||||
open var inProgress: Bool {
|
||||
return tasks.reduce(false) { $0 || $1.value?.state ?? .canceling == .running }
|
||||
}
|
||||
}
|
||||
|
||||
class SessionDelegate: NSObject, URLSessionDataDelegate, URLSessionDownloadDelegate {
|
||||
|
||||
weak var fileProvider: FileProvider?
|
||||
@@ -38,7 +91,7 @@ class SessionDelegate: NSObject, URLSessionDataDelegate, URLSessionDownloadDeleg
|
||||
return
|
||||
}
|
||||
let dest = json["dest"] as? String
|
||||
let op : FileOperation
|
||||
let op : FileOperationType
|
||||
switch type {
|
||||
case "Create":
|
||||
op = .create(path: source)
|
||||
+36
-28
@@ -12,12 +12,24 @@ import Foundation
|
||||
// For big-endian platforms like PowerPC, there must be a huge overhaul
|
||||
|
||||
protocol SMBProtocolClientDelegate: class {
|
||||
func receivedSMB2Response(_ header: SMB2.Header, response: SMBResponse)
|
||||
func receivedResponse(client: SMB2ProtocolClient, response: SMBResponse, for: SMBRequest)
|
||||
}
|
||||
|
||||
class SMB2ProtocolClient: FPSStreamTask {
|
||||
var currentMessageID: UInt64 = 0
|
||||
var sessionId: UInt64 = 0
|
||||
var timeout: TimeInterval = 30
|
||||
|
||||
private(set) var lastMessageID: UInt64 = 0
|
||||
private(set) var sessionId: UInt64 = 0
|
||||
private func messageId() -> UInt64 {
|
||||
defer {
|
||||
lastMessageID += 1
|
||||
}
|
||||
return lastMessageID
|
||||
}
|
||||
|
||||
private(set) var establishedTrees = Array<SMB2.TreeConnectResponse>()
|
||||
private(set) var requestStack = [Int: SMBRequest]()
|
||||
private(set) var responseStack = [Int: SMBResponse]()
|
||||
|
||||
weak var delegate: SMBProtocolClientDelegate?
|
||||
|
||||
@@ -40,7 +52,7 @@ class SMB2ProtocolClient: FPSStreamTask {
|
||||
let data = createSMB2Message(header: smbHeader, message: msg)
|
||||
self.writeData(data, timeout: 0, completionHandler: { (e) in
|
||||
if self.sessionId == 0 {
|
||||
self.readData(OfMinLength: 64, maxLength: 65536, timeout: 30, completionHandler: { (data, eof, e2) in
|
||||
self.readData(OfMinLength: 64, maxLength: 65536, timeout: self.timeout, completionHandler: { (data, eof, e2) in
|
||||
// TODO: set session id
|
||||
completionHandler?(e2 ?? e)
|
||||
})
|
||||
@@ -70,6 +82,7 @@ class SMB2ProtocolClient: FPSStreamTask {
|
||||
})
|
||||
return mId
|
||||
}
|
||||
|
||||
func sendTreeDisconnect(id treeId: UInt32, completionHandler: SimpleCompletionHandler) -> UInt64 {
|
||||
let mId = messageId()
|
||||
let smbHeader = SMB2.Header(command: .TREE_DISCONNECT, creditRequestResponse: 111, messageId: mId, treeId: treeId, sessionId: sessionId)
|
||||
@@ -92,18 +105,15 @@ class SMB2ProtocolClient: FPSStreamTask {
|
||||
return mId
|
||||
}
|
||||
|
||||
func messageId() -> UInt64 {
|
||||
defer {
|
||||
currentMessageID += 1
|
||||
}
|
||||
return currentMessageID
|
||||
func reset() {
|
||||
|
||||
}
|
||||
|
||||
// MARK: create and analyse messages
|
||||
|
||||
}
|
||||
|
||||
// MARK: create and analyse messages
|
||||
extension SMB2ProtocolClient {
|
||||
func determineSMBVersion(_ data: Data) -> Float {
|
||||
var smbverChar: Int8 = 0
|
||||
(data as NSData).getBytes(&smbverChar, length: 1)
|
||||
let smbverChar: Int8 = Int8(bitPattern: data.first ?? 0)
|
||||
let version = 0 - smbverChar
|
||||
return Float(version)
|
||||
}
|
||||
@@ -117,7 +127,7 @@ class SMB2ProtocolClient: FPSStreamTask {
|
||||
throw SMBFileProviderError.incompatibleHeader
|
||||
}
|
||||
let headersize = MemoryLayout<SMB1.Header>.size
|
||||
let header: SMB1.Header = decode(data)
|
||||
let header: SMB1.Header = data.scanValue()!
|
||||
var blocks = [(params: [UInt16], message: Data?)]()
|
||||
var offset = headersize
|
||||
while offset < data.count {
|
||||
@@ -129,22 +139,22 @@ class SMB2ProtocolClient: FPSStreamTask {
|
||||
offset += MemoryLayout<UInt8>.size
|
||||
var rawParamWords = [UInt8](buffer[offset..<(offset + paramWordsCount * 2)])
|
||||
let paramData = Data(bytesNoCopy: UnsafeMutablePointer<UInt8>(&rawParamWords), count: rawParamWords.count, deallocator: .free)
|
||||
paramWords = decode(paramData)
|
||||
paramWords = paramData.scanValue()!
|
||||
offset += paramWordsCount * 2
|
||||
let messageBytesCount = Int(UInt16(buffer[0]) + UInt16(buffer[1]) << 8)
|
||||
offset += MemoryLayout<UInt16>.size
|
||||
guard data.count >= (offset + messageBytesCount) else {
|
||||
throw SMBFileProviderError.incorrectMessageLength
|
||||
}
|
||||
var rawMessage = [UInt8](buffer[offset..<(offset + messageBytesCount)])
|
||||
let rawMessage = [UInt8](buffer[offset..<(offset + messageBytesCount)])
|
||||
offset += messageBytesCount
|
||||
let message = NSData(bytes: &rawMessage, length: rawMessage.count) as Data
|
||||
let message = Data(bytes: rawMessage)
|
||||
blocks.append((params: paramWords, message: message))
|
||||
}
|
||||
return (header, blocks)
|
||||
}
|
||||
|
||||
func digestSMB2Message(_ data: Data) throws -> (header: SMB2.Header, message: SMBResponse?)? {
|
||||
func digestSMB2Message(_ data: Data) throws -> SMBResponse? {
|
||||
guard data.count > 65 else {
|
||||
throw URLError(.badServerResponse)
|
||||
}
|
||||
@@ -152,10 +162,10 @@ class SMB2ProtocolClient: FPSStreamTask {
|
||||
throw SMBFileProviderError.incompatibleHeader
|
||||
}
|
||||
let headersize = MemoryLayout<SMB2.Header>.size
|
||||
let headerData = data.subdata(in: NSRange(location: 0, length: headersize))
|
||||
let headerData = data.subdata(in: 0..<headersize)
|
||||
let messageSize = data.count - headersize
|
||||
let messageData = data.subdata(in: NSRange(location: headersize, length: messageSize))
|
||||
let header: SMB2.Header = decode(headerData)
|
||||
let messageData = data.subdata(in: headersize..<(headersize + messageSize))
|
||||
let header: SMB2.Header = headerData.scanValue()!
|
||||
switch header.command {
|
||||
case .NEGOTIATE:
|
||||
return (header, SMB2.NegotiateResponse(data: messageData))
|
||||
@@ -201,8 +211,7 @@ class SMB2ProtocolClient: FPSStreamTask {
|
||||
}
|
||||
|
||||
func createSMBMessage(header: SMB1.Header, blocks: [(params: Data?, message: Data?)]) -> Data {
|
||||
var headerv = header
|
||||
var result = NSData(data: encode(&headerv)) as Data
|
||||
var result = Data(value: header)
|
||||
for block in blocks {
|
||||
var paramWordsCount = UInt8(block.params?.count ?? 0)
|
||||
result.append(¶mWordsCount, count: MemoryLayout.size(ofValue: paramWordsCount))
|
||||
@@ -219,10 +228,9 @@ class SMB2ProtocolClient: FPSStreamTask {
|
||||
return result
|
||||
}
|
||||
|
||||
func createSMB2Message(header: SMB2.Header, message: SMBRequest) -> Data {
|
||||
var headerv = header
|
||||
var result = NSData(data: encode(&headerv)) as Data
|
||||
result.append(message.data() as Data)
|
||||
func createSMB2Message(header: SMB2.Header, message: SMBRequestBody) -> Data {
|
||||
var result = Data(value: header)
|
||||
result.append(message.data())
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
open class SMBFileProvider: FileProvider, FileProviderMonitor {
|
||||
open var type: String = "Samba"
|
||||
class SMBFileProvider: FileProvider, FileProviderMonitor {
|
||||
open static var type: String = "Samba"
|
||||
open var isPathRelative: Bool = true
|
||||
open var baseURL: URL?
|
||||
open var currentPath: String = ""
|
||||
@@ -24,16 +24,13 @@ open class SMBFileProvider: FileProvider, FileProviderMonitor {
|
||||
return nil
|
||||
}
|
||||
self.baseURL = baseURL
|
||||
dispatch_queue = DispatchQueue(label: "FileProvider.\(type)", attributes: DispatchQueue.Attributes.concurrent)
|
||||
dispatch_queue = DispatchQueue(label: "FileProvider.\(SMBFileProvider.type)", attributes: DispatchQueue.Attributes.concurrent)
|
||||
//let url = baseURL.uw_absoluteString
|
||||
self.credential = credential
|
||||
}
|
||||
|
||||
open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObjectClass], _ error: Error?) -> Void)) {
|
||||
NotImplemented()
|
||||
dispatch_queue.async {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObjectClass?, _ error: Error?) -> Void)) {
|
||||
@@ -46,44 +43,54 @@ open class SMBFileProvider: FileProvider, FileProviderMonitor {
|
||||
|
||||
open weak var fileOperationDelegate: FileOperationDelegate?
|
||||
|
||||
open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) {
|
||||
open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
NotImplemented()
|
||||
return nil
|
||||
}
|
||||
|
||||
open func create(file fileAttribs: FileObject, at atPath: String, contents data: Data?, completionHandler: SimpleCompletionHandler) {
|
||||
open func create(file fileName: String, at atPath: String, contents data: Data?, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
NotImplemented()
|
||||
return nil
|
||||
}
|
||||
|
||||
open func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
|
||||
open func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
NotImplemented()
|
||||
return nil
|
||||
}
|
||||
|
||||
open func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
|
||||
open func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
NotImplemented()
|
||||
return nil
|
||||
}
|
||||
|
||||
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) {
|
||||
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
NotImplemented()
|
||||
return nil
|
||||
}
|
||||
|
||||
open func copyItem(localFile: URL, to toPath: String, completionHandler: SimpleCompletionHandler) {
|
||||
open func copyItem(localFile: URL, to toPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
NotImplemented()
|
||||
return nil
|
||||
}
|
||||
|
||||
open func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) {
|
||||
open func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
NotImplemented()
|
||||
return nil
|
||||
}
|
||||
|
||||
open func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) {
|
||||
open func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
|
||||
NotImplemented()
|
||||
return nil
|
||||
}
|
||||
|
||||
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) {
|
||||
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
|
||||
NotImplemented()
|
||||
return nil
|
||||
}
|
||||
|
||||
open func writeContents(path: String, contents data: Data, atomically: Bool, completionHandler: SimpleCompletionHandler) {
|
||||
open func writeContents(path: String, contents data: Data, atomically: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
NotImplemented()
|
||||
return nil
|
||||
}
|
||||
|
||||
open func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler:((FileObjectClass) -> Void)?, completionHandler: @escaping ((_ files: [FileObjectClass], _ error: Error?) -> Void)) {
|
||||
@@ -101,6 +108,14 @@ open class SMBFileProvider: FileProvider, FileProviderMonitor {
|
||||
open func isRegisteredForNotification(path: String) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
open func copy(with zone: NSZone? = nil) -> Any {
|
||||
let copy = SMBFileProvider(baseURL: self.baseURL!, credential: self.credential!, afterInitialized: { _ in })!
|
||||
copy.currentPath = self.currentPath
|
||||
copy.delegate = self.delegate
|
||||
copy.fileOperationDelegate = self.fileOperationDelegate
|
||||
return copy
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: basic CIFS interactivity
|
||||
|
||||
@@ -8,17 +8,35 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol SMBRequest {
|
||||
protocol SMBRequestBody {
|
||||
func data() -> Data
|
||||
}
|
||||
|
||||
protocol SMBResponse {
|
||||
extension SMBRequestBody {
|
||||
func data() -> Data {
|
||||
return Data(value: self)
|
||||
}
|
||||
}
|
||||
|
||||
protocol SMBResponseBody {
|
||||
init? (data: Data)
|
||||
}
|
||||
|
||||
extension SMBResponseBody {
|
||||
init? (data: Data) {
|
||||
if let v: Self = data.scanValue() {
|
||||
self = v
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protocol IOCtlRequestProtocol: SMBRequest {}
|
||||
protocol IOCtlResponseProtocol: SMBResponse {}
|
||||
typealias SMBRequest = (header: SMB2.Header, body: SMBRequestBody?)
|
||||
typealias SMBResponse = (header: SMB2.Header, body: SMBResponseBody?)
|
||||
|
||||
protocol IOCtlRequestProtocol: SMBRequestBody {}
|
||||
protocol IOCtlResponseProtocol: SMBResponseBody {}
|
||||
|
||||
|
||||
struct SMBTime {
|
||||
|
||||
@@ -11,7 +11,7 @@ import Foundation
|
||||
extension SMB2 {
|
||||
// MARK: SMB2 Create
|
||||
|
||||
struct CreateRequest: SMBRequest {
|
||||
struct CreateRequest: SMBRequestBody {
|
||||
let header: CreateRequest.Header
|
||||
let name: String?
|
||||
let contexts: [CreateContext]
|
||||
@@ -25,8 +25,8 @@ extension SMB2 {
|
||||
func data() -> Data {
|
||||
var header = self.header
|
||||
var offset = 0x78 //UInt16(sizeof(SMB2.Header.self) + sizeof(CreateContext.Header.self) - 1)
|
||||
let body = NSMutableData()
|
||||
if let name = self.name, let nameData = name.data(using: String.Encoding.utf16) {
|
||||
var body = Data()
|
||||
if let name = self.name, let nameData = name.data(using: .utf16) {
|
||||
header.nameOffset = UInt16(offset)
|
||||
header.nameLength = UInt16(nameData.count)
|
||||
offset += nameData.count
|
||||
@@ -40,8 +40,8 @@ extension SMB2 {
|
||||
header.contextLength = 0
|
||||
//result.appendData(nameData)
|
||||
}
|
||||
var result = NSData(data: encode(&header)) as Data
|
||||
result.append(body as Data)
|
||||
var result = Data(value: header)
|
||||
result.append(body)
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ extension SMB2 {
|
||||
}
|
||||
}
|
||||
|
||||
struct CreateResponse: SMBResponse {
|
||||
struct CreateResponse: SMBResponseBody {
|
||||
struct Header {
|
||||
let size: UInt16
|
||||
fileprivate let _oplockLevel: UInt8
|
||||
@@ -186,7 +186,7 @@ extension SMB2 {
|
||||
guard data.count >= MemoryLayout<CreateResponse.Header>.size else {
|
||||
return nil
|
||||
}
|
||||
self.header = decode(data)
|
||||
self.header = data.scanValue()!
|
||||
if self.header.contextsOffset > 0 {
|
||||
var contexts = [CreateContext]()
|
||||
var contextOffset = Int(self.header.contextsOffset) - MemoryLayout<SMB2.Header>.size
|
||||
@@ -195,14 +195,9 @@ extension SMB2 {
|
||||
self.contexts = contexts
|
||||
return
|
||||
}
|
||||
let contextDataHeader = data.subdata(in: NSRange(location: contextOffset, length: MemoryLayout<CreateContext.Header>.size))
|
||||
if let lastContextHeader = CreateContext(data: contextDataHeader) {
|
||||
let lastContextLen = Int(lastContextHeader.header.dataOffset) + Int(lastContextHeader.header.dataLength) - contextOffset
|
||||
let lastContextData = data.subdata(in: NSRange(location: contextOffset, length: lastContextLen))
|
||||
if let newContext = CreateContext(data: lastContextData) {
|
||||
contexts.append(newContext)
|
||||
}
|
||||
contextOffset = Int(lastContextHeader.header.next) - MemoryLayout<SMB2.Header>.size
|
||||
while contextOffset > 0, let context: CreateContext = data.scanValue(start: contextOffset) {
|
||||
contexts.append(context)
|
||||
contextOffset = Int(context.header.next) - MemoryLayout<SMB2.Header>.size
|
||||
}
|
||||
}
|
||||
self.contexts = contexts
|
||||
@@ -226,7 +221,7 @@ extension SMB2 {
|
||||
let buffer: Data
|
||||
|
||||
init(name: ContextNames, data: Data) {
|
||||
let nameData = NSData(data: (name.rawValue).data(using: String.Encoding.utf16)!) as Data
|
||||
let nameData = (name.rawValue).data(using: .utf16) ?? Data()
|
||||
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
|
||||
}
|
||||
@@ -234,8 +229,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)
|
||||
let nameData = NSMutableData(bytes: &uuid, length: 16)
|
||||
self.header = CreateContext.Header(next: 0, nameOffset: 32, nameLength: UInt16(nameData.length), reserved: 0, dataOffset: UInt16(nameData.length), dataLength: UInt32(data.count))
|
||||
var nameData = Data(bytes: &uuid.0, count: 16)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -244,12 +239,12 @@ extension SMB2 {
|
||||
guard data.count > headersize else {
|
||||
return nil
|
||||
}
|
||||
self.header = decode(data)
|
||||
self.buffer = data.subdata(in: NSRange(location: headersize, length: data.count - headersize))
|
||||
self.header = data.scanValue()!
|
||||
self.buffer = data.subdata(in: headersize..<data.count)
|
||||
}
|
||||
|
||||
func data() -> Data {
|
||||
var result = NSData(data: encode(header)) as Data
|
||||
var result = Data(value: header)
|
||||
result.append(buffer)
|
||||
return result
|
||||
}
|
||||
@@ -363,7 +358,7 @@ extension SMB2 {
|
||||
|
||||
// MARK: SMB2 Close
|
||||
|
||||
struct CloseRequest: SMBRequest {
|
||||
struct CloseRequest: SMBRequestBody {
|
||||
let size: UInt16
|
||||
let flags: CloseFlags
|
||||
fileprivate let reserved2: UInt32
|
||||
@@ -377,13 +372,9 @@ extension SMB2 {
|
||||
self.flags = []
|
||||
self.reserved2 = 0
|
||||
}
|
||||
|
||||
func data() -> Data {
|
||||
return encode(self)
|
||||
}
|
||||
}
|
||||
|
||||
struct CloseResponse: SMBResponse {
|
||||
struct CloseResponse: SMBResponseBody {
|
||||
let size: UInt16
|
||||
let flags: CloseFlags
|
||||
fileprivate let reserved: UInt32
|
||||
@@ -394,10 +385,6 @@ extension SMB2 {
|
||||
let allocationSize: UInt64
|
||||
let endOfFile: UInt64
|
||||
let fileAttributes: FileAttributes
|
||||
|
||||
init? (data: Data) {
|
||||
self = decode(data)
|
||||
}
|
||||
}
|
||||
|
||||
struct CloseFlags: OptionSet {
|
||||
@@ -412,7 +399,7 @@ extension SMB2 {
|
||||
|
||||
// MARK: SMB2 Flush
|
||||
|
||||
struct FlushRequest: SMBRequest {
|
||||
struct FlushRequest: SMBRequestBody {
|
||||
let size: UInt16
|
||||
fileprivate let reserved: UInt16
|
||||
fileprivate let reserved2: UInt32
|
||||
@@ -426,13 +413,9 @@ extension SMB2 {
|
||||
self.reserved = 0
|
||||
self.reserved2 = 0
|
||||
}
|
||||
|
||||
func data() -> Data {
|
||||
return encode(self)
|
||||
}
|
||||
}
|
||||
|
||||
struct FlushResponse: SMBResponse {
|
||||
struct FlushResponse: SMBResponseBody {
|
||||
let size: UInt16
|
||||
let reserved: UInt16
|
||||
|
||||
@@ -440,9 +423,5 @@ extension SMB2 {
|
||||
self.size = 4
|
||||
self.reserved = 0
|
||||
}
|
||||
|
||||
init? (data: Data) {
|
||||
self = decode(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import Foundation
|
||||
extension SMB2 {
|
||||
// MARK: SMB2 Read
|
||||
|
||||
struct ReadRequest: SMBRequest {
|
||||
struct ReadRequest: SMBRequestBody {
|
||||
let size: UInt16
|
||||
fileprivate let padding: UInt8
|
||||
let flags: ReadRequest.Flags
|
||||
@@ -43,10 +43,6 @@ extension SMB2 {
|
||||
self.channelBuffer = 0
|
||||
}
|
||||
|
||||
func data() -> Data {
|
||||
return encode(read)
|
||||
}
|
||||
|
||||
struct Flags: OptionSet {
|
||||
let rawValue: UInt8
|
||||
|
||||
@@ -58,7 +54,7 @@ extension SMB2 {
|
||||
}
|
||||
}
|
||||
|
||||
struct ReadRespone: SMBResponse {
|
||||
struct ReadRespone: SMBResponseBody {
|
||||
struct Header {
|
||||
let size: UInt16
|
||||
let offset: UInt8
|
||||
@@ -75,9 +71,9 @@ extension SMB2 {
|
||||
guard data.count > 16 else {
|
||||
return nil
|
||||
}
|
||||
self.header = decode(data)
|
||||
self.header = data.scanValue()!
|
||||
let headersize = MemoryLayout<Header>.size
|
||||
self.buffer = data.subdata(in: NSRange(location: headersize, length: data.count - headersize))
|
||||
self.buffer = data.subdata(in: headersize..<data.count)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +85,7 @@ extension SMB2 {
|
||||
|
||||
// MARK: SMB2 Write
|
||||
|
||||
struct WriteRequest: SMBRequest {
|
||||
struct WriteRequest: SMBRequestBody {
|
||||
let header: WriteRequest.Header
|
||||
let channelInfo: ChannelInfo?
|
||||
let fileData: Data
|
||||
@@ -124,7 +120,7 @@ extension SMB2 {
|
||||
}
|
||||
|
||||
func data() -> Data {
|
||||
var result = NSData(data: encode(self.header)) as Data
|
||||
var result = Data(value: self.header)
|
||||
if let channelInfo = channelInfo {
|
||||
result.append(channelInfo.data())
|
||||
}
|
||||
@@ -144,41 +140,29 @@ extension SMB2 {
|
||||
}
|
||||
}
|
||||
|
||||
struct WriteResponse: SMBResponse {
|
||||
struct WriteResponse: SMBResponseBody {
|
||||
let size: UInt16
|
||||
fileprivate let reserved: UInt16
|
||||
let writtenBytes: UInt32
|
||||
fileprivate let remaining: UInt32
|
||||
fileprivate let channelInfoOffset: UInt16
|
||||
fileprivate let channelInfoLength: UInt16
|
||||
|
||||
init?(data: Data) {
|
||||
self = decode(data)
|
||||
}
|
||||
}
|
||||
|
||||
struct ChannelInfo: SMBRequest {
|
||||
struct ChannelInfo: SMBRequestBody {
|
||||
let offset: UInt64
|
||||
let token: UInt32
|
||||
let length: UInt32
|
||||
|
||||
func data() -> Data {
|
||||
return encode(data)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: SMB2 Lock
|
||||
|
||||
struct LockElement: SMBRequest {
|
||||
struct LockElement: SMBRequestBody {
|
||||
let offset: UInt64
|
||||
let length: UInt64
|
||||
let flags: LockElement.Flags
|
||||
fileprivate let reserved: UInt32
|
||||
|
||||
func data() -> Data {
|
||||
return encode(self)
|
||||
}
|
||||
|
||||
struct Flags: OptionSet {
|
||||
let rawValue: UInt32
|
||||
|
||||
@@ -193,7 +177,7 @@ extension SMB2 {
|
||||
}
|
||||
}
|
||||
|
||||
struct LockRequest: SMBRequest {
|
||||
struct LockRequest: SMBRequestBody {
|
||||
let header: LockRequest.Header
|
||||
let locks: [LockElement]
|
||||
|
||||
@@ -203,9 +187,9 @@ extension SMB2 {
|
||||
}
|
||||
|
||||
func data() -> Data {
|
||||
var result = NSData(data: encode(header)) as Data
|
||||
var result = Data(value: header)
|
||||
for lock in locks {
|
||||
result.append(encode(lock))
|
||||
result.append(Data(value: lock))
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -218,7 +202,7 @@ extension SMB2 {
|
||||
}
|
||||
}
|
||||
|
||||
struct LockResponse: SMBResponse {
|
||||
struct LockResponse: SMBResponseBody {
|
||||
let size: UInt16
|
||||
let reserved: UInt16
|
||||
|
||||
@@ -226,15 +210,11 @@ extension SMB2 {
|
||||
self.size = 4
|
||||
self.reserved = 0
|
||||
}
|
||||
|
||||
init? (data: Data) {
|
||||
self = decode(data)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: SMB2 Cancel
|
||||
|
||||
struct CancelRequest: SMBRequest {
|
||||
struct CancelRequest: SMBRequestBody {
|
||||
let size: UInt16
|
||||
let reserved: UInt16
|
||||
|
||||
@@ -242,9 +222,5 @@ extension SMB2 {
|
||||
self.size = 4
|
||||
self.reserved = 0
|
||||
}
|
||||
|
||||
func data() -> Data {
|
||||
return encode(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ extension SMB2 {
|
||||
* IOCtl usage is usually limited in SMB to pipe requests and duplicating file inside server
|
||||
*/
|
||||
|
||||
struct IOCtlRequest: SMBRequest {
|
||||
struct IOCtlRequest: SMBRequestBody {
|
||||
let header: Header
|
||||
let requestData: IOCtlRequestProtocol?
|
||||
|
||||
@@ -26,7 +26,7 @@ extension SMB2 {
|
||||
}
|
||||
|
||||
func data() -> Data {
|
||||
var result = NSData(data: encode(self.header)) as Data
|
||||
var result = Data(value: self.header)
|
||||
if let reqData = requestData?.data() {
|
||||
result.append(reqData)
|
||||
}
|
||||
@@ -63,14 +63,14 @@ extension SMB2 {
|
||||
}
|
||||
}
|
||||
|
||||
struct IOCtlResponse: SMBResponse {
|
||||
struct IOCtlResponse: SMBResponseBody {
|
||||
let header: Header
|
||||
let responseData: IOCtlResponseProtocol?
|
||||
|
||||
init?(data: Data) {
|
||||
self.header = decode(data)
|
||||
let responseRange = NSRange(location: Int(self.header.outputOffset - 64), length: Int(self.header.outputCount))
|
||||
let response = data.subdata(in: responseRange)
|
||||
self.header = data.scanValue()!
|
||||
let endRange = Int(self.header.outputOffset - 64) + Int(self.header.outputCount)
|
||||
let response = data.subdata(in: Int(self.header.outputOffset - 64)..<endRange)
|
||||
switch self.header.ctlCode {
|
||||
case .SRV_COPYCHUNK, .SRV_COPYCHUNK_WRITE:
|
||||
self.responseData = IOCtlResponseData.SrvCopyChunk(data: response)
|
||||
@@ -140,10 +140,10 @@ extension SMB2 {
|
||||
let chunks: [Chunk]
|
||||
|
||||
func data() -> Data {
|
||||
var result = NSData(data: encode(sourceKey)) as Data
|
||||
result.append(encode(chunkCount))
|
||||
var reserved: UInt32 = 0
|
||||
result.append(encode(&reserved))
|
||||
var result = Data(value: sourceKey)
|
||||
result.append(Data(value: chunkCount))
|
||||
let reserved: UInt32 = 0
|
||||
result.append(Data(value: reserved))
|
||||
return Data()
|
||||
}
|
||||
|
||||
@@ -152,10 +152,6 @@ extension SMB2 {
|
||||
let targetOffset: UInt64
|
||||
let length: UInt32
|
||||
fileprivate let reserved: UInt32
|
||||
|
||||
func data() -> Data {
|
||||
return encode(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,10 +178,6 @@ extension SMB2 {
|
||||
self.length = length
|
||||
self.offset = offset
|
||||
}
|
||||
|
||||
func data() -> Data {
|
||||
return encode(self)
|
||||
}
|
||||
}
|
||||
|
||||
struct ResilencyRequest: IOCtlRequestProtocol {
|
||||
@@ -197,10 +189,6 @@ extension SMB2 {
|
||||
self.timeout = timeout
|
||||
self.reserved = 0
|
||||
}
|
||||
|
||||
func data() -> Data {
|
||||
return encode(self)
|
||||
}
|
||||
}
|
||||
|
||||
struct ValidateNegotiateInfo: IOCtlRequestProtocol {
|
||||
@@ -213,8 +201,8 @@ extension SMB2 {
|
||||
}
|
||||
|
||||
func data() -> Data {
|
||||
var result = NSData(data: encode(self.header)) as Data
|
||||
dialects.forEach { result.append(encode($0)) }
|
||||
var result = Data(value: self.header)
|
||||
dialects.forEach { result.append(Data(value: $0)) }
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -234,10 +222,6 @@ extension SMB2 {
|
||||
let chunksCount: UInt32
|
||||
let chunksBytesWritten: UInt32
|
||||
let totalBytesWriiten: UInt32
|
||||
|
||||
init?(data: Data) {
|
||||
self = decode(data)
|
||||
}
|
||||
}
|
||||
|
||||
// SRV_ENUMERATE_SNAPSHOTS
|
||||
@@ -247,8 +231,9 @@ extension SMB2 {
|
||||
let snapshots: [SMBTime]
|
||||
|
||||
init?(data: Data) {
|
||||
self.count = decode(data)
|
||||
self.returnedCount = decode(data.subdata(in: NSRange(location: 4, length: 4)))
|
||||
guard data.count > 8 else { return nil }
|
||||
self.count = data.scanValue()!
|
||||
self.returnedCount = data.scanValue(start: 4)!
|
||||
//let size: UInt32 = decode(data.subdataWithRange(NSRange(location: 8, length: 4)))
|
||||
var snapshots = [SMBTime]()
|
||||
let dateFormatter = DateFormatter()
|
||||
@@ -258,7 +243,7 @@ extension SMB2 {
|
||||
if data.count < offset + 48 {
|
||||
return nil
|
||||
}
|
||||
let datestring = String(data: data.subdata(in: NSRange(location: offset, length: 48)), encoding: String.Encoding.utf16)
|
||||
let datestring = data.scanString(start: offset, length: 48, encoding: .utf16)
|
||||
if let datestring = datestring, let date = dateFormatter.date(from: datestring) {
|
||||
snapshots.append(SMBTime(date: date))
|
||||
}
|
||||
@@ -271,32 +256,21 @@ extension SMB2 {
|
||||
let key: (UInt64, UInt64, UInt64)
|
||||
fileprivate let contextLength: UInt32
|
||||
fileprivate let context: UInt32
|
||||
|
||||
init?(data: Data) {
|
||||
self = decode(data)
|
||||
}
|
||||
}
|
||||
|
||||
struct ReadHash: IOCtlResponseProtocol {
|
||||
// TODO: Implement IOCTL READ_HASH
|
||||
|
||||
init?(data: Data) {
|
||||
self = decode(data)
|
||||
}
|
||||
}
|
||||
|
||||
struct NetworkInterfaceInfo: IOCtlResponseProtocol {
|
||||
let items: [NetworkInterfaceInfo.Item]
|
||||
|
||||
init?(data: Data) {
|
||||
let count = data.count / MemoryLayout<Item>.size
|
||||
guard count > 0 else {
|
||||
return nil
|
||||
}
|
||||
var items = [Item]()
|
||||
for i in 0..<count {
|
||||
let itemdata = data.subdata(in: NSRange(location: i * MemoryLayout<Item>.size, length: MemoryLayout<Item>.size))
|
||||
items.append(decode(itemdata))
|
||||
var offset = 0
|
||||
while let item: Item = data.scanValue(start: offset) {
|
||||
items.append(item)
|
||||
offset += MemoryLayout<Item>.size
|
||||
}
|
||||
self.items = items
|
||||
}
|
||||
@@ -310,7 +284,8 @@ extension SMB2 {
|
||||
fileprivate let reserved: UInt32
|
||||
/// Speed of the network interface in bits per second
|
||||
let linkSpeed: UInt64
|
||||
fileprivate let sockaddrStorage: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
|
||||
fileprivate let sockaddrStorage:
|
||||
(UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
|
||||
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
|
||||
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
|
||||
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
|
||||
@@ -334,15 +309,11 @@ extension SMB2 {
|
||||
static let ipv6: sa_family_t = 0x17
|
||||
|
||||
var sockaddr: sockaddr_in {
|
||||
var sockaddrStorage = self.sockaddrStorage
|
||||
let data = Data(bytes: &sockaddrStorage, count: 16)
|
||||
return decode(data)
|
||||
return Data.mapMemory(from: self.sockaddrStorage)!
|
||||
}
|
||||
|
||||
var sockaddr6: sockaddr_in6 {
|
||||
var sockaddrStorage = self.sockaddrStorage
|
||||
let data = Data(bytes: &sockaddrStorage, count: 28)
|
||||
return decode(data)
|
||||
return Data.mapMemory(from: self.sockaddrStorage)!
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -355,10 +326,6 @@ extension SMB2 {
|
||||
var dialect: (major: Int, minor: Int) {
|
||||
return (major: Int(_dialect & 0xFF), minor: Int(_dialect >> 8))
|
||||
}
|
||||
|
||||
init?(data: Data) {
|
||||
self = decode(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import Foundation
|
||||
extension SMB2 {
|
||||
// MARK: SMB2 Change Notify
|
||||
|
||||
struct ChangeNotifyRequest: SMBRequest {
|
||||
struct ChangeNotifyRequest: SMBRequestBody {
|
||||
let size: UInt16
|
||||
let flags: ChangeNotifyRequest.Flags
|
||||
let outputBufferLength: UInt32
|
||||
@@ -28,10 +28,6 @@ extension SMB2 {
|
||||
self.reserved = 0
|
||||
}
|
||||
|
||||
func data() -> Data {
|
||||
return encode(self)
|
||||
}
|
||||
|
||||
struct Flags: OptionSet {
|
||||
let rawValue: UInt16
|
||||
|
||||
@@ -79,7 +75,7 @@ extension SMB2 {
|
||||
}
|
||||
}
|
||||
|
||||
struct ChangeNotifyResponse: SMBResponse {
|
||||
struct ChangeNotifyResponse: SMBResponseBody {
|
||||
let notifications: [(action: FileNotifyAction, fileName: String)]
|
||||
|
||||
init?(data: Data) {
|
||||
@@ -87,22 +83,19 @@ extension SMB2 {
|
||||
var i = 0
|
||||
var result = [(action: FileNotifyAction, fileName: String)]()
|
||||
|
||||
var offset: UInt32 = 0
|
||||
var offset = 0
|
||||
while i < maxLoop {
|
||||
let actionData = data.subdata(in: NSRange(location: Int(offset + 4), length: 4))
|
||||
let actionValue: UInt32 = decode(actionData)
|
||||
let nextOffset: UInt32 = data.scanValue(start: offset) ?? 0
|
||||
let actionValue: UInt32 = data.scanValue(start: offset + 4) ?? 0
|
||||
guard let action = FileNotifyAction(rawValue: actionValue) else {
|
||||
continue
|
||||
}
|
||||
let fileLenData = data.subdata(in: NSRange(location: Int(offset + 8), length: 4))
|
||||
let fileNameLen: UInt32 = decode(fileLenData)
|
||||
let fileNameData = data.subdata(in: NSRange(location: Int(offset + 12), length: Int(12 + fileNameLen)))
|
||||
let fileName = String(data: fileNameData, encoding: String.Encoding.utf16) ?? ""
|
||||
|
||||
let fileNameLen = Int(data.scanValue(start: offset + 8) as UInt32? ?? 0)
|
||||
let fileName = data.scanString(start: offset + 12, length: fileNameLen, encoding: .utf16) ?? ""
|
||||
result.append((action: action, fileName: fileName))
|
||||
|
||||
let nextOffsetData = data.subdata(in: NSRange(location: Int(offset), length: 4))
|
||||
let nextOffset: UInt32 = decode(nextOffsetData)
|
||||
offset += nextOffset
|
||||
offset += Int(nextOffset)
|
||||
if nextOffset == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import Foundation
|
||||
extension SMB2 {
|
||||
// MARK: SMB2 Query Directory
|
||||
|
||||
struct QueryDirectoryRequest: SMBRequest {
|
||||
struct QueryDirectoryRequest: SMBRequestBody {
|
||||
let header: QueryDirectoryRequest.Header
|
||||
let searchPattern: String?
|
||||
|
||||
@@ -22,14 +22,14 @@ extension SMB2 {
|
||||
assert(FileInformationEnum.queryDirectory.contains(infoClass), "Invalid FileInformationClass used for QueryDirectoryRequest")
|
||||
let searchPatternOffset = searchPattern != nil ? MemoryLayout<SMB2.Header>.size + MemoryLayout<QueryDirectoryRequest.Header>.size : 0
|
||||
let nflags = flags.intersection(fileIndex > 0 ? [.INDEX_SPECIFIED] : [])
|
||||
let searchPatternLength = searchPattern?.data(using: String.Encoding.utf16)?.count ?? 0
|
||||
let searchPatternLength = searchPattern?.data(using: .utf16)?.count ?? 0
|
||||
self.header = Header(size: 53, infoClass: infoClass, flags: nflags, fileIndex: fileIndex, fileId: fileId, searchPatternOffset: UInt8(searchPatternOffset), searchPatternLength: UInt8(searchPatternLength), bufferLength: bufferLength)
|
||||
self.searchPattern = searchPattern
|
||||
}
|
||||
|
||||
func data() -> Data {
|
||||
var result = NSData(data: encode(header)) as Data
|
||||
if let patternData = searchPattern?.data(using: String.Encoding.utf16) {
|
||||
var result = Data(value: header)
|
||||
if let patternData = searchPattern?.data(using: .utf16) {
|
||||
result.append(patternData)
|
||||
}
|
||||
return result
|
||||
@@ -60,7 +60,7 @@ extension SMB2 {
|
||||
}
|
||||
}
|
||||
|
||||
struct QueryDirectoryResponse: SMBResponse {
|
||||
struct QueryDirectoryResponse: SMBResponseBody {
|
||||
let buffer: Data
|
||||
|
||||
func parseAs(type: FileInformationEnum) -> [(header: SMB2FilesInformationHeader, fileName: String)] {
|
||||
@@ -70,34 +70,22 @@ extension SMB2 {
|
||||
let header: SMB2FilesInformationHeader
|
||||
switch type {
|
||||
case .fileDirectoryInformation:
|
||||
let headerData = buffer.subdata(in: NSRange(location: offset, length: MemoryLayout<FileDirectoryInformationHeader>.size))
|
||||
let h: FileDirectoryInformationHeader = decode(headerData)
|
||||
header = h
|
||||
header = buffer.scanValue(start: offset) as FileDirectoryInformationHeader!
|
||||
case .fileFullDirectoryInformation:
|
||||
let headerData = buffer.subdata(in: NSRange(location: offset, length: MemoryLayout<FileFullDirectoryInformationHeader>.size))
|
||||
let h: FileFullDirectoryInformationHeader = decode(headerData)
|
||||
header = h
|
||||
header = buffer.scanValue(start: offset) as FileFullDirectoryInformationHeader!
|
||||
case .fileIdFullDirectoryInformation:
|
||||
let headerData = buffer.subdata(in: NSRange(location: offset, length: MemoryLayout<FileIdFullDirectoryInformationHeader>.size))
|
||||
let h: FileIdFullDirectoryInformationHeader = decode(headerData)
|
||||
header = h
|
||||
header = buffer.scanValue(start: offset) as FileIdFullDirectoryInformationHeader!
|
||||
case .fileBothDirectoryInformation:
|
||||
let headerData = buffer.subdata(in: NSRange(location: offset, length: MemoryLayout<FileBothDirectoryInformationHeader>.size))
|
||||
let h: FileBothDirectoryInformationHeader = decode(headerData)
|
||||
header = h
|
||||
header = buffer.scanValue(start: offset) as FileBothDirectoryInformationHeader!
|
||||
case .fileIdBothDirectoryInformation:
|
||||
let headerData = buffer.subdata(in: NSRange(location: offset, length: MemoryLayout<FileIdBothDirectoryInformationHeader>.size))
|
||||
let h: FileIdBothDirectoryInformationHeader = decode(headerData)
|
||||
header = h
|
||||
header = buffer.scanValue(start: offset) as FileIdBothDirectoryInformationHeader!
|
||||
case .fileNamesInformation:
|
||||
let headerData = buffer.subdata(in: NSRange(location: offset, length: MemoryLayout<FileNamesInformationHeader>.size))
|
||||
let h: FileNamesInformationHeader = decode(headerData)
|
||||
header = h
|
||||
header = buffer.scanValue(start: offset) as FileNamesInformationHeader!
|
||||
default:
|
||||
return []
|
||||
}
|
||||
let fnData = buffer.subdata(in: NSRange(location: offset + MemoryLayout.size(ofValue: header), length: Int(header.fileNameLength)))
|
||||
let fileName = String(data: fnData, encoding: String.Encoding.utf16) ?? ""
|
||||
let headersize = MemoryLayout.size(ofValue: header)
|
||||
let fileName = buffer.scanString(start: headersize, length: Int(header.fileNameLength), encoding: .utf16) ?? ""
|
||||
result.append((header: header, fileName: fileName))
|
||||
if header.nextEntryOffset == 0 {
|
||||
break
|
||||
@@ -108,18 +96,18 @@ extension SMB2 {
|
||||
}
|
||||
|
||||
init? (data: Data) {
|
||||
let offset: UInt16 = decode(data.subdata(in: NSRange(location: 2, length: 2)))
|
||||
let length: UInt32 = decode(data.subdata(in: NSRange(location: 4, length: 4)))
|
||||
guard data.count > Int(offset) + Int(length) else {
|
||||
let offset = Int(data.scanValue(start: 2) as UInt16!)
|
||||
let length = Int(data.scanValue(start: 4) as UInt32!)
|
||||
guard data.count > offset + length else {
|
||||
return nil
|
||||
}
|
||||
self.buffer = data.subdata(in: NSRange(location: Int(offset), length: Int(length)))
|
||||
self.buffer = data.subdata(in: offset..<(offset + length))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: SMB2 Query Info
|
||||
|
||||
struct QueryInfoRequest: SMBRequest {
|
||||
struct QueryInfoRequest: SMBRequestBody {
|
||||
let header: Header
|
||||
let buffer: Data?
|
||||
|
||||
@@ -129,22 +117,24 @@ extension SMB2 {
|
||||
}
|
||||
|
||||
init(fileId: FileId, extendedAttributes: [String], flags: Flags = [], outputBufferLength: UInt32 = 65535) {
|
||||
let buffer = NSMutableData()
|
||||
var buffer = Data()
|
||||
for ea in extendedAttributes {
|
||||
let strData = ea.data(using: String.Encoding.ascii)!
|
||||
guard let strData = ea.data(using: .ascii) else {
|
||||
continue
|
||||
}
|
||||
let strLength = UInt8(strData.count)
|
||||
let nextOffset = UInt32(4 + 1 + strData.count)
|
||||
let data = (encode(nextOffset) as NSData).mutableCopy() as! NSMutableData
|
||||
data.append(encode(strLength))
|
||||
var data = Data(value: nextOffset)
|
||||
data.append(Data(value: strLength))
|
||||
data.append(strData)
|
||||
data.length += 1
|
||||
let padSize = (data.length) % 4
|
||||
data.length += padSize
|
||||
data.count += 1
|
||||
let padSize = (data.count) % 4
|
||||
data.count += padSize
|
||||
buffer.append(data as Data)
|
||||
}
|
||||
|
||||
let bufferOffset = UInt16(MemoryLayout<SMB2.Header>.size + MemoryLayout<QueryInfoRequest.Header>.size)
|
||||
self.header = Header(size: 41, infoType: 1, infoClass: FileInformationEnum.fileFullEaInformation.rawValue, outputBufferLength: outputBufferLength, inputBufferOffset: bufferOffset, reserved: 0, inputBufferLength: UInt32(buffer.length), additionalInformation: [], flags: flags, fileId: fileId)
|
||||
self.header = Header(size: 41, infoType: 1, infoClass: FileInformationEnum.fileFullEaInformation.rawValue, outputBufferLength: outputBufferLength, inputBufferOffset: bufferOffset, reserved: 0, inputBufferLength: UInt32(buffer.count), additionalInformation: [], flags: flags, fileId: fileId)
|
||||
self.buffer = buffer as Data
|
||||
}
|
||||
|
||||
@@ -161,8 +151,7 @@ extension SMB2 {
|
||||
// TODO: Implement QUOTA_INFO init
|
||||
|
||||
func data() -> Data {
|
||||
let headerData = encode(header)
|
||||
var result = NSData(data: headerData) as Data
|
||||
var result = Data(value: header)
|
||||
if let buffer = buffer {
|
||||
result.append(buffer)
|
||||
}
|
||||
@@ -195,12 +184,11 @@ extension SMB2 {
|
||||
}
|
||||
}
|
||||
|
||||
struct QueryInfoResponse: SMBResponse {
|
||||
struct QueryInfoResponse: SMBResponseBody {
|
||||
let buffer: Data
|
||||
|
||||
init?(data: Data) {
|
||||
let structSizeData = data.subdata(in: NSRange(location: 0, length: 2))
|
||||
let structSize: UInt16 = decode(structSizeData)
|
||||
let structSize: UInt16 = data.scanValue()!
|
||||
guard structSize == 9 else {
|
||||
return nil
|
||||
}
|
||||
@@ -208,50 +196,48 @@ extension SMB2 {
|
||||
/*let offsetData = data.subdataWithRange(NSRange(location: 2, length: 2))
|
||||
let offset: UInt16 = decode(offsetData)*/
|
||||
|
||||
let lengthData = data.subdata(in: NSRange(location: 4, length: 4))
|
||||
let length: UInt32 = decode(lengthData)
|
||||
let length = Int(data.scanValue(start: 4) as UInt32!)
|
||||
|
||||
guard data.count >= 8 + Int(length) else {
|
||||
guard data.count >= 8 + length else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.buffer = data.subdata(in: NSRange(location: 8, length: Int(length)))
|
||||
self.buffer = data.subdata(in: 8..<(8 + length))
|
||||
}
|
||||
|
||||
var asAccessInformation: FileAccessInformation {
|
||||
return decode(buffer)
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asAlignmentInformation: FileAlignmentInformation {
|
||||
return decode(buffer)
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asAllInformation: (header: FileAllInformationHeader, name: String) {
|
||||
let header: FileAllInformationHeader = decode(buffer)
|
||||
let nameData = buffer.subdata(in: NSRange(location: MemoryLayout<FileAllInformationHeader>.size, length: Int(header.nameLength)))
|
||||
let name = String(data: nameData, encoding: String.Encoding.utf16) ?? ""
|
||||
let header: FileAllInformationHeader = buffer.scanValue()!
|
||||
let headersize = MemoryLayout<FileAllInformationHeader>.size
|
||||
let name = buffer.scanString(start: headersize, length: Int(header.nameLength), encoding: .utf16) ?? ""
|
||||
return (header, name)
|
||||
}
|
||||
|
||||
var asAlternateNameInformation: String {
|
||||
let b = (buffer as NSData).bytes.bindMemory(to: CChar.self, capacity: buffer.count)
|
||||
return String(cString: b, encoding: String.Encoding.utf16) ?? ""
|
||||
return buffer.scanString(start: 0, length: buffer.count, encoding: .utf16) ?? ""
|
||||
}
|
||||
|
||||
var asAttributeTagInformation: FileAttributeTagInformation {
|
||||
return decode(buffer)
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asBasicInformation: FileBasicInformation {
|
||||
return decode(buffer)
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asCompressionInformation: FileCompressionInformation {
|
||||
return decode(buffer)
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asEaInformation: FileEaInformation {
|
||||
return decode(buffer)
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asFullEaInformation: FileFullEaInformation {
|
||||
@@ -260,80 +246,80 @@ extension SMB2 {
|
||||
}
|
||||
|
||||
var asInternalInformation: FileInternalInformation {
|
||||
return decode(buffer)
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asModeInformation: FileModeInformation {
|
||||
return decode(buffer)
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asNetworkOpenInformation: FileNetworkOpenInformation {
|
||||
return decode(buffer)
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asPipeInformation: FilePipeInformation {
|
||||
return decode(buffer)
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asPipeLocalInformation: FilePipeLocalInformation {
|
||||
return decode(buffer)
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asPipeRemoteInformation: FilePipeRemoteInformation {
|
||||
return decode(buffer)
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asPositionInformation: FilePositionInformation {
|
||||
return decode(buffer)
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asStandardInformation: FileStandardInformation {
|
||||
return decode(buffer)
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asStreamInformation: (header: FileStreamInformationHeader, name: String) {
|
||||
let header: FileStreamInformationHeader = decode(buffer)
|
||||
let nameData = buffer.subdata(in: NSRange(location: MemoryLayout<FileStreamInformationHeader>.size, length: Int(header.streamNameLength)))
|
||||
let name = String(data: nameData, encoding: String.Encoding.utf16) ?? ""
|
||||
let header: FileStreamInformationHeader = buffer.scanValue()!
|
||||
let headersize = MemoryLayout<FileStreamInformationHeader>.size
|
||||
let name = buffer.scanString(start: headersize, length: Int(header.streamNameLength), encoding: .utf16) ?? ""
|
||||
return (header, name)
|
||||
}
|
||||
|
||||
var asFsVolumeInformation: (header: FileFsVolumeInformationHeader, name: String) {
|
||||
let header: FileFsVolumeInformationHeader = decode(buffer)
|
||||
let nameData = buffer.subdata(in: NSRange(location: MemoryLayout<FileFsVolumeInformationHeader>.size, length: Int(header.labelLength)))
|
||||
let name = String(data: nameData, encoding: String.Encoding.utf16) ?? ""
|
||||
let header: FileFsVolumeInformationHeader = buffer.scanValue()!
|
||||
let headersize = MemoryLayout<FileFsVolumeInformationHeader>.size
|
||||
let name = buffer.scanString(start: headersize, length: Int(header.labelLength), encoding: .utf16) ?? ""
|
||||
return (header, name)
|
||||
}
|
||||
|
||||
var asFsSizeInformation: FileFsSizeInformation {
|
||||
return decode(buffer)
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asFsDeviceInformation: FileFsDeviceInformation {
|
||||
return decode(buffer)
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asFsAttributeInformation: (header: FileFsAttributeInformationHeader, name: String) {
|
||||
let header: FileFsAttributeInformationHeader = decode(buffer)
|
||||
let nameData = buffer.subdata(in: NSRange(location: MemoryLayout<FileFsAttributeInformationHeader>.size, length: Int(header.nameLength)))
|
||||
let name = String(data: nameData, encoding: String.Encoding.utf16) ?? ""
|
||||
let header: FileFsAttributeInformationHeader = buffer.scanValue()!
|
||||
let headersize = MemoryLayout<FileFsAttributeInformationHeader>.size
|
||||
let name = buffer.scanString(start: headersize, length: Int(header.nameLength), encoding: .utf16) ?? ""
|
||||
return (header, name)
|
||||
}
|
||||
|
||||
var asFsControlInformation: FileFsControlInformation {
|
||||
return decode(buffer)
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asFsFullSizeInformation: FileFsFullSizeInformation {
|
||||
return decode(buffer)
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asFsObjectIdInformation: FileFsObjectIdInformation {
|
||||
return decode(buffer)
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asFsSectorSizeInformation: FileFsSectorSizeInformation {
|
||||
return decode(buffer)
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol SMB2FilesInformationHeader: SMBResponse {
|
||||
protocol SMB2FilesInformationHeader: SMBResponseBody {
|
||||
var nextEntryOffset: UInt32 { get }
|
||||
var fileIndex: UInt32 { get }
|
||||
var fileNameLength : UInt32 { get }
|
||||
@@ -16,7 +16,7 @@ protocol SMB2FilesInformationHeader: SMBResponse {
|
||||
|
||||
extension SMB2 {
|
||||
enum FileInformationEnum: UInt8 {
|
||||
case `nil` = 0x00
|
||||
case none = 0x00
|
||||
case fileDirectoryInformation = 0x01
|
||||
case fileFullDirectoryInformation = 0x02
|
||||
case fileBothDirectoryInformation = 0x03
|
||||
@@ -88,7 +88,7 @@ extension SMB2 {
|
||||
}
|
||||
|
||||
enum FileSystemInformationEnum: UInt8 {
|
||||
case `nil` = 0
|
||||
case none = 0
|
||||
case fileFsAttributeInformation
|
||||
case fileFsControlInformation
|
||||
case fileFsDeviceInformation
|
||||
@@ -127,10 +127,6 @@ extension SMB2 {
|
||||
let allocationSize: UInt64
|
||||
let fileAttributes: FileAttributes
|
||||
let fileNameLength : UInt32
|
||||
|
||||
init?(data: Data) {
|
||||
self = decode(data)
|
||||
}
|
||||
}
|
||||
|
||||
struct FileFullDirectoryInformationHeader: SMB2FilesInformationHeader {
|
||||
@@ -145,10 +141,6 @@ extension SMB2 {
|
||||
let fileAttributes: FileAttributes
|
||||
let fileNameLength : UInt32
|
||||
let extendedAttributesSize: UInt32
|
||||
|
||||
init?(data: Data) {
|
||||
self = decode(data)
|
||||
}
|
||||
}
|
||||
|
||||
struct FileIdFullDirectoryInformationHeader: SMB2FilesInformationHeader {
|
||||
@@ -165,10 +157,6 @@ extension SMB2 {
|
||||
let extendedAttributesSize: UInt32
|
||||
fileprivate let reserved: UInt32
|
||||
let fileId: FileId
|
||||
|
||||
init?(data: Data) {
|
||||
self = decode(data)
|
||||
}
|
||||
}
|
||||
|
||||
struct FileBothDirectoryInformationHeader: SMB2FilesInformationHeader {
|
||||
@@ -187,14 +175,9 @@ extension SMB2 {
|
||||
fileprivate let reserved: UInt8
|
||||
fileprivate let _shortName: FileShortNameType
|
||||
var shortName: String? {
|
||||
let s = encode(_shortName)
|
||||
var d = NSData(data: s) as Data
|
||||
d.count = Int(shortNameLen)
|
||||
return String(data: d, encoding: String.Encoding.utf16)
|
||||
}
|
||||
|
||||
init?(data: Data) {
|
||||
self = decode(data)
|
||||
var data = Data(value: _shortName)
|
||||
data.count = Int(shortNameLen)
|
||||
return String(data: data, encoding: .utf16)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,27 +197,18 @@ extension SMB2 {
|
||||
fileprivate let reserved: UInt8
|
||||
fileprivate let _shortName: FileShortNameType
|
||||
var shortName: String? {
|
||||
let s = encode(_shortName)
|
||||
var d = NSData(data: s) as Data
|
||||
d.count = Int(shortNameLen)
|
||||
return String(data: d, encoding: String.Encoding.utf16)
|
||||
var data = Data(value: _shortName)
|
||||
data.count = Int(shortNameLen)
|
||||
return String(data: data, encoding: .utf16)
|
||||
}
|
||||
fileprivate let reserved2: UInt16
|
||||
let fileId : FileId
|
||||
|
||||
init?(data: Data) {
|
||||
self = decode(data)
|
||||
}
|
||||
}
|
||||
|
||||
struct FileNamesInformationHeader: SMB2FilesInformationHeader {
|
||||
let nextEntryOffset: UInt32
|
||||
let fileIndex: UInt32
|
||||
let fileNameLength : UInt32
|
||||
|
||||
init?(data: Data) {
|
||||
self = decode(data)
|
||||
}
|
||||
}
|
||||
|
||||
typealias FileShortNameType = (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
|
||||
|
||||
@@ -11,7 +11,7 @@ import Foundation
|
||||
extension SMB2 {
|
||||
// MARK: SMB2 Negotiating
|
||||
|
||||
struct NegotiateRequest: SMBRequest {
|
||||
struct NegotiateRequest: SMBRequestBody {
|
||||
let header: NegotiateRequest.Header
|
||||
let dialects: [UInt16]
|
||||
let contexts: [(type: NegotiateContextType, data: Data)]
|
||||
@@ -31,25 +31,23 @@ extension SMB2 {
|
||||
func data() -> Data {
|
||||
var header = self.header
|
||||
header.dialectCount = UInt16(dialects.count)
|
||||
let dialectData = NSMutableData()
|
||||
var dialectData = Data()
|
||||
for dialect in dialects {
|
||||
var dialect = dialect
|
||||
dialectData.append(&dialect, length: 2)
|
||||
dialectData.append(UnsafeBufferPointer(start: &dialect, count: 2))
|
||||
}
|
||||
let pad = ((1024 - dialectData.length) % 8)
|
||||
dialectData.increaseLength(by: pad)
|
||||
header.contextOffset = UInt32(MemoryLayout<NegotiateRequest.Header>.size) + UInt32(dialectData.length)
|
||||
let pad = ((1024 - dialectData.count) % 8)
|
||||
dialectData.count += pad
|
||||
header.contextOffset = UInt32(MemoryLayout<NegotiateRequest.Header>.size) + UInt32(dialectData.count)
|
||||
header.contextCount = UInt16(contexts.count)
|
||||
|
||||
let contextData = NSMutableData()
|
||||
var contextData = Data()
|
||||
for context in contexts {
|
||||
var contextType = context.type.rawValue
|
||||
contextData.append(&contextType, length: 2)
|
||||
var dataLen = UInt16(context.data.count)
|
||||
contextData.increaseLength(by: 4)
|
||||
contextData.append(&dataLen, length: 2)
|
||||
contextData.append(Data(value: context.type.rawValue))
|
||||
contextData.count += 4
|
||||
contextData.append(Data(value: UInt16(context.data.count)))
|
||||
}
|
||||
var result = NSData(data: encode(&header)) as Data
|
||||
var result = Data(value: header)
|
||||
result.append(dialectData as Data)
|
||||
result.append(contextData as Data)
|
||||
return result
|
||||
@@ -91,23 +89,23 @@ extension SMB2 {
|
||||
}
|
||||
}
|
||||
|
||||
struct NegotiateResponse: SMBResponse {
|
||||
struct NegotiateResponse: SMBResponseBody {
|
||||
let header: NegotiateResponse.Header
|
||||
let buffer: Data?
|
||||
let contexts: [(type: NegotiateContextType, data: Data)]
|
||||
|
||||
init? (data: Data) {
|
||||
if data.count < 64 {
|
||||
guard data.count >= 64 else {
|
||||
return nil
|
||||
}
|
||||
self.header = decode(data)
|
||||
self.header = data.scanValue()!
|
||||
if Int(header.size) != 65 {
|
||||
return nil
|
||||
}
|
||||
let bufOffset = Int(self.header.bufferOffset) - MemoryLayout<SMB2.Header>.size
|
||||
let bufLen = Int(self.header.bufferLength)
|
||||
if bufOffset > 0 && bufLen > 0 && data.count >= bufOffset + bufLen {
|
||||
self.buffer = data.subdata(in: NSRange(location: bufOffset, length: bufLen))
|
||||
self.buffer = data.subdata(in: bufOffset..<(bufOffset + bufLen))
|
||||
} else {
|
||||
self.buffer = nil
|
||||
}
|
||||
@@ -176,7 +174,7 @@ extension SMB2 {
|
||||
|
||||
// MARK: SMB2 Session Setup
|
||||
|
||||
struct SessionSetupRequest: SMBRequest {
|
||||
struct SessionSetupRequest: SMBRequestBody {
|
||||
let header: SessionSetupRequest.Header
|
||||
let buffer: Data?
|
||||
|
||||
@@ -194,7 +192,7 @@ extension SMB2 {
|
||||
var header = self.header
|
||||
header.bufferOffset = UInt16(MemoryLayout<SMB2.Header>.size + MemoryLayout<SessionSetupRequest.Header>.size)
|
||||
header.bufferLength = UInt16(buffer?.count ?? 0)
|
||||
var result = NSData(data: encode(&header)) as Data
|
||||
var result = Data(value: header)
|
||||
if let buffer = self.buffer {
|
||||
result.append(buffer)
|
||||
}
|
||||
@@ -235,22 +233,22 @@ extension SMB2 {
|
||||
}
|
||||
}
|
||||
|
||||
struct SessionSetupResponse: SMBResponse {
|
||||
struct SessionSetupResponse: SMBResponseBody {
|
||||
let header: SessionSetupResponse.Header
|
||||
let buffer: Data?
|
||||
|
||||
init? (data: Data) {
|
||||
if data.count < 64 {
|
||||
guard data.count >= 64 else {
|
||||
return nil
|
||||
}
|
||||
self.header = decode(data)
|
||||
self.header = data.scanValue()!
|
||||
if Int(header.size) != 9 {
|
||||
return nil
|
||||
}
|
||||
let bufOffset = Int(self.header.bufferOffset) - MemoryLayout<SMB2.Header>.size
|
||||
let bufLen = Int(self.header.bufferLength)
|
||||
if bufOffset > 0 && bufLen > 0 && data.count >= bufOffset + bufLen {
|
||||
self.buffer = data.subdata(in: NSRange(location: bufOffset, length: bufLen))
|
||||
self.buffer = data.subdata(in: bufOffset..<(bufOffset + bufLen))
|
||||
} else {
|
||||
self.buffer = nil
|
||||
}
|
||||
@@ -289,7 +287,7 @@ extension SMB2 {
|
||||
|
||||
// MARK: SMB2 Log off
|
||||
|
||||
struct LogOff: SMBRequest, SMBResponse {
|
||||
struct LogOff: SMBRequestBody, SMBResponseBody {
|
||||
let size: UInt16
|
||||
let reserved: UInt16
|
||||
|
||||
@@ -297,19 +295,11 @@ extension SMB2 {
|
||||
self.size = 4
|
||||
self.reserved = 0
|
||||
}
|
||||
|
||||
init? (data: Data) {
|
||||
self = decode(data)
|
||||
}
|
||||
|
||||
func data() -> Data {
|
||||
return encode(self)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: SMB2 Echo
|
||||
|
||||
struct Echo: SMBRequest, SMBResponse {
|
||||
struct Echo: SMBRequestBody, SMBResponseBody {
|
||||
let size: UInt16
|
||||
let reserved: UInt16
|
||||
|
||||
@@ -317,13 +307,5 @@ extension SMB2 {
|
||||
self.size = 4
|
||||
self.reserved = 0
|
||||
}
|
||||
|
||||
init? (data: Data) {
|
||||
self = decode(data)
|
||||
}
|
||||
|
||||
func data() -> Data {
|
||||
return encode(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,14 +10,14 @@ import Foundation
|
||||
|
||||
extension SMB2 {
|
||||
// MARK: SMB2 Set Info
|
||||
struct SetInfoRequest: SMBRequest {
|
||||
struct SetInfoRequest: SMBRequestBody {
|
||||
let header: Header
|
||||
let buffer: Data?
|
||||
|
||||
|
||||
|
||||
func data() -> Data {
|
||||
return Data()
|
||||
var result = Data(value: header)
|
||||
result.append(buffer ?? Data())
|
||||
return result
|
||||
}
|
||||
|
||||
struct Header {
|
||||
@@ -32,15 +32,11 @@ extension SMB2 {
|
||||
}
|
||||
}
|
||||
|
||||
struct SetInfoResponse: SMBResponse {
|
||||
struct SetInfoResponse: SMBResponseBody {
|
||||
let size: UInt16
|
||||
|
||||
init() {
|
||||
self.size = 2
|
||||
}
|
||||
|
||||
init? (data: Data) {
|
||||
self = decode(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import Foundation
|
||||
extension SMB2 {
|
||||
// MARK: SMB2 Tree Connect
|
||||
|
||||
struct TreeConnectRequest: SMBRequest {
|
||||
struct TreeConnectRequest: SMBRequestBody {
|
||||
let header: TreeConnectRequest.Header
|
||||
let buffer: Data?
|
||||
var path: String {
|
||||
@@ -27,14 +27,14 @@ extension SMB2 {
|
||||
}
|
||||
self.header = header
|
||||
let path = "\\\\\(host)\\\(share)"
|
||||
self.buffer = path.data(using: String.Encoding.utf16)
|
||||
self.buffer = path.data(using: .utf16)
|
||||
}
|
||||
|
||||
func data() -> Data {
|
||||
var header = self.header
|
||||
header.pathOffset = UInt16(MemoryLayout<SMB2.Header>.size + MemoryLayout<TreeConnectRequest.Header>.size)
|
||||
header.pathLength = UInt16(buffer?.count ?? 0)
|
||||
var result = NSData(data: encode(&header)) as Data
|
||||
var result = Data(value: header)
|
||||
if let buffer = self.buffer {
|
||||
result.append(buffer)
|
||||
}
|
||||
@@ -66,7 +66,7 @@ extension SMB2 {
|
||||
}
|
||||
}
|
||||
|
||||
struct TreeConnectResponse: SMBResponse {
|
||||
struct TreeConnectResponse: SMBResponseBody {
|
||||
let size: UInt16 // = 16
|
||||
fileprivate let _type: UInt8
|
||||
var type: ShareType {
|
||||
@@ -77,13 +77,6 @@ extension SMB2 {
|
||||
let capabilities: TreeConnectResponse.Capabilities
|
||||
let maximalAccess: FileAccessMask
|
||||
|
||||
init? (data: Data) {
|
||||
if data.count != 16 {
|
||||
return nil
|
||||
}
|
||||
self = decode(data)
|
||||
}
|
||||
|
||||
enum ShareType: UInt8 {
|
||||
case UNKNOWN = 0x00
|
||||
case DISK = 0x01
|
||||
@@ -131,7 +124,7 @@ extension SMB2 {
|
||||
|
||||
// MARK: SMB2 Tree Disconnect
|
||||
|
||||
struct TreeDisconnect: SMBRequest, SMBResponse {
|
||||
struct TreeDisconnect: SMBRequestBody, SMBResponseBody {
|
||||
let size: UInt16
|
||||
let reserved: UInt16
|
||||
|
||||
@@ -139,13 +132,5 @@ extension SMB2 {
|
||||
self.size = 4
|
||||
self.reserved = 0
|
||||
}
|
||||
|
||||
init? (data: Data) {
|
||||
self = decode(data)
|
||||
}
|
||||
|
||||
func data() -> Data {
|
||||
return encode(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,24 +8,33 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
internal func encode<T>(_ value: inout T) -> Data {
|
||||
return withUnsafePointer(to: &value) { p in
|
||||
NSData(bytes: p, length: MemoryLayout.size(ofValue: value)) as Data
|
||||
extension Data {
|
||||
init<T>(value: T) {
|
||||
var value = value
|
||||
self = Data(buffer: UnsafeBufferPointer(start: &value, count: MemoryLayout.size(ofValue: value)))
|
||||
}
|
||||
}
|
||||
|
||||
internal func encode<T>(_ value: T) -> Data {
|
||||
var value = value
|
||||
return withUnsafePointer(to: &value) { p in
|
||||
NSData(bytes: p, length: MemoryLayout.size(ofValue: value)) as Data
|
||||
}
|
||||
}
|
||||
|
||||
internal func decode<T>(_ data: Data) -> T {
|
||||
let pointer = UnsafeMutablePointer<T>.allocate(capacity: MemoryLayout<T.Type>.size)
|
||||
(data as NSData).getBytes(pointer, length: MemoryLayout<T.Type>.size)
|
||||
|
||||
return pointer.move()
|
||||
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 {
|
||||
@@ -155,10 +164,5 @@ struct SMB2 {
|
||||
|
||||
// MARK: SMB2 Oplock Break
|
||||
|
||||
}
|
||||
|
||||
extension Data {
|
||||
func subdata(in range: NSRange) -> Data {
|
||||
return (self as NSData).subdata(with: range)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import Foundation
|
||||
|
||||
/// Error Types and Description
|
||||
|
||||
public enum NTStatus: UInt32, Error, CustomStringConvertible {
|
||||
enum NTStatus: UInt32, Error, CustomStringConvertible {
|
||||
case SUCCESS = 0x00000000
|
||||
case NOT_IMPLEMENTED = 0xC0000002
|
||||
case INVALID_DEVICE_REQUEST = 0xC0000010
|
||||
|
||||
+192
-151
@@ -7,76 +7,74 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
fileprivate func < <T : Comparable>(lhs: T?, rhs: T?) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case let (l?, r?):
|
||||
return l < r
|
||||
case (nil, _?):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func >= <T : Comparable>(lhs: T?, rhs: T?) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case let (l?, r?):
|
||||
return l >= r
|
||||
default:
|
||||
return !(lhs < rhs)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
// in case of using this class with unencrypted HTTP connection.
|
||||
/// 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 WebDAVFileProvider: NSObject, FileProviderBasic {
|
||||
open let type: String = "WebDAV"
|
||||
open class WebDAVFileProvider: NSObject, FileProviderBasicRemote {
|
||||
open static let type: String = "WebDAV"
|
||||
open let isPathRelative: Bool = true
|
||||
open let baseURL: URL?
|
||||
open var currentPath: String = ""
|
||||
open var dispatch_queue: DispatchQueue {
|
||||
public 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?
|
||||
public 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?
|
||||
fileprivate var session: URLSession {
|
||||
public var session: URLSession {
|
||||
if _session == nil {
|
||||
self.sessionDelegate = SessionDelegate(fileProvider: self, credential: credential)
|
||||
let queue = OperationQueue()
|
||||
//queue.underlyingQueue = dispatch_queue
|
||||
_session = URLSession(configuration: URLSessionConfiguration.default, delegate: sessionDelegate as URLSessionDownloadDelegate?, delegateQueue: queue)
|
||||
let config = URLSessionConfiguration.default
|
||||
config.urlCache = cache
|
||||
config.requestCachePolicy = .returnCacheDataElseLoad
|
||||
_session = URLSession(configuration: config, delegate: sessionDelegate as URLSessionDownloadDelegate?, delegateQueue: queue)
|
||||
}
|
||||
return _session!
|
||||
}
|
||||
|
||||
public init? (baseURL: URL, credential: URLCredential?) {
|
||||
public init? (baseURL: URL, credential: URLCredential?, cache: URLCache? = nil) {
|
||||
if !["http", "https"].contains(baseURL.uw_scheme.lowercased()) {
|
||||
return nil
|
||||
}
|
||||
self.baseURL = baseURL
|
||||
dispatch_queue = DispatchQueue(label: "FileProvider.\(type)", attributes: DispatchQueue.Attributes.concurrent)
|
||||
dispatch_queue = DispatchQueue(label: "FileProvider.\(WebDAVFileProvider.type)", attributes: DispatchQueue.Attributes.concurrent)
|
||||
//let url = baseURL.uw_absoluteString
|
||||
self.credential = credential
|
||||
self.cache = cache
|
||||
}
|
||||
|
||||
deinit {
|
||||
@@ -84,33 +82,31 @@ open class WebDAVFileProvider: NSObject, FileProviderBasic {
|
||||
}
|
||||
|
||||
open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {
|
||||
let opType = FileOperationType.fetch(path: path)
|
||||
let url = absoluteURL(path)
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "PROPFIND"
|
||||
request.setValue("1", forHTTPHeaderField: "Depth")
|
||||
request.setValue("text/xml; charset=\"utf-8\"", forHTTPHeaderField: "Content-Type")
|
||||
request.httpBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/></D:propfind>".data(using: String.Encoding.utf8)
|
||||
request.httpBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/></D:propfind>".data(using: .utf8)
|
||||
request.setValue(String(request.httpBody!.count), forHTTPHeaderField: "Content-Length")
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
runDataTask(with: request, operationHandle: RemoteOperationHandle(operationType: opType, tasks: []), completionHandler: { (data, response, error) in
|
||||
var responseError: FileProviderWebDavError?
|
||||
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = FileProviderWebDavError(code: rCode, url: url)
|
||||
}
|
||||
var fileObjects = [WebDavFileObject]()
|
||||
if let data = data {
|
||||
let xresponse = self.parseXMLResponse(data)
|
||||
var fileObjects = [WebDavFileObject]()
|
||||
for attr in xresponse {
|
||||
if attr.href.path == url.path {
|
||||
continue
|
||||
}
|
||||
fileObjects.append(self.mapToFileObject(attr))
|
||||
}
|
||||
completionHandler(fileObjects, responseError ?? error)
|
||||
return
|
||||
}
|
||||
completionHandler([], responseError ?? error)
|
||||
})
|
||||
task.resume()
|
||||
completionHandler(fileObjects, responseError ?? error)
|
||||
})
|
||||
}
|
||||
|
||||
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
|
||||
@@ -119,9 +115,9 @@ open class WebDAVFileProvider: NSObject, FileProviderBasic {
|
||||
request.httpMethod = "PROPFIND"
|
||||
request.setValue("1", forHTTPHeaderField: "Depth")
|
||||
request.setValue("text/xml; charset=\"utf-8\"", forHTTPHeaderField: "Content-Type")
|
||||
request.httpBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/></D:propfind>".data(using: String.Encoding.utf8)
|
||||
request.httpBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/></D:propfind>".data(using: .utf8)
|
||||
request.setValue(String(request.httpBody!.count), forHTTPHeaderField: "Content-Length")
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
runDataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var responseError: FileProviderWebDavError?
|
||||
if let code = (response as? HTTPURLResponse)?.statusCode, code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = FileProviderWebDavError(code: rCode, url: url)
|
||||
@@ -135,7 +131,6 @@ open class WebDAVFileProvider: NSObject, FileProviderBasic {
|
||||
}
|
||||
completionHandler(nil, responseError ?? error)
|
||||
})
|
||||
task.resume()
|
||||
}
|
||||
|
||||
open func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void)) {
|
||||
@@ -149,28 +144,32 @@ open class WebDAVFileProvider: NSObject, FileProviderBasic {
|
||||
request.httpMethod = "PROPFIND"
|
||||
request.setValue("0", forHTTPHeaderField: "Depth")
|
||||
request.setValue("text/xml; charset=\"utf-8\"", forHTTPHeaderField: "Content-Type")
|
||||
request.httpBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:prop><D:quota-available-bytes/><D:quota-used-bytes/></D:prop>\n</D:propfind>".data(using: String.Encoding.utf8)
|
||||
request.httpBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:prop><D:quota-available-bytes/><D:quota-used-bytes/></D:prop>\n</D:propfind>".data(using: .utf8)
|
||||
request.setValue(String(request.httpBody!.count), forHTTPHeaderField: "Content-Length")
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
runDataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var totalSize: Int64 = -1
|
||||
var usedSize: Int64 = 0
|
||||
if let data = data {
|
||||
let xresponse = self.parseXMLResponse(data)
|
||||
if let attr = xresponse.first {
|
||||
let totalSize = Int64(attr.prop["quota-available-bytes"] ?? "")
|
||||
let usedSize = Int64(attr.prop["quota-used-bytes"] ?? "")
|
||||
completionHandler(totalSize ?? -1, usedSize ?? 0)
|
||||
return
|
||||
totalSize = Int64(attr.prop["quota-available-bytes"] ?? "") ?? -1
|
||||
usedSize = Int64(attr.prop["quota-used-bytes"] ?? "") ?? 0
|
||||
}
|
||||
}
|
||||
completionHandler(-1, 0)
|
||||
})
|
||||
task.resume()
|
||||
completionHandler(totalSize, usedSize)
|
||||
})
|
||||
}
|
||||
|
||||
open weak var fileOperationDelegate: FileOperationDelegate?
|
||||
}
|
||||
|
||||
extension WebDAVFileProvider: FileProviderOperations {
|
||||
public func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) {
|
||||
@discardableResult
|
||||
public func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.create(path: (atPath as NSString).appendingPathComponent(folderName) + "/")
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
let url = absoluteURL((atPath as NSString).appendingPathComponent(folderName) + "/")
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "MKCOL"
|
||||
@@ -179,18 +178,21 @@ extension WebDAVFileProvider: FileProviderOperations {
|
||||
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = FileProviderWebDavError(code: rCode, url: url)
|
||||
}
|
||||
if let response = response as? HTTPURLResponse, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) , code != .ok {
|
||||
completionHandler?(FileProviderWebDavError(code: code, url: url))
|
||||
return
|
||||
}
|
||||
completionHandler?(responseError ?? error)
|
||||
self.delegateNotify(.create(path: (atPath as NSString).appendingPathComponent(folderName) + "/"), error: responseError ?? error)
|
||||
})
|
||||
self.delegateNotify(opType, error: responseError ?? error)
|
||||
})
|
||||
task.taskDescription = opType.json
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: opType, tasks: [task])
|
||||
}
|
||||
|
||||
public func create(file fileAttribs: FileObject, at path: String, contents data: Data?, completionHandler: SimpleCompletionHandler) {
|
||||
let url = absoluteURL(path)
|
||||
@discardableResult
|
||||
public func create(file fileName: String, at path: String, contents data: Data?, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.create(path: (path as NSString).appendingPathComponent(fileName))
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
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
|
||||
@@ -199,78 +201,90 @@ extension WebDAVFileProvider: FileProviderOperations {
|
||||
responseError = FileProviderWebDavError(code: rCode, url: url)
|
||||
}
|
||||
completionHandler?(responseError ?? error)
|
||||
self.delegateNotify(.create(path: (path as NSString).appendingPathComponent(fileAttribs.name)), error: responseError ?? error)
|
||||
})
|
||||
task.taskDescription = dictionaryToJSON(["type": "Create" as NSString, "source": (path as NSString).appendingPathComponent(fileAttribs.name) as NSString])
|
||||
self.delegateNotify(opType, error: responseError ?? error)
|
||||
})
|
||||
task.taskDescription = opType.json
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: opType, tasks: [task])
|
||||
}
|
||||
|
||||
public func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
|
||||
self.copyMoveItem(move: true, path: path, toPath: toPath, overwrite: overwrite, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
public func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
|
||||
self.copyMoveItem(move: false, path: path, toPath: toPath, overwrite: overwrite, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
fileprivate func copyMoveItem(move:Bool, path: String, toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) {
|
||||
let url = absoluteURL(path)
|
||||
var request = URLRequest(url: url)
|
||||
if move {
|
||||
request.httpMethod = "MOVE"
|
||||
} else {
|
||||
request.httpMethod = "COPY"
|
||||
@discardableResult
|
||||
public func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.move(source: path, destination: toPath)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
request.setValue(absoluteURL(path).uw_absoluteString, forHTTPHeaderField: "Destination")
|
||||
if !overwrite {
|
||||
return self.doOperation(operation: opType, overwrite: overwrite, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.copy(source: path, destination: toPath)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
return self.doOperation(operation: opType, overwrite: overwrite, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@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)
|
||||
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
|
||||
var responseError: FileProviderWebDavError?
|
||||
if let response = response as? HTTPURLResponse, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) {
|
||||
defer {
|
||||
let op = move ? FileOperation.move(source: path, destination: toPath) : .copy(source: path, destination: toPath)
|
||||
self.delegateNotify(op, error: error)
|
||||
if response.statusCode >= 300 {
|
||||
responseError = FileProviderWebDavError(code: code, url: sourceURL)
|
||||
}
|
||||
if code == .multiStatus, let data = data {
|
||||
let xresponses = self.parseXMLResponse(data)
|
||||
for xresponse in xresponses where xresponse.status >= 300 {
|
||||
completionHandler?(FileProviderWebDavError(code: code, url: url))
|
||||
for xresponse in xresponses where (xresponse.status ?? 0) >= 300 {
|
||||
completionHandler?(FileProviderWebDavError(code: code, url: sourceURL))
|
||||
}
|
||||
} else {
|
||||
completionHandler?(FileProviderWebDavError(code: code, url: url))
|
||||
}
|
||||
return
|
||||
}
|
||||
completionHandler?(error)
|
||||
})
|
||||
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])
|
||||
}
|
||||
|
||||
public func removeItem(path: String, completionHandler: SimpleCompletionHandler) {
|
||||
let url = absoluteURL(path)
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "DELETE"
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
if let response = response as? HTTPURLResponse, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) {
|
||||
defer {
|
||||
self.delegateNotify(.remove(path: path), error: error)
|
||||
}
|
||||
if code == .multiStatus, let data = data {
|
||||
let xresponses = self.parseXMLResponse(data)
|
||||
for xresponse in xresponses where xresponse.status >= 300 {
|
||||
completionHandler?(FileProviderWebDavError(code: code, url: url))
|
||||
}
|
||||
} else {
|
||||
completionHandler?(FileProviderWebDavError(code: code, url: url))
|
||||
}
|
||||
return
|
||||
}
|
||||
completionHandler?(error)
|
||||
})
|
||||
task.resume()
|
||||
}
|
||||
|
||||
public func copyItem(localFile: URL, to toPath: String, completionHandler: SimpleCompletionHandler) {
|
||||
@discardableResult
|
||||
public func copyItem(localFile: URL, to toPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.copy(source: localFile.absoluteString, destination: toPath)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
let url = absoluteURL(toPath)
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "PUT"
|
||||
@@ -280,13 +294,19 @@ extension WebDAVFileProvider: FileProviderOperations {
|
||||
responseError = FileProviderWebDavError(code: rCode, url: url)
|
||||
}
|
||||
completionHandler?(responseError ?? error)
|
||||
self.delegateNotify(.move(source: localFile.uw_absoluteString, destination: toPath), error: responseError ?? error)
|
||||
self.delegateNotify(opType, error: responseError ?? error)
|
||||
})
|
||||
task.taskDescription = dictionaryToJSON(["type": "Copy" as NSString, "source": localFile.uw_absoluteString as NSString, "dest": toPath as NSString])
|
||||
task.taskDescription = opType.json
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: opType, tasks: [task])
|
||||
}
|
||||
|
||||
public func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) {
|
||||
@discardableResult
|
||||
public func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.copy(source: path, destination: toLocalURL.absoluteString)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
let url = absoluteURL(path)
|
||||
let request = URLRequest(url: url)
|
||||
let task = session.downloadTask(with: request, completionHandler: { (sourceFileURL, response, error) in
|
||||
@@ -303,18 +323,23 @@ extension WebDAVFileProvider: FileProviderOperations {
|
||||
}
|
||||
}
|
||||
completionHandler?(responseError ?? error)
|
||||
self.delegateNotify(opType, error: responseError ?? error)
|
||||
})
|
||||
task.taskDescription = dictionaryToJSON(["type": "Copy" as NSString, "source": path as NSString, "dest": toLocalURL.uw_absoluteString as NSString])
|
||||
task.taskDescription = opType.json
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: opType, tasks: [task])
|
||||
}
|
||||
}
|
||||
|
||||
extension WebDAVFileProvider: FileProviderReadWrite {
|
||||
public func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) {
|
||||
self.contents(path: path, offset: 0, length: -1, completionHandler: completionHandler)
|
||||
@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)
|
||||
}
|
||||
|
||||
public func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) {
|
||||
@discardableResult
|
||||
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 = absoluteURL(path)
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "GET"
|
||||
@@ -323,19 +348,25 @@ extension WebDAVFileProvider: FileProviderReadWrite {
|
||||
} else if offset > 0 && length < 0 {
|
||||
request.setValue("bytes=\(offset)-", forHTTPHeaderField: "Range")
|
||||
}
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
let handle = RemoteOperationHandle(operationType: opType, tasks: [])
|
||||
runDataTask(with: request, operationHandle: handle, completionHandler: { (data, response, error) in
|
||||
var responseError: FileProviderWebDavError?
|
||||
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = FileProviderWebDavError(code: rCode, url: url)
|
||||
}
|
||||
completionHandler(data, responseError ?? error)
|
||||
})
|
||||
task.resume()
|
||||
})
|
||||
return handle
|
||||
}
|
||||
|
||||
public func writeContents(path: String, contents data: Data, atomically: Bool = false, completionHandler: SimpleCompletionHandler) {
|
||||
@discardableResult
|
||||
public func writeContents(path: String, contents data: Data, atomically: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.modify(path: path)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
// FIXME: lock destination before writing process
|
||||
let url = atomically ? absoluteURL(path).uw_URLByAppendingPathExtension("tmp") : absoluteURL(path)
|
||||
let url = atomically ? absoluteURL(path).appendingPathExtension("tmp") : absoluteURL(path)
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "PUT"
|
||||
let task = session.uploadTask(with: request, from: data, completionHandler: { (data, response, error) in
|
||||
@@ -344,7 +375,7 @@ extension WebDAVFileProvider: FileProviderReadWrite {
|
||||
responseError = FileProviderWebDavError(code: rCode, url: self.absoluteURL(path))
|
||||
}
|
||||
defer {
|
||||
self.delegateNotify(.modify(path: path), error: responseError ?? error)
|
||||
self.delegateNotify(opType, error: responseError ?? error)
|
||||
}
|
||||
if let error = error {
|
||||
completionHandler?(error)
|
||||
@@ -354,8 +385,9 @@ extension WebDAVFileProvider: FileProviderReadWrite {
|
||||
self.moveItem(path: (path as NSString).appendingPathExtension("tmp")!, to: path, completionHandler: completionHandler)
|
||||
}
|
||||
})
|
||||
task.taskDescription = dictionaryToJSON(["type": "Modify" as NSString, "source": path as NSString])
|
||||
task.taskDescription = opType.json
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: opType, tasks: [task])
|
||||
}
|
||||
|
||||
public func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
|
||||
@@ -364,9 +396,8 @@ extension WebDAVFileProvider: FileProviderReadWrite {
|
||||
request.httpMethod = "PROPFIND"
|
||||
//request.setValue("1", forHTTPHeaderField: "Depth")
|
||||
request.setValue("text/xml; charset=\"utf-8\"", forHTTPHeaderField: "Content-Type")
|
||||
request.httpBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/></D:propfind>".data(using: String.Encoding.utf8)
|
||||
request.setValue(String(request.httpBody!.count), forHTTPHeaderField: "Content-Length")
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
request.httpBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/></D:propfind>".data(using: .utf8)
|
||||
runDataTask(with: request, completionHandler: { (data, response, error) in
|
||||
// FIXME: paginating results
|
||||
var responseError: FileProviderWebDavError?
|
||||
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
@@ -388,8 +419,7 @@ extension WebDAVFileProvider: FileProviderReadWrite {
|
||||
return
|
||||
}
|
||||
completionHandler([], responseError ?? error)
|
||||
})
|
||||
task.resume()
|
||||
})
|
||||
}
|
||||
|
||||
fileprivate func registerNotifcation(path: String, eventHandler: (() -> Void)) {
|
||||
@@ -406,7 +436,17 @@ extension WebDAVFileProvider: FileProviderReadWrite {
|
||||
// TODO: implements methods for lock mechanism
|
||||
}
|
||||
|
||||
extension WebDAVFileProvider: FileProvider {}
|
||||
extension WebDAVFileProvider: FileProvider {
|
||||
open func copy(with zone: NSZone? = nil) -> Any {
|
||||
let copy = WebDAVFileProvider(baseURL: self.baseURL!, 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
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: WEBDAV XML response implementation
|
||||
|
||||
@@ -495,16 +535,17 @@ 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.fileType = fileObject.contentType == "httpd/unix-directory" ? .directory : .regular
|
||||
fileObject.entryTag = davResponse.prop["getetag"]
|
||||
return fileObject
|
||||
}
|
||||
|
||||
fileprivate func delegateNotify(_ operation: FileOperation, error: Error?) {
|
||||
fileprivate func delegateNotify(_ operation: FileOperationType, error: Error?) {
|
||||
DispatchQueue.main.async(execute: {
|
||||
if error == nil {
|
||||
self.delegate?.fileproviderSucceed(self, operation: operation)
|
||||
|
||||
Reference in New Issue
Block a user