Compare commits

...

43 Commits

Author SHA1 Message Date
Amir Abbas 8bad5944bc Updated pod spec version to 0.8.1 2016-12-07 02:36:49 +03:30
Amir Abbas 6ef2ab11c4 Fixed bug in Data(value:) initializer. Switch uuid init via this 2016-12-07 02:34:34 +03:30
Amir Abbas Mousavian 7f27d46c70 Merge pull request #16 from RndmTsk/master
Fixes #15, FileObject.fileType always returns nil.
2016-12-07 02:29:28 +03:30
Terry Latanville b1ec99b1b8 Fixes #15, FileObject.fileType always returns nil. 2016-12-06 17:18:41 -05:00
Amir Abbas Mousavian c4b8065cd3 Merge pull request #14 from skela/master
Update DropboxFileProvider.swift
2016-12-06 10:51:58 +03:30
Alek Slater 2d8454c711 Update DropboxFileProvider.swift
Using the same request dict creation method that can be found in the contents method, to avoid error when trying to use copyItem to copy a remote file to a local file destination.
2016-12-06 14:51:48 +08:00
Amir Abbas Mousavian 3b35c066de fixed get_temporary_link url 2016-12-03 20:40:04 +03:30
Amir Abbas Mousavian 7d8de9cefe FileObject init accessors fixed, refactoring 2016-12-03 20:26:27 +03:30
Amir Abbas Mousavian e84fef20ea [gardening] Refactoring WebDAV copy, move and remove operations 2016-12-03 15:32:54 +03:30
Amir Abbas Mousavian 4a9a3196a2 Dropbox get_temporary_link and save_url implementations 2016-12-03 15:12:11 +03:30
Amir Abbas Mousavian f4e6d277ae Updated source highlighting 2016-12-03 13:34:32 +03:30
Amir Abbas Mousavian 1328a8e9e2 New FIleObject implementation, highlighted Readme 2016-12-03 13:21:13 +03:30
Amir Abbas Mousavian 5bf620916f Fixes #12, createFile method path in WebDAV is not correct.
- removed NSURL cast for resource values, now using swift 3 methods
2016-12-02 01:12:08 +03:30
Amir Abbas Mousavian 4f56e20441 createFile definition improved to resolve #10 2016-12-01 12:12:53 +03:30
Amir Abbas Mousavian 9dda618b73 Revert "createFile definition improved"
This reverts commit da60c05188.
2016-12-01 12:10:14 +03:30
Amir Abbas Mousavian da60c05188 createFile definition improved 2016-12-01 11:54:03 +03:30
Amir Abbas Mousavian 4366855d54 Added NSCopying conformance, SMB headers gardening 2016-11-28 19:40:09 +03:30
Amir Abbas Mousavian fe05fd83fe Replaced encode/decode methods with Data extension 2016-11-26 00:11:46 +03:30
Amir Abbas Mousavian 826d207e6b OperationHandle optimizations
- code refactoring for RemoteOperationHandle usage and description
- bug fix: move operation in Dropbox provider did copy
- bug fix: dynamic inProgress result for RemoteOperationHandle
2016-11-24 22:54:00 +03:30
Amir Abbas Mousavian 66fc1e1284 Fixed unexpected behaviors in Remote providers:
- Calling delegate and completion methods
- refactoring codes
2016-11-23 23:42:36 +03:30
Amir Abbas Mousavian a1d489f5a5 Optimization in project settings for dynamic linking 2016-11-23 19:43:28 +03:30
Amir Abbas Mousavian ad2699cd19 Update Readme for OperationHandle 2016-11-11 16:37:32 +03:30
Amir Abbas Mousavian 97ae86cedb - fixed a bug in fileByUniqueName() function
- more neat code
2016-11-11 03:45:37 +03:30
Amir Abbas Mousavian 71b07cac1b fixed NotImplement() issue on build 2016-10-30 01:55:19 +03:30
Amir Abbas Mousavian a15f8f3809 Added OperationHandle, to cancel remote operations 2016-10-29 23:11:48 +03:30
Amir Abbas Mousavian dc1270d8d1 Fixing wrong spellings in readme 2016-10-26 01:04:33 +03:30
Amir Abbas Mousavian 68b1e23be3 Using Data instead of NSData in many places 2016-10-24 20:31:19 +03:30
Amir Abbas Mousavian 057c9fd940 Update podspec to 0.5.2 2016-09-30 14:08:25 +03:30
Amir Abbas Mousavian 6d63322779 FileProviderOperationDelegate methods will call for WebDav and Dropbox providers
- Added Carthage to Travis
2016-09-30 14:07:02 +03:30
Amir Abbas Mousavian fc6b46d17a Updated swift 3 semantics 2016-09-28 19:15:05 +03:30
Amir Abbas Mousavian bb9c08e309 Add .swift-version to pass cocoapods's validation, added static type var 2016-09-27 16:59:54 +03:30
Amir Abbas Mousavian 315ead606e AnyObject casts changed to proper one according to data type 2016-09-17 12:12:52 +04:30
Amir Abbas Mousavian d63e9c7f04 Swift 3 travis & podspec update 2016-09-16 05:20:34 +04:30
Amir Abbas Mousavian a5fe28c18d Swift 3 readme update 2016-09-15 07:58:36 +04:30
Amir Abbas Mousavian 542c18bab6 Convert to Swift 2 2016-09-15 02:48:04 +04:30
Amir Abbas Mousavian dca5dddbfd Code beat exceptions added 2016-08-20 19:19:05 +04:30
Amir Abbas Mousavian 6764c23f1b Update README.md and some refactors 2016-08-15 13:50:03 +04:30
Amir Abbas Mousavian 955a86372b Refactored NSURLSession delegate in Dropbox and WebDAV 2016-08-15 13:30:37 +04:30
Amir Abbas Mousavian 32a20fbc49 CodeCov added 2016-08-11 14:26:24 +04:30
Amir Abbas Mousavian 921ba19afa travis yml update 2016-08-11 14:16:04 +04:30
Amir Abbas Mousavian 8625b0464c Travis CI integration 2016-08-11 14:04:37 +04:30
Amir Abbas Mousavian 1bf9d1eadd Swift Package Manager Added 2016-08-11 13:49:28 +04:30
Amir Abbas Mousavian cbf774ba9e update README.md 2016-08-11 12:52:04 +04:30
43 changed files with 3786 additions and 3082 deletions
+1
View File
@@ -0,0 +1 @@
3.0
+27
View File
@@ -0,0 +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)
before_deploy:
- carthage build --no-skip-current
- carthage archive FileProvider
deploy:
file: FileProvider.framework.zip
skip_cleanup: true
on:
tags: true
+4 -4
View File
@@ -16,8 +16,8 @@ Pod::Spec.new do |s|
#
s.name = "FileProvider"
s.version = "0.4.1"
s.summary = "NSFileManager replacement for Local and Remote (WebDAV/Dropbox/SMB2) files on iOS and MacOS."
s.version = "0.8.1"
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"
+144 -57
View File
@@ -7,6 +7,34 @@
objects = {
/* Begin PBXBuildFile section */
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 */; };
7924B1991D89DAE000589DB7 /* Element.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B18E1D89DAE000589DB7 /* Element.swift */; };
7924B19A1D89DAE000589DB7 /* Element.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B18E1D89DAE000589DB7 /* Element.swift */; };
7924B19B1D89DAE000589DB7 /* Element.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B18E1D89DAE000589DB7 /* Element.swift */; };
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 */; };
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 */; };
7924B1A51D89DAE000589DB7 /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B1921D89DAE000589DB7 /* Parser.swift */; };
7924B1A61D89DAE000589DB7 /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B1921D89DAE000589DB7 /* Parser.swift */; };
7924B1A71D89DAE000589DB7 /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B1921D89DAE000589DB7 /* Parser.swift */; };
7924B1AD1D89F7D800589DB7 /* SMBClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396971D48C02300086753 /* SMBClient.swift */; };
7924B1AE1D89F7D900589DB7 /* SMBClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396971D48C02300086753 /* SMBClient.swift */; };
7924B1B01D89F7DE00589DB7 /* FPSStreamTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B1A81D89F79200589DB7 /* FPSStreamTask.swift */; };
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 */; };
@@ -16,12 +44,6 @@
794C220E1D591A4B00EC49B8 /* SMB2QueryTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C220D1D591A4B00EC49B8 /* SMB2QueryTypes.swift */; };
794C220F1D591A4B00EC49B8 /* SMB2QueryTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C220D1D591A4B00EC49B8 /* SMB2QueryTypes.swift */; };
794C22101D591A4B00EC49B8 /* SMB2QueryTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C220D1D591A4B00EC49B8 /* SMB2QueryTypes.swift */; };
794C22121D599FBE00EC49B8 /* FPSStreamTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C22111D599FBE00EC49B8 /* FPSStreamTask.swift */; };
794C22131D599FBE00EC49B8 /* FPSStreamTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C22111D599FBE00EC49B8 /* FPSStreamTask.swift */; };
794C22141D599FBE00EC49B8 /* FPSStreamTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C22111D599FBE00EC49B8 /* FPSStreamTask.swift */; };
799396A71D48C02300086753 /* AEXML.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396921D48C02300086753 /* AEXML.swift */; };
799396A81D48C02300086753 /* AEXML.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396921D48C02300086753 /* AEXML.swift */; };
799396A91D48C02300086753 /* AEXML.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396921D48C02300086753 /* AEXML.swift */; };
799396AA1D48C02300086753 /* DropboxFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396931D48C02300086753 /* DropboxFileProvider.swift */; };
799396AB1D48C02300086753 /* DropboxFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396931D48C02300086753 /* DropboxFileProvider.swift */; };
799396AC1D48C02300086753 /* DropboxFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396931D48C02300086753 /* DropboxFileProvider.swift */; };
@@ -31,9 +53,6 @@
799396B31D48C02300086753 /* LocalFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396961D48C02300086753 /* LocalFileProvider.swift */; };
799396B41D48C02300086753 /* LocalFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396961D48C02300086753 /* LocalFileProvider.swift */; };
799396B51D48C02300086753 /* LocalFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396961D48C02300086753 /* LocalFileProvider.swift */; };
799396B61D48C02300086753 /* SMBClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396971D48C02300086753 /* SMBClient.swift */; };
799396B71D48C02300086753 /* SMBClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396971D48C02300086753 /* SMBClient.swift */; };
799396B81D48C02300086753 /* SMBClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396971D48C02300086753 /* SMBClient.swift */; };
799396B91D48C02300086753 /* SMBFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396981D48C02300086753 /* SMBFileProvider.swift */; };
799396BA1D48C02300086753 /* SMBFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396981D48C02300086753 /* SMBFileProvider.swift */; };
799396BB1D48C02300086753 /* SMBFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396981D48C02300086753 /* SMBFileProvider.swift */; };
@@ -76,17 +95,24 @@
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
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>"; };
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>"; };
794C22111D599FBE00EC49B8 /* FPSStreamTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FPSStreamTask.swift; sourceTree = "<group>"; };
799396671D48B7F600086753 /* FileProvider.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FileProvider.framework; sourceTree = BUILT_PRODUCTS_DIR; };
799396751D48B80D00086753 /* FileProvider.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FileProvider.framework; sourceTree = BUILT_PRODUCTS_DIR; };
799396821D48B82700086753 /* FileProvider.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FileProvider.framework; sourceTree = BUILT_PRODUCTS_DIR; };
7993968B1D48B8C700086753 /* Info-iOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-iOS.plist"; sourceTree = "<group>"; };
7993968C1D48B8C700086753 /* Info-MacOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-MacOS.plist"; sourceTree = "<group>"; };
7993968D1D48B8C700086753 /* Info-tvOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-tvOS.plist"; sourceTree = "<group>"; };
799396921D48C02300086753 /* AEXML.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AEXML.swift; sourceTree = "<group>"; };
799396931D48C02300086753 /* DropboxFileProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropboxFileProvider.swift; sourceTree = "<group>"; };
799396941D48C02300086753 /* FileProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FileProvider.h; sourceTree = "<group>"; };
799396951D48C02300086753 /* FileProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileProvider.swift; sourceTree = "<group>"; };
@@ -112,6 +138,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
791950F51DE58A5400B4426E /* libxml2.tbd in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -132,12 +159,33 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
791950F31DE58A5300B4426E /* Frameworks */ = {
isa = PBXGroup;
children = (
791950F41DE58A5400B4426E /* libxml2.tbd */,
);
name = Frameworks;
sourceTree = "<group>";
};
7924B18B1D89DAE000589DB7 /* AEXML */ = {
isa = PBXGroup;
children = (
7924B18D1D89DAE000589DB7 /* Document.swift */,
7924B18E1D89DAE000589DB7 /* Element.swift */,
7924B18F1D89DAE000589DB7 /* Error.swift */,
7924B1911D89DAE000589DB7 /* Options.swift */,
7924B1921D89DAE000589DB7 /* Parser.swift */,
);
path = AEXML;
sourceTree = "<group>";
};
7993965B1D48B7BF00086753 = {
isa = PBXGroup;
children = (
799396911D48C02300086753 /* Sources */,
7993968A1D48B8C700086753 /* Pod */,
799396681D48B7F600086753 /* Products */,
791950F31DE58A5300B4426E /* Frameworks */,
);
sourceTree = "<group>";
};
@@ -164,16 +212,18 @@
799396911D48C02300086753 /* Sources */ = {
isa = PBXGroup;
children = (
7924B18B1D89DAE000589DB7 /* AEXML */,
799396991D48C02300086753 /* SMBTypes */,
799396921D48C02300086753 /* AEXML.swift */,
799396931D48C02300086753 /* DropboxFileProvider.swift */,
794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */,
799396941D48C02300086753 /* FileProvider.h */,
799396951D48C02300086753 /* FileProvider.swift */,
799396961D48C02300086753 /* LocalFileProvider.swift */,
792572401DF23BDA006A1526 /* LocalHelper.swift */,
7902C0851D61B56D00564440 /* RemoteSession.swift */,
7924B1A81D89F79200589DB7 /* FPSStreamTask.swift */,
799396971D48C02300086753 /* SMBClient.swift */,
799396981D48C02300086753 /* SMBFileProvider.swift */,
794C22111D599FBE00EC49B8 /* FPSStreamTask.swift */,
799396A61D48C02300086753 /* WebDAVFileProvider.swift */,
);
path = Sources;
@@ -286,10 +336,11 @@
7993965C1D48B7BF00086753 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0730;
LastUpgradeCheck = 0810;
TargetAttributes = {
799396661D48B7F600086753 = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 0800;
};
799396741D48B80D00086753 = {
CreatedOnToolsVersion = 7.3.1;
@@ -349,8 +400,12 @@
files = (
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 */,
7924B1B21D89FCDA00589DB7 /* FPSStreamTask.swift in Sources */,
799396C51D48C02300086753 /* SMB2FileOperation.swift in Sources */,
799396BF1D48C02300086753 /* SMB2DataTypes.swift in Sources */,
799396B91D48C02300086753 /* SMBFileProvider.swift in Sources */,
@@ -359,16 +414,18 @@
799396BC1D48C02300086753 /* CIFSTypes.swift in Sources */,
794C220A1D5893F800EC49B8 /* SMB2Notification.swift in Sources */,
799396D11D48C02300086753 /* SMB2SetInfo.swift in Sources */,
799396A71D48C02300086753 /* AEXML.swift in Sources */,
7924B1961D89DAE000589DB7 /* Document.swift in Sources */,
7902C0861D61B56D00564440 /* RemoteSession.swift in Sources */,
799396CE1D48C02300086753 /* SMB2Session.swift in Sources */,
799396E01D48C02300086753 /* WebDAVFileProvider.swift in Sources */,
799396DA1D48C02300086753 /* SMBErrorType.swift in Sources */,
799396C21D48C02300086753 /* SMB2FileHandle.swift in Sources */,
799396CB1D48C02300086753 /* SMB2Query.swift in Sources */,
794C22121D599FBE00EC49B8 /* FPSStreamTask.swift in Sources */,
799396AA1D48C02300086753 /* DropboxFileProvider.swift in Sources */,
7924B1B31D89FD6400589DB7 /* SMBClient.swift in Sources */,
7924B1A51D89DAE000589DB7 /* Parser.swift in Sources */,
799396B01D48C02300086753 /* FileProvider.swift in Sources */,
799396B61D48C02300086753 /* SMBClient.swift in Sources */,
7924B19C1D89DAE000589DB7 /* Error.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -378,6 +435,10 @@
files = (
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 */,
799396D81D48C02300086753 /* SMB2Types.swift in Sources */,
799396C61D48C02300086753 /* SMB2FileOperation.swift in Sources */,
@@ -388,16 +449,18 @@
799396BD1D48C02300086753 /* CIFSTypes.swift in Sources */,
794C220B1D5893F800EC49B8 /* SMB2Notification.swift in Sources */,
799396D21D48C02300086753 /* SMB2SetInfo.swift in Sources */,
799396A81D48C02300086753 /* AEXML.swift in Sources */,
7924B1971D89DAE000589DB7 /* Document.swift in Sources */,
7902C0871D61B67100564440 /* RemoteSession.swift in Sources */,
799396CF1D48C02300086753 /* SMB2Session.swift in Sources */,
799396E11D48C02300086753 /* WebDAVFileProvider.swift in Sources */,
799396DB1D48C02300086753 /* SMBErrorType.swift in Sources */,
799396C31D48C02300086753 /* SMB2FileHandle.swift in Sources */,
799396CC1D48C02300086753 /* SMB2Query.swift in Sources */,
794C22131D599FBE00EC49B8 /* FPSStreamTask.swift in Sources */,
7924B1AD1D89F7D800589DB7 /* SMBClient.swift in Sources */,
799396AB1D48C02300086753 /* DropboxFileProvider.swift in Sources */,
7924B1A61D89DAE000589DB7 /* Parser.swift in Sources */,
799396B11D48C02300086753 /* FileProvider.swift in Sources */,
799396B71D48C02300086753 /* SMBClient.swift in Sources */,
7924B19D1D89DAE000589DB7 /* Error.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -407,6 +470,10 @@
files = (
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 */,
799396D91D48C02300086753 /* SMB2Types.swift in Sources */,
799396C71D48C02300086753 /* SMB2FileOperation.swift in Sources */,
@@ -417,16 +484,18 @@
799396BE1D48C02300086753 /* CIFSTypes.swift in Sources */,
794C220C1D5893F800EC49B8 /* SMB2Notification.swift in Sources */,
799396D31D48C02300086753 /* SMB2SetInfo.swift in Sources */,
799396A91D48C02300086753 /* AEXML.swift in Sources */,
7924B1981D89DAE000589DB7 /* Document.swift in Sources */,
7902C0881D61B67100564440 /* RemoteSession.swift in Sources */,
799396D01D48C02300086753 /* SMB2Session.swift in Sources */,
799396E21D48C02300086753 /* WebDAVFileProvider.swift in Sources */,
799396DC1D48C02300086753 /* SMBErrorType.swift in Sources */,
799396C41D48C02300086753 /* SMB2FileHandle.swift in Sources */,
799396CD1D48C02300086753 /* SMB2Query.swift in Sources */,
794C22141D599FBE00EC49B8 /* FPSStreamTask.swift in Sources */,
7924B1AE1D89F7D900589DB7 /* SMBClient.swift in Sources */,
799396AC1D48C02300086753 /* DropboxFileProvider.swift in Sources */,
7924B1A71D89DAE000589DB7 /* Parser.swift in Sources */,
799396B21D48C02300086753 /* FileProvider.swift in Sources */,
799396B81D48C02300086753 /* SMBClient.swift in Sources */,
7924B19E1D89DAE000589DB7 /* Error.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -436,12 +505,58 @@
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;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
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;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
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;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
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;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 3.0.1;
};
name = Release;
};
@@ -450,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;
@@ -464,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*]" = "iPhone Developer";
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)",
@@ -493,7 +602,6 @@
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;
@@ -510,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;
@@ -524,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*]" = "iPhone Developer";
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;
@@ -577,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;
@@ -589,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)",
@@ -608,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;
@@ -638,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;
@@ -650,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;
@@ -691,19 +784,16 @@
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
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)",
@@ -718,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 = "";
};
@@ -750,16 +839,14 @@
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
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;
@@ -776,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 = "0730"
LastUpgradeVersion = "0810"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0730"
LastUpgradeVersion = "0810"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0730"
LastUpgradeVersion = "0810"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
+5
View File
@@ -0,0 +1,5 @@
import PackageDescription
let package = Package(
name: "FileProvider"
)
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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>
+186 -127
View File
@@ -1,19 +1,22 @@
# 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]
[![Platform](https://img.shields.io/badge/Platform-iOS%2C%20OSX-lightgray.svg)]()
[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/FileProvider.svg)](https://img.shields.io/cocoapods/v/FileProvider.svg)
[![Platform](https://img.shields.io/badge/Platform-iOS%2C%20macOS%2C%20tvOS-lightgray.svg)]()
[![Build Status][travis-image]][travis-url]
[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/FileProvider.svg)](https://cocoapods.org/pods/FileProvider)
[![codebeat badge][codebeat-image]][codebeat-url]
<!---
[![Build Status][travis-image]][travis-url]
[![codecov](https://codecov.io/gh/amosavian/FileProvider/branch/master/graph/badge.svg)](https://codecov.io/gh/amosavian/FileProvider)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](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.
@@ -21,18 +24,20 @@ Local and WebDAV providers are fully tested and can be used in production enviro
## Features
- [x] **LocalFileProvider** a wrapper around `NSFileManager` with some additions like searching and reading a portion of file.
- [x] **WebDAVFileProvider** WebDAV protocol is defacto file transmission protocol standard, replaced FTP.
- [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
- [ ] **FTPFileProvider** while depericated in 1990s, it's still in use on some Web hosts.
- [ ] **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**
## Requirements
- **Swift 2.2 or 2.3**
- **Swift 3**
- iOS 8.0 , OSX 10.10
- XCode 7.3
- XCode 8.0
Legacy version is available in swift-2 branch
## Installation
@@ -42,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
@@ -68,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 = NSURL(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 = NSURLCredential(user: "user", password: "pass", persistence: NSURLCredentialPersistence.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)
@@ -89,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
}
```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):
NSLog("\(source) copied to \(dest).")
case .Remove(path: let path):
NSLog("\(path) has been deleted.")
default:
break
}
}
func fileproviderFailed(fileProvider: FileProviderOperations, operation: FileOperation) {
switch operation {
case .Copy(source: let source, destination: let dest):
NSLog("copy of \(source) failed.")
case .Remove(path: let path):
NSLog("\(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):
NSLog("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.
@@ -142,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.**
@@ -154,109 +183,143 @@ There is a `FileObject` class which holds file attributes like size and creation
For a single file:
documentsProvider.attributesOfItemAtPath(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.contentsOfDirectoryAtPath(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.createFolder(folderName: "new folder", atPath: "/", 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!".dataUsingEncoding(NSUTF8StringEncoding)
let file = FileObject(name: "old.txt", createdDate: NSDate(), modifiedDate: NSDate(), isHidden: false, isReadOnly: true)
documentsProvider.createFile(fileAttribs: file, atPath: "/", 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.copyItemAtPath(path: "new folder/old.txt", toPath: "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.moveItemAtPath(path: "new folder/old.txt", toPath: "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.removeItemAtPath(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.contentsAtPath(path: "old.txt", completionHandler: {
(contents: NSData?, error: ErrorType?) -> Void
if let contents = contents {
print(String(data: contents, encoding: NSUTF8StringEncoding)) // "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.contentsAtPath(path: "old.txt", offset: 2, length: 5, completionHandler: {
(contents: NSData?, error: ErrorType?) -> Void
if let contents = contents {
print(String(data: contents, encoding: NSUTF8StringEncoding)) // "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!".dataUsingEncoding(NSUTF8StringEncoding)
documentsProvider.writeContentsAtPath(path: "old.txt", contents data: 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(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(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.
@@ -277,17 +340,13 @@ Amir-Abbas Mousavian [@amosavian](https://twitter.com/amosavian)
Distributed under the MIT license. See `LICENSE` for more information.
[https://github.com/yourname/github-link](https://github.com/dbader/)
[https://github.com/amosavian/](https://github.com/amosavian/)
[swift-image]:https://img.shields.io/badge/swift-2.2%2C%202.3-green.svg
[swift-image]:https://img.shields.io/badge/swift-3.0-orange.svg
[swift-url]: https://swift.org/
[license-image]: https://img.shields.io/badge/License-MIT-blue.svg
[license-url]: LICENSE
[codebeat-image]: https://codebeat.co/badges/7b359f48-78eb-4647-ab22-56262a827517
[codebeat-url]: https://codebeat.co/projects/github-com-amosavian-fileprovider
<!---
[travis-image]: https://img.shields.io/travis/dbader/node-datadog-metrics/master.svg?style=flat-square
[travis-url]: https://travis-ci.org/dbader/node-datadog-metrics
--->
[travis-image]: https://img.shields.io/travis/amosavian/FileProvider/master.svg
[travis-url]: https://travis-ci.org/amosavian/FileProvider
-492
View File
@@ -1,492 +0,0 @@
//
// AEXML.swift
//
// Copyright (c) 2014 Marko Tadić <tadija@me.com> http://tadija.net
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
// MARK: - AEXMLElement
/**
This is base class for holding XML structure.
You can access its structure by using subscript like this: `element["foo"]["bar"]` which would
return `<bar></bar>` element from `<element><foo><bar></bar></foo></element>` XML as an `AEXMLElement` object.
*/
public class AEXMLElement {
/// A type representing an error value that can be inside `error` property.
public enum Error: ErrorType {
case ElementNotFound
case RootElementMissing
}
private struct Defaults {
static let name = String()
static let attributes = [String : String]()
}
// MARK: Properties
/// Every `AEXMLElement` should have its parent element instead of `AEXMLDocument` which parent is `nil`.
public private(set) weak var parent: AEXMLElement?
/// Child XML elements.
public private(set) var children: [AEXMLElement] = [AEXMLElement]()
/// XML Element name (defaults to empty string).
public var name: String
/// XML Element value.
public var value: String?
/// XML Element attributes (defaults to empty dictionary).
public var attributes: [String : String]
/// Error value (`nil` if there is no error).
public var error: Error?
/// String representation of `value` property (if `value` is `nil` this is empty String).
public var stringValue: String { return value ?? String() }
/// Boolean representation of `value` property (if `value` is "true" or 1 this is `True`, otherwise `False`).
public var boolValue: Bool { return stringValue.lowercaseString == "true" || Int(stringValue) == 1 ? true : false }
/// Integer representation of `value` property (this is **0** if `value` can't be represented as Integer).
public var intValue: Int { return Int(stringValue) ?? 0 }
/// Double representation of `value` property (this is **0.00** if `value` can't be represented as Double).
public var doubleValue: Double { return (stringValue as NSString).doubleValue }
// MARK: Lifecycle
/**
Designated initializer - all parameters are optional.
- parameter name: XML element name.
- parameter value: XML element value
- parameter attributes: XML element attributes
- returns: An initialized `AEXMLElement` object.
*/
public init(_ name: String? = nil, value: String? = nil, attributes: [String : String]? = nil) {
self.name = name ?? Defaults.name
self.value = value
self.attributes = attributes ?? Defaults.attributes
}
// MARK: XML Read
/// The first element with given name **(Empty element with error if not exists)**.
public subscript(key: String) -> AEXMLElement {
guard let
first = children.filter({ $0.name == key }).first
else {
let errorElement = AEXMLElement(key)
errorElement.error = Error.ElementNotFound
return errorElement
}
return first
}
/// Returns all of the elements with equal name as `self` **(nil if not exists)**.
public var all: [AEXMLElement]? { return parent?.children.filter { $0.name == self.name } }
/// Returns the first element with equal name as `self` **(nil if not exists)**.
public var first: AEXMLElement? { return all?.first }
/// Returns the last element with equal name as `self` **(nil if not exists)**.
public var last: AEXMLElement? { return all?.last }
/// Returns number of all elements with equal name as `self`.
public var count: Int { return all?.count ?? 0 }
private func allWithCondition(fulfillCondition: (element: AEXMLElement) -> Bool) -> [AEXMLElement]? {
var found = [AEXMLElement]()
if let elements = all {
for element in elements {
if fulfillCondition(element: element) {
found.append(element)
}
}
return found.count > 0 ? found : nil
} else {
return nil
}
}
/**
Returns all elements with given value.
- parameter value: XML element value.
- returns: Optional Array of found XML elements.
*/
public func allWithValue(value: String) -> [AEXMLElement]? {
let found = allWithCondition { (element) -> Bool in
return element.value == value
}
return found
}
/**
Returns all elements with given attributes.
- parameter attributes: Dictionary of Keys and Values of attributes.
- returns: Optional Array of found XML elements.
*/
public func allWithAttributes(attributes: [String : String]) -> [AEXMLElement]? {
let found = allWithCondition { (element) -> Bool in
var countAttributes = 0
for (key, value) in attributes {
if element.attributes[key] == value {
countAttributes += 1
}
}
return countAttributes == attributes.count
}
return found
}
// MARK: XML Write
/**
Adds child XML element to `self`.
- parameter child: Child XML element to add.
- returns: Child XML element with `self` as `parent`.
*/
public func addChild(child: AEXMLElement) -> AEXMLElement {
child.parent = self
children.append(child)
return child
}
/**
Adds child XML element to `self`.
- parameter name: Child XML element name.
- parameter value: Child XML element value.
- parameter attributes: Child XML element attributes.
- returns: Child XML element with `self` as `parent`.
*/
public func addChild(name name: String, value: String? = nil, attributes: [String : String]? = nil) -> AEXMLElement {
let child = AEXMLElement(name, value: value, attributes: attributes)
return addChild(child)
}
/// Removes `self` from `parent` XML element.
public func removeFromParent() {
parent?.removeChild(self)
}
private func removeChild(child: AEXMLElement) {
if let childIndex = children.indexOf({ $0 === child }) {
children.removeAtIndex(childIndex)
}
}
private var parentsCount: Int {
var count = 0
var element = self
while let parent = element.parent {
count += 1
element = parent
}
return count
}
private func indentation(depth: Int) -> String {
var count = depth
var indent = String()
while count > 0 {
indent += "\t"
count -= 1
}
return indent
}
/// Complete hierarchy of `self` and `children` in **XML** escaped and formatted String
public var xmlString: String {
var xml = String()
// open element
xml += indentation(parentsCount - 1)
xml += "<\(name)"
if attributes.count > 0 {
// insert attributes
for (key, value) in attributes {
xml += " \(key)=\"\(value.xmlEscaped)\""
}
}
if value == nil && children.count == 0 {
// close element
xml += " />"
} else {
if children.count > 0 {
// add children
xml += ">\n"
for child in children {
xml += "\(child.xmlString)\n"
}
// add indentation
xml += indentation(parentsCount - 1)
xml += "</\(name)>"
} else {
// insert string value and close element
xml += ">\(stringValue.xmlEscaped)</\(name)>"
}
}
return xml
}
/// Same as `xmlString` but without `\n` and `\t` characters
public var xmlStringCompact: String {
let chars = NSCharacterSet(charactersInString: "\n\t")
return xmlString.componentsSeparatedByCharactersInSet(chars).joinWithSeparator("")
}
}
public extension String {
/// String representation of self with XML special characters escaped.
public var xmlEscaped: String {
// we need to make sure "&" is escaped first. Not doing this may break escaping the other characters
var escaped = stringByReplacingOccurrencesOfString("&", withString: "&amp;", options: .LiteralSearch)
// replace the other four special characters
let escapeChars = ["<" : "&lt;", ">" : "&gt;", "'" : "&apos;", "\"" : "&quot;"]
for (char, echar) in escapeChars {
escaped = escaped.stringByReplacingOccurrencesOfString(char, withString: echar, options: .LiteralSearch)
}
return escaped
}
}
// MARK: - AEXMLDocument
/**
This class is inherited from `AEXMLElement` and has a few addons to represent **XML Document**.
XML Parsing is also done with this object.
*/
public class AEXMLDocument: AEXMLElement {
private struct Defaults {
static let version = 1.0
static let encoding = "utf-8"
static let standalone = "no"
static let documentName = "AEXMLDocument"
}
/// Default options used by NSXMLParser
public struct NSXMLParserOptions {
public var shouldProcessNamespaces = false
public var shouldReportNamespacePrefixes = false
public var shouldResolveExternalEntities = false
public init() {}
}
// MARK: Properties
/// This is only used for XML Document header (default value is 1.0).
public let version: Double
/// This is only used for XML Document header (default value is "utf-8").
public let encoding: String
/// This is only used for XML Document header (default value is "no").
public let standalone: String
/// Options for NSXMLParser (default values are `false`)
public let xmlParserOptions: NSXMLParserOptions
/// Root (the first child element) element of XML Document **(Empty element with error if not exists)**.
public var root: AEXMLElement {
guard let rootElement = children.first else {
let errorElement = AEXMLElement()
errorElement.error = Error.RootElementMissing
return errorElement
}
return rootElement
}
// MARK: Lifecycle
/**
Designated initializer - Creates and returns XML Document object.
- parameter version: Version value for XML Document header (defaults to 1.0).
- parameter encoding: Encoding value for XML Document header (defaults to "utf-8").
- parameter standalone: Standalone value for XML Document header (defaults to "no").
- parameter root: Root XML element for XML Document (defaults to `nil`).
- parameter xmlParserOptions: Options for NSXMLParser (defaults to `false` for all).
- returns: An initialized XML Document object.
*/
public init(version: Double = Defaults.version,
encoding: String = Defaults.encoding,
standalone: String = Defaults.standalone,
root: AEXMLElement? = nil,
xmlParserOptions: NSXMLParserOptions = NSXMLParserOptions())
{
// set document properties
self.version = version
self.encoding = encoding
self.standalone = standalone
self.xmlParserOptions = xmlParserOptions
// init super with default name
super.init(Defaults.documentName)
// document has no parent element
parent = nil
// add root element to document (if any)
if let rootElement = root {
addChild(rootElement)
}
}
/**
Convenience initializer - used for parsing XML data (by calling `loadXMLData:` internally).
- parameter version: Version value for XML Document header (defaults to 1.0).
- parameter encoding: Encoding value for XML Document header (defaults to "utf-8").
- parameter standalone: Standalone value for XML Document header (defaults to "no").
- parameter xmlData: XML data to parse.
- parameter xmlParserOptions: Options for NSXMLParser (defaults to `false` for all).
- returns: An initialized XML Document object containing parsed data. Throws error if data could not be parsed.
*/
public convenience init(version: Double = Defaults.version,
encoding: String = Defaults.encoding,
standalone: String = Defaults.standalone,
xmlData: NSData,
xmlParserOptions: NSXMLParserOptions = NSXMLParserOptions()) throws
{
self.init(version: version, encoding: encoding, standalone: standalone, xmlParserOptions: xmlParserOptions)
try loadXMLData(xmlData)
}
// MARK: Read XML
/**
Creates instance of `AEXMLParser` (private class which is simple wrapper around `NSXMLParser`)
and starts parsing the given XML data. Throws error if data could not be parsed.
- parameter data: XML which should be parsed.
*/
public func loadXMLData(data: NSData) throws {
children.removeAll(keepCapacity: false)
let xmlParser = AEXMLParser(xmlDocument: self, xmlData: data)
try xmlParser.parse()
}
// MARK: Override
/// Override of `xmlString` property of `AEXMLElement` - it just inserts XML Document header at the beginning.
public override var xmlString: String {
var xml = "<?xml version=\"\(version)\" encoding=\"\(encoding)\" standalone=\"\(standalone)\"?>\n"
for child in children {
xml += child.xmlString
}
return xml
}
}
// MARK: - AEXMLParser
private class AEXMLParser: NSObject, NSXMLParserDelegate {
// MARK: Properties
let xmlDocument: AEXMLDocument
let xmlData: NSData
var currentParent: AEXMLElement?
var currentElement: AEXMLElement?
var currentValue = String()
var parseError: NSError?
// MARK: Lifecycle
init(xmlDocument: AEXMLDocument, xmlData: NSData) {
self.xmlDocument = xmlDocument
self.xmlData = xmlData
currentParent = xmlDocument
super.init()
}
// MARK: XML Parse
func parse() throws {
let parser = NSXMLParser(data: xmlData)
parser.delegate = self
parser.shouldProcessNamespaces = xmlDocument.xmlParserOptions.shouldProcessNamespaces
parser.shouldReportNamespacePrefixes = xmlDocument.xmlParserOptions.shouldReportNamespacePrefixes
parser.shouldResolveExternalEntities = xmlDocument.xmlParserOptions.shouldResolveExternalEntities
let success = parser.parse()
if !success {
throw parseError ?? NSError(domain: "net.tadija.AEXML", code: 1, userInfo: nil)
}
}
// MARK: NSXMLParserDelegate
@objc func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
currentValue = String()
currentElement = currentParent?.addChild(name: elementName, attributes: attributeDict)
currentParent = currentElement
}
@objc func parser(parser: NSXMLParser, foundCharacters string: String) {
currentValue += string
let newValue = currentValue.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
currentElement?.value = newValue == String() ? nil : newValue
}
@objc func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
currentParent = currentParent?.parent
currentElement = nil
}
@objc func parser(parser: NSXMLParser, parseErrorOccurred parseError: NSError) {
self.parseError = parseError
}
}
+29
View File
@@ -0,0 +1,29 @@
//
// AEXML.h
//
// Copyright (c) 2014 Marko Tadić <tadija@me.com> http://tadija.net
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#import <Foundation/Foundation.h>
FOUNDATION_EXPORT double AEXMLVersionNumber;
FOUNDATION_EXPORT const unsigned char AEXMLVersionString[];
+128
View File
@@ -0,0 +1,128 @@
//
// Document.swift
//
// Copyright (c) 2014-2016 Marko Tadić <tadija@me.com> http://tadija.net
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
This class is inherited from `AEXMLElement` and has a few addons to represent **XML Document**.
XML Parsing is also done with this object.
*/
open class AEXMLDocument: AEXMLElement {
// MARK: - Properties
/// Root (the first child element) element of XML Document **(Empty element with error if not exists)**.
open var root: AEXMLElement {
guard let rootElement = children.first else {
let errorElement = AEXMLElement(name: "Error")
errorElement.error = AEXMLError.rootElementMissing
return errorElement
}
return rootElement
}
open let options: AEXMLOptions
// MARK: - Lifecycle
/**
Designated initializer - Creates and returns new XML Document object.
- parameter root: Root XML element for XML Document (defaults to `nil`).
- parameter options: Options for XML Document header and parser settings (defaults to `AEXMLOptions()`).
- returns: Initialized XML Document object.
*/
public init(root: AEXMLElement? = nil, options: AEXMLOptions = AEXMLOptions()) {
self.options = options
let documentName = String(describing: AEXMLDocument.self)
super.init(name: documentName)
// document has no parent element
parent = nil
// add root element to document (if any)
if let rootElement = root {
_ = addChild(rootElement)
}
}
/**
Convenience initializer - used for parsing XML data (by calling `loadXMLData:` internally).
- parameter xmlData: XML data to parse.
- parameter options: Options for XML Document header and parser settings (defaults to `AEXMLOptions()`).
- returns: Initialized XML Document object containing parsed data. Throws error if data could not be parsed.
*/
public convenience init(xml: Data, options: AEXMLOptions = AEXMLOptions()) throws {
self.init(options: options)
try loadXML(xml)
}
/**
Convenience initializer - used for parsing XML string (by calling `init(xmlData:options:)` internally).
- parameter xmlString: XML string to parse.
- parameter encoding: String encoding for creating `Data` from `xmlString` (defaults to `String.Encoding.utf8`)
- parameter options: Options for XML Document header and parser settings (defaults to `AEXMLOptions()`).
- returns: Initialized XML Document object containing parsed data. Throws error if data could not be parsed.
*/
public convenience init(xml: String,
encoding: String.Encoding = String.Encoding.utf8,
options: AEXMLOptions = AEXMLOptions()) throws
{
guard let data = xml.data(using: encoding) else { throw AEXMLError.parsingFailed }
try self.init(xml: data, options: options)
}
// MARK: - Parse XML
/**
Creates instance of `AEXMLParser` (private class which is simple wrapper around `XMLParser`)
and starts parsing the given XML data. Throws error if data could not be parsed.
- parameter data: XML which should be parsed.
*/
open func loadXML(_ data: Data) throws {
children.removeAll(keepingCapacity: false)
let xmlParser = AEXMLParser(document: self, data: data)
try xmlParser.parse()
}
// MARK: - Override
/// Override of `xml` property of `AEXMLElement` - it just inserts XML Document header at the beginning.
open override var xml: String {
var xml = "\(options.documentHeader.xmlString)\n"
for child in children {
xml += child.xml
}
return xml
}
}
+285
View File
@@ -0,0 +1,285 @@
//
// Element.swift
//
// Copyright (c) 2014-2016 Marko Tadić <tadija@me.com> http://tadija.net
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
This is base class for holding XML structure.
You can access its structure by using subscript like this: `element["foo"]["bar"]` which would
return `<bar></bar>` element from `<element><foo><bar></bar></foo></element>` XML as an `AEXMLElement` object.
*/
open class AEXMLElement {
// MARK: - Properties
/// Every `AEXMLElement` should have its parent element instead of `AEXMLDocument` which parent is `nil`.
open internal(set) weak var parent: AEXMLElement?
/// Child XML elements.
open internal(set) var children = [AEXMLElement]()
/// XML Element name.
open var name: String
/// XML Element value.
open var value: String?
/// XML Element attributes.
open var attributes: [String : String]
/// Error value (`nil` if there is no error).
open var error: AEXMLError?
/// String representation of `value` property (if `value` is `nil` this is empty String).
open var string: String { return value ?? String() }
/// Boolean representation of `value` property (if `value` is "true" or 1 this is `True`, otherwise `False`).
open var bool: Bool { return string.lowercased() == "true" || Int(string) == 1 ? true : false }
/// Integer representation of `value` property (this is **0** if `value` can't be represented as Integer).
open var int: Int { return Int(string) ?? 0 }
/// Double representation of `value` property (this is **0.00** if `value` can't be represented as Double).
open var double: Double { return Double(string) ?? 0.00 }
// MARK: - Lifecycle
/**
Designated initializer - all parameters are optional.
- parameter name: XML element name.
- parameter value: XML element value (defaults to `nil`).
- parameter attributes: XML element attributes (defaults to empty dictionary).
- returns: An initialized `AEXMLElement` object.
*/
public init(name: String, value: String? = nil, attributes: [String : String] = [String : String]()) {
self.name = name
self.value = value
self.attributes = attributes
}
// MARK: - XML Read
/// The first element with given name **(Empty element with error if not exists)**.
open subscript(key: String) -> AEXMLElement {
guard let
first = children.filter({ $0.name == key }).first
else {
let errorElement = AEXMLElement(name: key)
errorElement.error = AEXMLError.elementNotFound
return errorElement
}
return first
}
/// Returns all of the elements with equal name as `self` **(nil if not exists)**.
open var all: [AEXMLElement]? { return parent?.children.filter { $0.name == self.name } }
/// Returns the first element with equal name as `self` **(nil if not exists)**.
open var first: AEXMLElement? { return all?.first }
/// Returns the last element with equal name as `self` **(nil if not exists)**.
open var last: AEXMLElement? { return all?.last }
/// Returns number of all elements with equal name as `self`.
open var count: Int { return all?.count ?? 0 }
fileprivate func filter(withCondition condition: (AEXMLElement) -> Bool) -> [AEXMLElement]? {
guard let elements = all else { return nil }
var found = [AEXMLElement]()
for element in elements {
if condition(element) {
found.append(element)
}
}
return found.count > 0 ? found : nil
}
/**
Returns all elements with given value.
- parameter value: XML element value.
- returns: Optional Array of found XML elements.
*/
open func all(withValue value: String) -> [AEXMLElement]? {
let found = filter { (element) -> Bool in
return element.value == value
}
return found
}
/**
Returns all elements with given attributes.
- parameter attributes: Dictionary of Keys and Values of attributes.
- returns: Optional Array of found XML elements.
*/
open func all(withAttributes attributes: [String : String]) -> [AEXMLElement]? {
let found = filter { (element) -> Bool in
var countAttributes = 0
for (key, value) in attributes {
if element.attributes[key] == value {
countAttributes += 1
}
}
return countAttributes == attributes.count
}
return found
}
// MARK: - XML Write
/**
Adds child XML element to `self`.
- parameter child: Child XML element to add.
- returns: Child XML element with `self` as `parent`.
*/
@discardableResult open func addChild(_ child: AEXMLElement) -> AEXMLElement {
child.parent = self
children.append(child)
return child
}
/**
Adds child XML element to `self`.
- parameter name: Child XML element name.
- parameter value: Child XML element value (defaults to `nil`).
- parameter attributes: Child XML element attributes (defaults to empty dictionary).
- returns: Child XML element with `self` as `parent`.
*/
@discardableResult open func addChild(name: String,
value: String? = nil,
attributes: [String : String] = [String : String]()) -> AEXMLElement
{
let child = AEXMLElement(name: name, value: value, attributes: attributes)
return addChild(child)
}
/// Removes `self` from `parent` XML element.
open func removeFromParent() {
parent?.removeChild(self)
}
fileprivate func removeChild(_ child: AEXMLElement) {
if let childIndex = children.index(where: { $0 === child }) {
children.remove(at: childIndex)
}
}
fileprivate var parentsCount: Int {
var count = 0
var element = self
while let parent = element.parent {
count += 1
element = parent
}
return count
}
fileprivate func indent(withDepth depth: Int) -> String {
var count = depth
var indent = String()
while count > 0 {
indent += "\t"
count -= 1
}
return indent
}
/// Complete hierarchy of `self` and `children` in **XML** escaped and formatted String
open var xml: String {
var xml = String()
// open element
xml += indent(withDepth: parentsCount - 1)
xml += "<\(name)"
if attributes.count > 0 {
// insert attributes
for (key, value) in attributes {
xml += " \(key)=\"\(value.xmlEscaped)\""
}
}
if value == nil && children.count == 0 {
// close element
xml += " />"
} else {
if children.count > 0 {
// add children
xml += ">\n"
for child in children {
xml += "\(child.xml)\n"
}
// add indentation
xml += indent(withDepth: parentsCount - 1)
xml += "</\(name)>"
} else {
// insert string value and close element
xml += ">\(string.xmlEscaped)</\(name)>"
}
}
return xml
}
/// Same as `xmlString` but without `\n` and `\t` characters
open var xmlCompact: String {
let chars = CharacterSet(charactersIn: "\n\t")
return xml.components(separatedBy: chars).joined(separator: "")
}
}
public extension String {
/// String representation of self with XML special characters escaped.
public var xmlEscaped: String {
// we need to make sure "&" is escaped first. Not doing this may break escaping the other characters
var escaped = replacingOccurrences(of: "&", with: "&amp;", options: .literal)
// replace the other four special characters
let escapeChars = ["<" : "&lt;", ">" : "&gt;", "'" : "&apos;", "\"" : "&quot;"]
for (char, echar) in escapeChars {
escaped = escaped.replacingOccurrences(of: char, with: echar, options: .literal)
}
return escaped
}
}
+37
View File
@@ -0,0 +1,37 @@
//
// Error.swift
//
// Copyright (c) 2014-2016 Marko Tadić <tadija@me.com> http://tadija.net
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/// A type representing error value that can be thrown or inside `error` property of `AEXMLElement`.
public enum AEXMLError: Error {
/// This will be inside `error` property of `AEXMLElement` when subscript is used for not-existing element.
case elementNotFound
/// This will be inside `error` property of `AEXMLDocument` when there is no root element.
case rootElementMissing
/// `AEXMLDocument` can throw this error on `init` or `loadXMLData` if parsing with `XMLParser` was not successful.
case parsingFailed
}
+26
View File
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>3.0.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
+52
View File
@@ -0,0 +1,52 @@
//
// Options.swift
// AEXML
//
// Created by Marko Tadic on 9/10/16.
// Copyright © 2016 AE. All rights reserved.
//
import Foundation
/// Options used in `AEXMLDocument`
public struct AEXMLOptions {
/// Values used in XML Document header
public struct DocumentHeader {
/// Version value for XML Document header (defaults to 1.0).
public var version = 1.0
/// Encoding value for XML Document header (defaults to "utf-8").
public var encoding = "utf-8"
/// Standalone value for XML Document header (defaults to "no").
public var standalone = "no"
/// XML Document header
public var xmlString: String {
return "<?xml version=\"\(version)\" encoding=\"\(encoding)\" standalone=\"\(standalone)\"?>"
}
}
/// Settings used by `Foundation.XMLParser`
public struct ParserSettings {
/// Parser reports the namespaces and qualified names of elements. (defaults to `false`)
public var shouldProcessNamespaces = false
/// Parser reports the prefixes indicating the scope of namespace declarations. (defaults to `false`)
public var shouldReportNamespacePrefixes = false
/// Parser reports declarations of external entities. (defaults to `false`)
public var shouldResolveExternalEntities = false
}
/// Values used in XML Document header (defaults to `DocumentHeader()`)
public var documentHeader = DocumentHeader()
/// Settings used by `Foundation.XMLParser` (defaults to `ParserSettings()`)
public var parserSettings = ParserSettings()
/// Designated initializer - Creates and returns default `AEXMLOptions`.
public init() {}
}
+101
View File
@@ -0,0 +1,101 @@
//
// Parser.swift
//
// Copyright (c) 2014-2016 Marko Tadić <tadija@me.com> http://tadija.net
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/// Simple wrapper around `Foundation.XMLParser`.
internal class AEXMLParser: NSObject, XMLParserDelegate {
// MARK: - Properties
let document: AEXMLDocument
let data: Data
var currentParent: AEXMLElement?
var currentElement: AEXMLElement?
var currentValue = String()
var parseError: Error?
// MARK: - Lifecycle
init(document: AEXMLDocument, data: Data) {
self.document = document
self.data = data
currentParent = document
super.init()
}
// MARK: - API
func parse() throws {
let parser = XMLParser(data: data)
parser.delegate = self
parser.shouldProcessNamespaces = document.options.parserSettings.shouldProcessNamespaces
parser.shouldReportNamespacePrefixes = document.options.parserSettings.shouldReportNamespacePrefixes
parser.shouldResolveExternalEntities = document.options.parserSettings.shouldResolveExternalEntities
let success = parser.parse()
if !success {
guard let error = parseError else { throw AEXMLError.parsingFailed }
throw error
}
}
// MARK: - XMLParserDelegate
@objc func parser(_ parser: XMLParser,
didStartElement elementName: String,
namespaceURI: String?,
qualifiedName qName: String?,
attributes attributeDict: [String : String])
{
currentValue = String()
currentElement = currentParent?.addChild(name: elementName, attributes: attributeDict)
currentParent = currentElement
}
@objc func parser(_ parser: XMLParser, foundCharacters string: String) {
currentValue += string
let newValue = currentValue.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
currentElement?.value = newValue == String() ? nil : newValue
}
@objc func parser(_ parser: XMLParser,
didEndElement elementName: String,
namespaceURI: String?,
qualifiedName qName: String?)
{
currentParent = currentParent?.parent
currentElement = nil
}
@objc func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) {
self.parseError = parseError
}
}
+248 -209
View File
@@ -1,3 +1,4 @@
//
// DropboxFileProvider.swift
// FileProvider
@@ -12,246 +13,264 @@ 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.
public class DropboxFileProvider: NSObject, FileProviderBasic {
public let type: String = "WebDAV"
public let isPathRelative: Bool = true
public let baseURL: NSURL?
public var currentPath: String = ""
public var dispatch_queue: dispatch_queue_t {
open class DropboxFileProvider: 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 {
willSet {
assert(_session == nil, "It's not effective to change dispatch_queue property after session is initialized.")
}
}
public weak var delegate: FileProviderDelegate?
public let credential: NSURLCredential?
private var _session: NSURLSession?
internal var session: NSURLSession {
open weak var delegate: FileProviderDelegate?
open let credential: URLCredential?
open private(set) var cache: URLCache?
public var useCache: Bool = false
public var validatingCache: Bool = true
fileprivate var _session: URLSession?
fileprivate var sessionDelegate: SessionDelegate?
public var session: URLSession {
if _session == nil {
let queue = NSOperationQueue()
self.sessionDelegate = SessionDelegate(fileProvider: self, credential: credential)
let queue = OperationQueue()
//queue.underlyingQueue = dispatch_queue
_session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), delegate: self, 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: NSURLCredential?) {
public init? (credential: URLCredential?, cache: URLCache? = nil) {
self.baseURL = nil
dispatch_queue = dispatch_queue_create("FileProvider.\(type)", DISPATCH_QUEUE_CONCURRENT)
dispatch_queue = DispatchQueue(label: "FileProvider.\(DropboxFileProvider.type)", attributes: DispatchQueue.Attributes.concurrent)
//let url = baseURL.uw_absoluteString
self.credential = credential
self.cache = cache
}
deinit {
_session?.invalidateAndCancel()
}
public func contentsOfDirectoryAtPath(path: String, completionHandler: ((contents: [FileObject], error: ErrorType?) -> Void)) {
open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {
list(path) { (contents, cursor, error) in
completionHandler(contents: contents, error: error)
completionHandler(contents, error)
}
}
public func attributesOfItemAtPath(path: String, completionHandler: ((attributes: FileObject?, error: ErrorType?) -> Void)) {
let url = NSURL(string: "https://api.dropboxapi.com/2/files/get_metadata")!
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
let url = URL(string: "https://api.dropboxapi.com/2/files/get_metadata")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let requestDictionary = ["path": correctPath(path)!]
request.HTTPBody = dictionaryToJSON(requestDictionary)?.dataUsingEncoding(NSUTF8StringEncoding)
let task = session.dataTaskWithRequest(request) { (data, response, error) in
if let response = response as? NSHTTPURLResponse {
defer {
self.delegateNotify(FileOperation.Create(path: path), error: error)
}
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 fileObject: DropboxFileObject?
if let response = response as? HTTPURLResponse {
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: String(data: data ?? NSData(), encoding: NSUTF8StringEncoding)) : nil
if let data = data, let jsonStr = String(data: data, encoding: NSUTF8StringEncoding), let json = self.jsonToDictionary(jsonStr), let file = self.mapToFileObject(json) {
completionHandler(attributes: file, error: 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(attributes: nil, error: dbError)
return
}
completionHandler(attributes: nil, error: error)
}
completionHandler(fileObject, dbError ?? error)
})
task.resume()
}
public func storageProperties(completionHandler: ((total: Int64, used: Int64) -> Void)) {
let url = NSURL(string: "https://api.dropboxapi.com/2/users/get_space_usage")!
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
open func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void)) {
let url = URL(string: "https://api.dropboxapi.com/2/users/get_space_usage")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
let task = session.dataTaskWithRequest(request) { (data, response, error) in
if let data = data, let jsonStr = String(data: data, encoding: NSUTF8StringEncoding), let json = self.jsonToDictionary(jsonStr) {
let totalSize = ((json["allocation"] as? NSDictionary)?["allocated"] as? NSNumber)?.longLongValue ?? -1
let usedSize = (json["used"] as? NSNumber)?.longLongValue ?? 0
completionHandler(total: totalSize, used: usedSize)
return
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var totalSize: Int64 = -1
var usedSize: Int64 = 0
if let data = data, let jsonStr = String(data: data, encoding: .utf8), let json = jsonToDictionary(jsonStr) {
totalSize = ((json["allocation"] as? NSDictionary)?["allocated"] as? NSNumber)?.int64Value ?? -1
usedSize = (json["used"] as? NSNumber)?.int64Value ?? 0
}
completionHandler(total: -1, used: 0)
}
completionHandler(totalSize, usedSize)
})
task.resume()
}
public weak var fileOperationDelegate: FileOperationDelegate?
open weak var fileOperationDelegate: FileOperationDelegate?
}
extension DropboxFileProvider: FileProviderOperations {
public func createFolder(folderName: String, atPath: String, completionHandler: SimpleCompletionHandler) {
let path = (atPath as NSString).stringByAppendingPathComponent(folderName) + "/"
doOperation(.Create(path: path), completionHandler: completionHandler)
public func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let path = (atPath as NSString).appendingPathComponent(folderName) + "/"
return doOperation(.create(path: path), completionHandler: completionHandler)
}
public func createFile(fileAttribs: FileObject, atPath path: String, contents data: NSData?, completionHandler: SimpleCompletionHandler) {
self.writeContentsAtPath(path, contents: data ?? NSData(), 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 moveItemAtPath(path: String, 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 copyItemAtPath(path: String, 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 removeItemAtPath(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)
}
private func doOperation(operation: FileOperation, completionHandler: SimpleCompletionHandler) {
let url: String
var path: String?, fromPath: String?, toPath: String?
switch operation {
case .Create(path: let p):
url = "https://api.dropboxapi.com/2/files/create_folder"
path = p
case .Copy(source: let fp, destination: let tp):
url = "https://api.dropboxapi.com/2/files/copy"
fromPath = fp
toPath = tp
case .Move(source: let fp, destination: let tp):
url = "https://api.dropboxapi.com/2/files/move"
fromPath = fp
toPath = tp
case .Modify(path: let p):
return
case .Remove(path: let p):
url = "https://api.dropboxapi.com/2/files/delete"
path = p
case .Link(link: _, target: _):
return
fileprivate func doOperation(_ operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: operation) ?? true == true else {
return nil
}
let request = NSMutableURLRequest(URL: NSURL(string: url)!)
request.HTTPMethod = "POST"
let url: String
guard let sourcePath = operation.source else { return nil }
let destPath = operation.destination
switch operation {
case .create:
url = "https://api.dropboxapi.com/2/files/create_folder"
case .copy:
url = "https://api.dropboxapi.com/2/files/copy"
case .move:
url = "https://api.dropboxapi.com/2/files/move"
case .remove:
url = "https://api.dropboxapi.com/2/files/delete"
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)
requestDictionary["from_path"] = correctPath(fromPath)
requestDictionary["to_path"] = correctPath(toPath)
request.HTTPBody = dictionaryToJSON(requestDictionary)?.dataUsingEncoding(NSUTF8StringEncoding)
let task = session.dataTaskWithRequest(request) { (data, response, error) in
if let response = response as? NSHTTPURLResponse {
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path ?? fromPath ?? "", errorDescription: String(data: data ?? NSData(), encoding: NSUTF8StringEncoding)) : 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?(error: dbError)
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?
}
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?(error: error)
}
completionHandler?(dbError ?? error)
self.delegateNotify(operation, error: dbError ?? error)
})
task.taskDescription = operation.json
task.resume()
return RemoteOperationHandle(operationType: operation, tasks: [task])
}
public func copyLocalFileToPath(localFile: NSURL, toPath: String, completionHandler: SimpleCompletionHandler) {
guard let data = NSData(contentsOfURL: localFile) else {
let error = throwError(localFile.uw_absoluteString, code: NSURLError.FileDoesNotExist)
completionHandler?(error: error)
return
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
}
upload_simple(toPath, data: data, overwrite: true, operation: .Copy(source: localFile.absoluteString, destination: toPath), completionHandler: completionHandler)
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 copyPathToLocalFile(path: String, toLocalURL destURL: NSURL, completionHandler: SimpleCompletionHandler) {
let url = NSURL(string: "https://content.dropboxapi.com/2/files/download")!
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "GET"
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]
request.setValue(dictionaryToJSON(requestDictionary), forHTTPHeaderField: "Dropbox-API-Arg")
let task = session.downloadTaskWithRequest(request, completionHandler: { (cacheURL, response, error) in
guard let cacheURL = cacheURL, let httpResponse = response as? NSHTTPURLResponse where httpResponse.statusCode < 300 else {
let code = FileProviderHTTPErrorCode(rawValue: (response as? NSHTTPURLResponse)?.statusCode ?? -1)
let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: nil) : nil
completionHandler?(error: dbError ?? error)
let requestJson = dictionaryToJSON(requestDictionary as [String : AnyObject]) ?? ""
request.setValue(requestJson, forHTTPHeaderField: "Dropbox-API-Arg")
let task = session.downloadTask(with: request, completionHandler: { (cacheURL, response, error) in
guard let cacheURL = cacheURL, let httpResponse = response as? HTTPURLResponse , httpResponse.statusCode < 300 else {
let code = FileProviderHTTPErrorCode(rawValue: (response as? HTTPURLResponse)?.statusCode ?? -1)
let errorData : Data? = nil //Data(contentsOf:cacheURL) // TODO: Figure out how to get error response data for the error description
let dbError : FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: String(data: errorData ?? Data(), encoding: .utf8)) : nil
completionHandler?(dbError ?? error)
return
}
do {
try NSFileManager.defaultManager().moveItemAtURL(cacheURL, toURL: destURL)
completionHandler?(error: nil)
try FileManager.default.moveItem(at: cacheURL, to: destURL)
completionHandler?(nil)
} catch let e {
completionHandler?(error: e)
completionHandler?(e)
}
})
task.taskDescription = self.dictionaryToJSON(["type": "Copy", "source": path, "dest": destURL.uw_absoluteString])
task.taskDescription = opType.json
task.resume()
return RemoteOperationHandle(operationType: opType, tasks: [task])
}
}
extension DropboxFileProvider: FileProviderReadWrite {
public func contentsAtPath(path: String, completionHandler: ((contents: NSData?, error: ErrorType?) -> Void)) {
self.contentsAtPath(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 contentsAtPath(path: String, offset: Int64, length: Int, completionHandler: ((contents: NSData?, error: ErrorType?) -> Void)) {
let url = NSURL(string: "https://content.dropboxapi.com/2/files/download")!
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "GET"
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"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
if length > 0 {
request.setValue("bytes=\(offset)-\(offset + length)", forHTTPHeaderField: "Range")
} else if offset > 0 && length < 0 {
request.setValue("bytes=\(offset)-", forHTTPHeaderField: "Range")
}
let requestDictionary = ["path": path]
request.setValue(dictionaryToJSON(requestDictionary), forHTTPHeaderField: "Dropbox-API-Arg")
let task = session.dataTaskWithRequest(request, completionHandler: { (datam, response, error) in
guard let data = datam, let httpResponse = response as? NSHTTPURLResponse where httpResponse.statusCode < 300 else {
let code = FileProviderHTTPErrorCode(rawValue: (response as? NSHTTPURLResponse)?.statusCode ?? -1)
let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: String(data: datam ?? NSData(), encoding: NSUTF8StringEncoding)) : nil
completionHandler(contents: nil, error: dbError ?? error)
return
let requestDictionary = ["path": correctPath(path)! as NSString]
request.setValue(dictionaryToJSON(requestDictionary as [String : AnyObject]), forHTTPHeaderField: "Dropbox-API-Arg")
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var dbError: FileProviderDropboxError?
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(contents: data, error: 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 writeContentsAtPath(path: String, contents data: NSData, 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 searchFilesAtPath(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: ((files: [FileObject], error: ErrorType?) -> Void)) {
public func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
var foundFiles = [DropboxFileObject]()
search(path, query: query, foundItem: { (file) in
foundFiles.append(file)
foundItemHandler?(file)
}, completionHandler: { (error) in
completionHandler(files: foundFiles, error: error)
completionHandler(foundFiles, error)
})
}
private func registerNotifcation(path: String, eventHandler: (() -> Void)) {
fileprivate func registerNotifcation(path: String, eventHandler: (() -> Void)) {
/* There is two ways to monitor folders changing in Dropbox. Either using webooks
* which means you have to implement a server to translate it to push notifications
* or using apiv2 list_folder/longpoll method. The second one is implemeted here.
@@ -260,16 +279,74 @@ extension DropboxFileProvider: FileProviderReadWrite {
*/
NotImplemented()
}
private func unregisterNotifcation(path: String) {
fileprivate func unregisterNotifcation(path: String) {
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 {
public func thumbnailOfFileSupported(path: String) -> Bool {
switch (path as NSString).pathExtension.lowercaseString {
switch (path as NSString).pathExtension.lowercased() {
case "jpg", "jpeg", "gif", "bmp", "png", "tif", "tiff":
return true
/*case "doc", "docx", "docm", "xls", "xlsx", "xlsm":
@@ -287,11 +364,11 @@ extension DropboxFileProvider: ExtendedFileProvider {
return false
}
public func thumbnailOfFileAtPath(path: String, dimension: CGSize, completionHandler: ((image: ImageClass?, error: ErrorType?) -> Void)) {
let url: NSURL
switch (path as NSString).pathExtension.lowercaseString {
public func thumbnailOfFile(path: String, dimension: CGSize, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void)) {
let url: URL
switch (path as NSString).pathExtension.lowercased() {
case "jpg", "jpeg", "gif", "bmp", "png", "tif", "tiff":
url = NSURL(string: "https://content.dropboxapi.com/2/files/get_thumbnail")!
url = URL(string: "https://content.dropboxapi.com/2/files/get_thumbnail")!
/*case "doc", "docx", "docm", "xls", "xlsx", "xlsm":
fallthrough
case "ppt", "pps", "ppsx", "ppsm", "pptx", "pptm":
@@ -301,78 +378,40 @@ extension DropboxFileProvider: ExtendedFileProvider {
default:
return
}
let request = NSMutableURLRequest(URL: url)
var request = URLRequest(url: url)
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
var requestDictionary = ["path": path]
requestDictionary["format"] = "jpeg"
requestDictionary["size"] = "w\(Int(dimension.width))h\(Int(dimension.height))"
var requestDictionary = ["path": path as NSString]
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.dataTaskWithRequest(request) { (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? NSHTTPURLResponse, let result = r.allHeaderFields["Dropbox-API-Result"] as? String, let jsonResult = self.jsonToDictionary(result) {
if let r = response as? HTTPURLResponse, let result = r.allHeaderFields["Dropbox-API-Result"] as? String, let jsonResult = jsonToDictionary(result) {
if jsonResult["error"] != nil {
completionHandler(image: nil, error: self.throwError(path, code: NSURLError.CannotDecodeRawData))
completionHandler(nil, self.throwError(path, code: URLError.cannotDecodeRawData as FoundationErrorEnum))
}
}
if let data = data {
image = ImageClass(data: data)
}
completionHandler(image: image, error: error)
}
completionHandler(image, error)
})
task.resume()
}
public func propertiesOfFileAtPath(path: String, completionHandler: ((propertiesDictionary: [String : AnyObject], keys: [String], error: ErrorType?) -> Void)) {
public func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String : Any], _ keys: [String], _ error: Error?) -> Void)) {
NotImplemented()
}
}
extension DropboxFileProvider: FileProvider {}
// MARK: URLSession delegate
extension DropboxFileProvider: NSURLSessionDataDelegate, NSURLSessionDownloadDelegate {
public func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {
return
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
}
public func URLSession(session: NSURLSession, task: NSURLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
guard let desc = task.taskDescription, let json = jsonToDictionary(desc) else {
return
}
guard let type = json["type"] as? String, let source = json["source"] as? String else {
return
}
let dest = json["dest"] as? String
let op : FileOperation
switch type {
case "Create":
op = .Create(path: source)
case "Copy":
guard let dest = dest else { return }
op = .Copy(source: source, destination: dest)
case "Move":
guard let dest = dest else { return }
op = .Move(source: source, destination: dest)
case "Modify":
op = .Modify(path: source)
case "Remove":
op = .Remove(path: source)
case "Link":
guard let dest = dest else { return }
op = .Link(link: source, target: dest)
default:
return
}
let progress = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
self.delegate?.fileproviderProgress(self, operation: op, progress: progress)
}
public func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
guard let desc = downloadTask.taskDescription, let json = jsonToDictionary(desc), let source = json["source"] as? String, dest = json["dest"] as? String else {
return
}
self.delegate?.fileproviderProgress(self, operation: .Copy(source: source, destination: dest), progress: Float(totalBytesWritten) / Float(totalBytesExpectedToWrite))
}
}
}
+105 -99
View File
@@ -8,7 +8,7 @@
import Foundation
public struct FileProviderDropboxError: ErrorType, CustomStringConvertible {
public struct FileProviderDropboxError: Error, CustomStringConvertible {
public let code: FileProviderHTTPErrorCode
public let path: String
public let errorDescription: String?
@@ -19,44 +19,65 @@ public struct FileProviderDropboxError: ErrorType, CustomStringConvertible {
}
public final class DropboxFileObject: FileObject {
public let serverTime: NSDate?
public let id: String?
public let rev: String?
internal init(name: String, path: String) {
super.init(absoluteURL: URL(string: path), name: name, path: path)
}
public init(name: String, path: String, size: Int64 = -1, serverTime: NSDate? = nil, modifiedDate: NSDate? = 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: NSURL(string: path), name: name, path: path, size: size, createdDate: nil, modifiedDate: modifiedDate, fileType: fileType, isHidden: isHidden, isReadOnly: isReadOnly)
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:disable[ARITY]
internal extension DropboxFileProvider {
func list(path: String, cursor: String? = nil, prevContents: [DropboxFileObject] = [], recursive: Bool = false, completionHandler: ((contents: [FileObject], cursor: String?, error: ErrorType?) -> Void)) {
func list(_ path: String, cursor: String? = nil, prevContents: [DropboxFileObject] = [], recursive: Bool = false, completionHandler: @escaping ((_ contents: [FileObject], _ cursor: String?, _ error: Error?) -> Void)) {
var requestDictionary = [String: AnyObject]()
let url: NSURL
let url: URL
if let cursor = cursor {
url = NSURL(string: "https://api.dropboxapi.com/2/files/list_folder/continue")!
requestDictionary["cursor"] = cursor
url = URL(string: "https://api.dropboxapi.com/2/files/list_folder/continue")!
requestDictionary["cursor"] = cursor as NSString?
} else {
url = NSURL(string: "https://api.dropboxapi.com/2/files/list_folder")!
requestDictionary["path"] = correctPath(path)
requestDictionary["recursive"] = recursive
url = URL(string: "https://api.dropboxapi.com/2/files/list_folder")!
requestDictionary["path"] = correctPath(path) as NSString?
requestDictionary["recursive"] = recursive as NSNumber?
}
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.HTTPBody = dictionaryToJSON(requestDictionary)?.dataUsingEncoding(NSUTF8StringEncoding)
let task = session.dataTaskWithRequest(request) { (data, response, error) in
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? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderDropboxError(code: rCode, path: path, errorDescription: String(data: data ?? NSData(), encoding: NSUTF8StringEncoding))
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: .utf8))
}
if let data = data, let jsonStr = String(data: data, encoding: NSUTF8StringEncoding) {
let json = self.jsonToDictionary(jsonStr)
if let entries = json?["entries"] as? [AnyObject] where entries.count > 0 {
var files = prevContents
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 {
for entry in entries {
if let entry = entry as? [String: AnyObject], let file = self.mapToFileObject(entry) {
files.append(file)
@@ -66,84 +87,67 @@ 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(contents: files, cursor: ncursor, error: responseError ?? error)
return
}
return
}
}
completionHandler(contents: [], cursor: nil, error: responseError ?? error)
}
completionHandler(files, nil, responseError ?? error)
})
task.taskDescription = FileOperationType.fetch(path: path).json
task.resume()
}
func upload_simple(targetPath: String, data: NSData, modifiedDate: NSDate = NSDate(), overwrite: Bool, operation: FileOperation, completionHandler: SimpleCompletionHandler) {
assert(data.length < 150*1024*1024, "Maximum size of allowed size to upload is 150MB")
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: NSURL
url = NSURL(string: "https://content.dropboxapi.com/2/files/upload")!
requestDictionary["path"] = correctPath(targetPath)
requestDictionary["mode"] = overwrite ? "overwrite" : "add"
let dateFormatter = NSDateFormatter()
let url: URL
url = URL(string: "https://content.dropboxapi.com/2/files/upload")!
requestDictionary["path"] = correctPath(targetPath) as NSString?
requestDictionary["mode"] = (overwrite ? "overwrite" : "add") as NSString
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssz"
requestDictionary["client_modified"] = dateFormatter.stringFromDate(modifiedDate)
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
requestDictionary["client_modified"] = dateFormatter.string(from: modifiedDate) as NSString
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
request.setValue(dictionaryToJSON(requestDictionary), forHTTPHeaderField: "Dropbox-API-Arg")
request.HTTPBody = data
let task = session.uploadTaskWithRequest(request, fromData: data) { (data, response, error) in
request.httpBody = data
let task = session.uploadTask(with: request, from: data, completionHandler: { (data, response, error) in
var responseError: FileProviderDropboxError?
if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderDropboxError(code: rCode, path: targetPath, errorDescription: String(data: data ?? NSData(), encoding: NSUTF8StringEncoding))
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderDropboxError(code: rCode, path: targetPath, errorDescription: String(data: data ?? Data(), encoding: .utf8))
}
defer {
self.delegateNotify(.Create(path: targetPath), error: responseError ?? error)
}
completionHandler?(error: responseError ?? error)
}
var dic: [String: AnyObject] = ["type": operation.description]
switch operation {
case .Create(path: let s):
dic["source"] = s
case .Copy(source: let s, destination: let d):
dic["source"] = s
dic["dest"] = d
case .Modify(path: let s):
dic["source"] = s
case .Move(source: let s, destination: let d):
dic["source"] = s
dic["dest"] = d
default:
break
}
task.taskDescription = self.dictionaryToJSON(dic)
completionHandler?(responseError ?? error)
self.delegateNotify(.create(path: targetPath), error: responseError ?? error)
})
task.taskDescription = operation.json
task.resume()
return RemoteOperationHandle(operationType: operation, tasks: [task])
}
func search(startPath: String = "", query: String, start: Int = 0, maxResultPerPage: Int = 25, maxResults: Int = -1, foundItem:((file: DropboxFileObject) -> Void), completionHandler: ((error: ErrorType?) -> Void)) {
let url = NSURL(string: "https://api.dropboxapi.com/2/files/search")!
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
func search(_ startPath: String = "", query: String, start: Int = 0, maxResultPerPage: Int = 25, maxResults: Int = -1, foundItem:@escaping ((_ file: DropboxFileObject) -> Void), completionHandler: @escaping ((_ error: Error?) -> Void)) {
let url = URL(string: "https://api.dropboxapi.com/2/files/search")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
var requestDictionary: [String: AnyObject] = ["path": startPath]
requestDictionary["query"] = query
requestDictionary["start"] = start
requestDictionary["max_results"] = maxResultPerPage
request.HTTPBody = dictionaryToJSON(requestDictionary)?.dataUsingEncoding(NSUTF8StringEncoding)
let task = session.dataTaskWithRequest(request) { (data, response, error) in
var requestDictionary: [String: AnyObject] = ["path": startPath as NSString]
requestDictionary["query"] = query as NSString
requestDictionary["start"] = start as NSNumber
requestDictionary["max_results"] = maxResultPerPage as NSNumber
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? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderDropboxError(code: rCode, path: startPath, errorDescription: String(data: data ?? NSData(), encoding: NSUTF8StringEncoding))
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: .utf8))
}
if let data = data, let jsonStr = String(data: data, encoding: NSUTF8StringEncoding) {
let json = self.jsonToDictionary(jsonStr)
if let entries = json?["matches"] as? [AnyObject] where entries.count > 0 {
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 {
if let entry = entry as? [String: AnyObject], let file = self.mapToFileObject(entry) {
foundItem(file: file)
foundItem(file)
}
}
let rstart = json?["start"] as? Int
@@ -151,38 +155,40 @@ internal extension DropboxFileProvider {
if hasmore, let rstart = rstart {
self.search(startPath, query: query, start: rstart + entries.count, maxResultPerPage: maxResultPerPage, foundItem: foundItem, completionHandler: completionHandler)
} else {
completionHandler(error: responseError ?? error)
completionHandler(responseError ?? error)
}
return
}
}
completionHandler(error: responseError ?? error)
}
completionHandler(responseError ?? error)
})
task.resume()
}
}
// codebeat:enable[ARITY]
internal extension DropboxFileProvider {
func mapToFileObject(jsonStr: String) -> DropboxFileObject? {
guard let json = self.jsonToDictionary(jsonStr) else { return nil }
func mapToFileObject(_ jsonStr: String) -> DropboxFileObject? {
guard let json = jsonToDictionary(jsonStr) else { return nil }
return self.mapToFileObject(json)
}
func mapToFileObject(json: [String: AnyObject]) -> DropboxFileObject? {
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)?.longLongValue ?? -1
let serverTime = resolveDate(json["server_modified"] as? String ?? "")
let modifiedDate = resolveDate(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: ErrorType?) {
dispatch_async(dispatch_get_main_queue(), {
func delegateNotify(_ operation: FileOperationType, error: Error?) {
DispatchQueue.main.async(execute: {
if error == nil {
self.delegate?.fileproviderSucceed(self, operation: operation)
} else {
@@ -190,4 +196,4 @@ internal extension DropboxFileProvider {
}
})
}
}
}
+119 -119
View File
@@ -14,26 +14,26 @@ private var lasttaskIdAssociated = 1_000_000_000
/// This class is a replica of NSURLSessionStreamTask with same api for iOS 7/8
/// while it will fallback to NSURLSessionStreamTask in iOS 9.
@objc
public class FPSStreamTask: NSURLSessionTask, NSStreamDelegate {
private var inputStream: NSInputStream?
private var outputStream: NSOutputStream?
open class FPSStreamTask: URLSessionTask, StreamDelegate {
fileprivate var inputStream: InputStream?
fileprivate var outputStream: OutputStream?
private var dispatch_queue: dispatch_queue_t!
internal var _underlyingSession: NSURLSession
private var streamDelegate: FPSStreamDelegate? {
fileprivate var dispatch_queue: DispatchQueue!
internal var _underlyingSession: URLSession
fileprivate var streamDelegate: FPSStreamDelegate? {
return (_underlyingSession.delegate as? FPSStreamDelegate)
}
private var _taskIdentifier: Int
fileprivate var _taskIdentifier: Int
@available(iOS 9.0, OSX 10.11, *)
static var streamTasks = [Int: NSURLSessionStreamTask]()
static var streamTasks = [Int: URLSessionStreamTask]()
@available(iOS 9.0, OSX 10.11, *)
internal var _underlyingTask: NSURLSessionStreamTask? {
internal var _underlyingTask: URLSessionStreamTask? {
return FPSStreamTask.streamTasks[_taskIdentifier]
}
public override var taskIdentifier: Int {
open override var taskIdentifier: Int {
if #available(iOS 9.0, OSX 10.11, *) {
return _underlyingTask!.taskIdentifier
} else {
@@ -41,8 +41,8 @@ public class FPSStreamTask: NSURLSessionTask, NSStreamDelegate {
}
}
private var _state: NSURLSessionTaskState = .Suspended
override public var state: NSURLSessionTaskState {
fileprivate var _state: URLSessionTask.State = .suspended
override open var state: URLSessionTask.State {
if #available(iOS 9.0, OSX 10.11, *) {
return _underlyingTask!.state
} else {
@@ -50,7 +50,7 @@ public class FPSStreamTask: NSURLSessionTask, NSStreamDelegate {
}
}
override public var originalRequest: NSURLRequest? {
override open var originalRequest: URLRequest? {
if #available(iOS 9.0, OSX 10.11, *) {
return _underlyingTask!.originalRequest
} else {
@@ -58,7 +58,7 @@ public class FPSStreamTask: NSURLSessionTask, NSStreamDelegate {
}
}
override public var currentRequest: NSURLRequest? {
override open var currentRequest: URLRequest? {
if #available(iOS 9.0, OSX 10.11, *) {
return _underlyingTask!.currentRequest
} else {
@@ -66,10 +66,10 @@ public class FPSStreamTask: NSURLSessionTask, NSStreamDelegate {
}
}
private var _countOfBytesSent: Int64 = 0
private var _countOfBytesRecieved: Int64 = 0
fileprivate var _countOfBytesSent: Int64 = 0
fileprivate var _countOfBytesRecieved: Int64 = 0
override public var countOfBytesSent: Int64 {
override open var countOfBytesSent: Int64 {
if #available(iOS 9.0, OSX 10.11, *) {
return _underlyingTask!.countOfBytesSent
} else {
@@ -77,7 +77,7 @@ public class FPSStreamTask: NSURLSessionTask, NSStreamDelegate {
}
}
override public var countOfBytesReceived: Int64 {
override open var countOfBytesReceived: Int64 {
if #available(iOS 9.0, OSX 10.11, *) {
return _underlyingTask!.countOfBytesReceived
} else {
@@ -85,7 +85,7 @@ public class FPSStreamTask: NSURLSessionTask, NSStreamDelegate {
}
}
override public var countOfBytesExpectedToSend: Int64 {
override open var countOfBytesExpectedToSend: Int64 {
if #available(iOS 9.0, OSX 10.11, *) {
return _underlyingTask!.countOfBytesExpectedToSend
} else {
@@ -93,7 +93,7 @@ public class FPSStreamTask: NSURLSessionTask, NSStreamDelegate {
}
}
override public var countOfBytesExpectedToReceive: Int64 {
override open var countOfBytesExpectedToReceive: Int64 {
if #available(iOS 9.0, OSX 10.11, *) {
return _underlyingTask!.countOfBytesExpectedToReceive
} else {
@@ -106,49 +106,49 @@ public class FPSStreamTask: NSURLSessionTask, NSStreamDelegate {
}
var host: (hostname: String, port: Int)?
var service: NSNetService?
var service: NetService?
internal init(session: NSURLSession, host: String, port: Int) {
internal init(session: URLSession, host: String, port: Int) {
self._underlyingSession = session
if #available(iOS 9.0, OSX 10.11, *) {
let task = session.streamTaskWithHostName(host, port: port)
let task = session.streamTask(withHostName: host, port: port)
self._taskIdentifier = task.taskIdentifier
FPSStreamTask.streamTasks[_taskIdentifier] = task
} else {
lasttaskIdAssociated += 1
self._taskIdentifier = lasttaskIdAssociated
self.host = (host, port)
self.dispatch_queue = dispatch_queue_create("FSPStreamTask", DISPATCH_QUEUE_CONCURRENT)
self.dispatch_queue = DispatchQueue(label: "FSPStreamTask", attributes: DispatchQueue.Attributes.concurrent)
}
}
internal init(session: NSURLSession, netService: NSNetService) {
internal init(session: URLSession, netService: NetService) {
self._underlyingSession = session
if #available(iOS 9.0, OSX 10.11, *) {
let task = session.streamTaskWithNetService(netService)
let task = session.streamTask(with: netService)
self._taskIdentifier = task.taskIdentifier
FPSStreamTask.streamTasks[_taskIdentifier] = task
} else {
lasttaskIdAssociated += 1
self._taskIdentifier = lasttaskIdAssociated
self.service = netService
self.dispatch_queue = dispatch_queue_create("FSPStreamTask", DISPATCH_QUEUE_CONCURRENT)
self.dispatch_queue = DispatchQueue(label: "FSPStreamTask", attributes: DispatchQueue.Attributes.concurrent)
}
}
override public func cancel() {
override open func cancel() {
if #available(iOS 9.0, OSX 10.11, *) {
_underlyingTask!.cancel()
} else {
self._state = .Canceling
self._state = .canceling
inputStream?.setValue(kCFBooleanTrue, forKey: kCFStreamPropertyShouldCloseNativeSocket as String)
outputStream?.setValue(kCFBooleanTrue, forKey: kCFStreamPropertyShouldCloseNativeSocket as String)
self.inputStream?.close()
self.outputStream?.close()
self.inputStream?.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
self.outputStream?.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
self.inputStream?.remove(from: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)
self.outputStream?.remove(from: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)
self.inputStream?.delegate = nil
self.outputStream?.delegate = nil
@@ -156,14 +156,14 @@ public class FPSStreamTask: NSURLSessionTask, NSStreamDelegate {
self.inputStream = nil
self.outputStream = nil
self._state = .Completed
self._state = .completed
self._countOfBytesSent = 0
self._countOfBytesRecieved = 0
}
}
var _error: NSError? = nil
override public var error: NSError? {
var _error: Error? = nil
override open var error: Error? {
if #available(iOS 9.0, OSX 10.11, *) {
return _underlyingTask!.error
} else {
@@ -171,19 +171,15 @@ public class FPSStreamTask: NSURLSessionTask, NSStreamDelegate {
}
}
override public func suspend() {
override open func suspend() {
if #available(iOS 9.0, OSX 10.11, *) {
_underlyingTask!.suspend()
} else {
inputStream?.close()
outputStream?.close()
streamDelegate?.URLSession?(_underlyingSession, readClosedForStreamTask: self)
streamDelegate?.URLSession?(_underlyingSession, writeClosedForStreamTask: self)
self._state = .Suspended
self._state = .suspended
}
}
override public func resume() {
override open func resume() {
if #available(iOS 9.0, OSX 10.11, *) {
_underlyingTask!.resume()
} else {
@@ -195,74 +191,74 @@ public class FPSStreamTask: NSURLSessionTask, NSStreamDelegate {
let hostRef: CFString = NSString(string: host.hostname)
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, hostRef, UInt32(host.port), &readStream, &writeStream)
} else if let service = service {
let cfnetService = CFNetServiceCreate(kCFAllocatorDefault, service.domain, service.type, service.name, Int32(service.port))
let cfnetService = CFNetServiceCreate(kCFAllocatorDefault, service.domain as CFString, service.type as CFString, service.name as CFString, Int32(service.port))
CFStreamCreatePairWithSocketToNetService(kCFAllocatorDefault, cfnetService.takeRetainedValue(), &readStream, &writeStream)
}
inputStream = readStream?.takeRetainedValue()
outputStream = writeStream?.takeRetainedValue()
guard let inputStream = inputStream, outputStream = outputStream else {
guard let inputStream = inputStream, let outputStream = outputStream else {
return
}
streamDelegate?.URLSession?(self._underlyingSession, streamTask: self, didBecomeInputStream: inputStream, outputStream: outputStream)
streamDelegate?.urlSession?(self._underlyingSession, streamTask: self, didBecome: inputStream, outputStream: outputStream)
}
guard let inputStream = inputStream, outputStream = outputStream else {
guard let inputStream = inputStream, let outputStream = outputStream else {
return
}
inputStream.delegate = self
outputStream.delegate = self
dispatch_sync(dispatch_queue, {
inputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
outputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
dispatch_queue.sync(execute: {
inputStream.schedule(in: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)
outputStream.schedule(in: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)
})
inputStream.open()
outputStream.open()
_state = .Running
_state = .running
}
}
private let dataToBeSent: NSMutableData = NSMutableData()
private let dataReceived: NSMutableData = NSMutableData()
fileprivate let dataToBeSent: NSMutableData = NSMutableData()
fileprivate let dataReceived: NSMutableData = NSMutableData()
/* Read minBytes, or at most maxBytes bytes and invoke the completion
* handler on the sessions delegate queue with the data or an error.
* If an error occurs, any outstanding reads will also fail, and new
* read requests will error out immediately.
*/
public func readDataOfMinLength(minBytes: Int, maxLength maxBytes: Int, timeout: NSTimeInterval, completionHandler: (NSData?, Bool, NSError?) -> Void) {
open func readData(OfMinLength minBytes: Int, maxLength maxBytes: Int, timeout: TimeInterval, completionHandler: @escaping (Data?, Bool, NSError?) -> Void) {
if #available(iOS 9.0, OSX 10.11, *) {
_underlyingTask!.readDataOfMinLength(minBytes, maxLength: maxBytes, timeout: timeout, completionHandler: completionHandler)
_underlyingTask!.readData(ofMinLength: minBytes, maxLength: maxBytes, timeout: timeout, completionHandler: completionHandler as! (Data?, Bool, Error?) -> Void)
} else {
guard let inputStream = inputStream else {
return
}
var timedOut: Bool = false
dispatch_async(dispatch_queue) {
dispatch_queue.async {
if timeout > 0 {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(timeout * 1_000_000_000)), self.dispatch_queue, {
self.dispatch_queue.asyncAfter(deadline: DispatchTime.now() + Double(Int64(timeout * 1_000_000_000)) / Double(NSEC_PER_SEC), execute: {
timedOut = true
completionHandler(nil, inputStream.streamStatus == .AtEnd, inputStream.streamError)
completionHandler(nil, inputStream.streamStatus == .atEnd, inputStream.streamError as NSError?)
})
}
while (self.dataReceived.length == 0 || self.dataReceived.length < minBytes) && !timedOut {
NSRunLoop.currentRunLoop().runUntilDate(NSDate(timeIntervalSinceNow: 0.1));
NSThread.sleepForTimeInterval(0.1)
RunLoop.current.run(until: Date(timeIntervalSinceNow: 0.1));
Thread.sleep(forTimeInterval: 0.1)
}
let dR = NSMutableData()
if self.dataReceived.length > maxBytes {
let range = NSRange(location: 0, length: maxBytes - 1)
dR.appendData(self.dataReceived.subdataWithRange(range))
self.dataReceived.replaceBytesInRange(range, withBytes: nil, length: 0)
dR.append(self.dataReceived.subdata(with: range))
self.dataReceived.replaceBytes(in: range, withBytes: nil, length: 0)
} else {
dR.appendData(self.dataReceived)
dR.append(self.dataReceived as Data)
self.dataReceived.length = 0
}
completionHandler(dR, inputStream.streamStatus == .AtEnd, inputStream.streamError)
completionHandler(dR as Data, inputStream.streamStatus == .atEnd, inputStream.streamError as NSError?)
}
}
}
@@ -272,32 +268,32 @@ public class FPSStreamTask: NSURLSessionTask, NSStreamDelegate {
* occur. Note that invocation of the completion handler does not
* guarantee that the remote side has received all the bytes, only
* that they have been written to the kernel. */
public func writeData(data: NSData, timeout: NSTimeInterval, completionHandler: (NSError?) -> Void) {
open func writeData(_ data: Data, timeout: TimeInterval, completionHandler: @escaping (Error?) -> Void) {
if #available(iOS 9.0, OSX 10.11, *) {
_underlyingTask!.writeData(data, timeout: timeout, completionHandler: completionHandler)
_underlyingTask!.write(data, timeout: timeout, completionHandler: completionHandler)
} else {
guard let outputStream = outputStream else {
return
}
var timedOut: Bool = false
dispatch_async(dispatch_queue) {
dispatch_queue.async {
if timeout > 0 {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(timeout * 1_000_000_000)), self.dispatch_queue, {
self.dispatch_queue.asyncAfter(deadline: DispatchTime.now() + Double(Int64(timeout * 1_000_000_000)) / Double(NSEC_PER_SEC), execute: {
timedOut = true
completionHandler(self._error)
})
}
self.dataToBeSent.appendData(data)
self.dataToBeSent.append(data)
while !outputStream.hasSpaceAvailable && !timedOut {
NSRunLoop.currentRunLoop().runUntilDate(NSDate(timeIntervalSinceNow: 0.1));
NSThread.sleepForTimeInterval(0.1)
RunLoop.current.run(until: Date(timeIntervalSinceNow: 0.1));
Thread.sleep(forTimeInterval: 0.1)
}
if self.dataToBeSent.length > 0 {
let bytesWritten = outputStream.write(UnsafePointer(self.dataToBeSent.bytes), maxLength: self.dataToBeSent.length) ?? -1
let bytesWritten = outputStream.write(self.dataToBeSent.bytes.bindMemory(to: UInt8.self, capacity: self.dataToBeSent.length), maxLength: self.dataToBeSent.length)
if bytesWritten > 0 {
let range = NSRange(location: 0, length: bytesWritten)
self.dataToBeSent.replaceBytesInRange(range, withBytes: nil, length: 0)
self.dataToBeSent.replaceBytes(in: range, withBytes: nil, length: 0)
self._countOfBytesSent += bytesWritten
completionHandler(nil)
} else {
@@ -315,20 +311,20 @@ public class FPSStreamTask: NSURLSessionTask, NSStreamDelegate {
* message. When that message is received, the task object is
* considered completed and will not receive any more delegate
* messages. */
public func captureStreams() {
open func captureStreams() {
if #available(iOS 9.0, OSX 10.11, *) {
_underlyingTask!.captureStreams()
} else {
guard let outputStream = outputStream, let inputStream = inputStream else {
return
}
dispatch_async(dispatch_queue) {
dispatch_queue.async {
self.write(false)
while inputStream.streamStatus != .AtEnd {
NSRunLoop.currentRunLoop().runUntilDate(NSDate(timeIntervalSinceNow: 0.1));
NSThread.sleepForTimeInterval(0.1)
while inputStream.streamStatus != .atEnd {
RunLoop.current.run(until: Date(timeIntervalSinceNow: 0.1));
Thread.sleep(forTimeInterval: 0.1)
}
self.streamDelegate?.URLSession?(self._underlyingSession, streamTask: self, didBecomeInputStream: inputStream, outputStream: outputStream)
self.streamDelegate?.urlSession?(self._underlyingSession, streamTask: self, didBecome: inputStream, outputStream: outputStream)
}
}
}
@@ -339,38 +335,38 @@ public class FPSStreamTask: NSURLSessionTask, NSStreamDelegate {
* back to the client, so best practice is to continue reading from
* the server until you receive EOF.
*/
public func closeWrite() {
open func closeWrite() {
if #available(iOS 9.0, OSX 10.11, *) {
_underlyingTask!.closeWrite()
} else {
dispatch_async(dispatch_queue, {
dispatch_queue.async(execute: {
self.write(true)
})
}
}
private func write(close: Bool) {
fileprivate func write(_ close: Bool) {
guard let outputStream = outputStream else {
return
}
while self.dataToBeSent.length > 0 {
let bytesWritten = outputStream.write(UnsafePointer(self.dataToBeSent.bytes), maxLength: self.dataToBeSent.length) ?? -1
let bytesWritten = outputStream.write(self.dataToBeSent.bytes.bindMemory(to: UInt8.self, capacity: self.dataToBeSent.length), maxLength: self.dataToBeSent.length)
if bytesWritten > 0 {
let range = NSRange(location: 0, length: bytesWritten)
self.dataToBeSent.replaceBytesInRange(range, withBytes: nil, length: 0)
self.dataToBeSent.replaceBytes(in: range, withBytes: nil, length: 0)
self._countOfBytesSent += bytesWritten
} else {
self._error = outputStream.streamError
self._error = outputStream.streamError as NSError?
}
if self.dataToBeSent.length == 0 {
break
}
NSRunLoop.currentRunLoop().runUntilDate(NSDate(timeIntervalSinceNow: 0.1));
NSThread.sleepForTimeInterval(0.1)
RunLoop.current.run(until: Date(timeIntervalSinceNow: 0.1));
Thread.sleep(forTimeInterval: 0.1)
}
if close {
outputStream.close()
self.streamDelegate?.URLSession?(self._underlyingSession, writeClosedForStreamTask: self)
self.streamDelegate?.urlSession?(self._underlyingSession, writeClosedFor: self)
}
}
@@ -378,20 +374,20 @@ public class FPSStreamTask: NSURLSessionTask, NSStreamDelegate {
* All outstanding IO will complete before the read side is closed.
* You may continue writing to the server.
*/
public func closeRead() {
open func closeRead() {
if #available(iOS 9.0, OSX 10.11, *) {
_underlyingTask!.closeRead()
} else {
guard let inputStream = inputStream else {
return
}
dispatch_async(dispatch_queue) {
while inputStream.streamStatus != .AtEnd {
NSRunLoop.currentRunLoop().runUntilDate(NSDate(timeIntervalSinceNow: 0.1));
NSThread.sleepForTimeInterval(0.1)
dispatch_queue.async {
while inputStream.streamStatus != .atEnd {
RunLoop.current.run(until: Date(timeIntervalSinceNow: 0.1));
Thread.sleep(forTimeInterval: 0.1)
}
inputStream.close()
self.streamDelegate?.URLSession?(self._underlyingSession, readClosedForStreamTask: self)
self.streamDelegate?.urlSession?(self._underlyingSession, readClosedFor: self)
}
}
}
@@ -401,12 +397,12 @@ public class FPSStreamTask: NSURLSessionTask, NSStreamDelegate {
* IO has completed. TLS authentication callbacks are sent to the
* session's -URLSession:task:didReceiveChallenge:completionHandler:
*/
public func startSecureConnection() {
open func startSecureConnection() {
if #available(iOS 9.0, OSX 10.11, *) {
_underlyingTask!.startSecureConnection()
} else {
inputStream!.setProperty(NSStreamSocketSecurityLevelNegotiatedSSL, forKey: NSStreamSocketSecurityLevelKey)
outputStream!.setProperty(NSStreamSocketSecurityLevelNegotiatedSSL, forKey: NSStreamSocketSecurityLevelKey)
inputStream!.setProperty(StreamSocketSecurityLevel.negotiatedSSL.rawValue, forKey: Stream.PropertyKey.socketSecurityLevelKey)
outputStream!.setProperty(StreamSocketSecurityLevel.negotiatedSSL.rawValue, forKey: Stream.PropertyKey.socketSecurityLevelKey)
}
}
@@ -414,38 +410,38 @@ public class FPSStreamTask: NSURLSessionTask, NSStreamDelegate {
* Cleanly close a secure connection after all pending secure IO has
* completed.
*/
public func stopSecureConnection() {
open func stopSecureConnection() {
if #available(iOS 9.0, OSX 10.11, *) {
_underlyingTask!.stopSecureConnection()
} else {
inputStream!.setProperty(NSStreamSocketSecurityLevelNone, forKey: NSStreamSocketSecurityLevelKey)
outputStream!.setProperty(NSStreamSocketSecurityLevelNone, forKey: NSStreamSocketSecurityLevelKey)
inputStream!.setProperty(StreamSocketSecurityLevel.none.rawValue, forKey: Stream.PropertyKey.socketSecurityLevelKey)
outputStream!.setProperty(StreamSocketSecurityLevel.none.rawValue, forKey: Stream.PropertyKey.socketSecurityLevelKey)
}
}
public func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent) {
open func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
switch (eventCode) {
case NSStreamEvent.ErrorOccurred:
self._error = aStream.streamError
streamDelegate?.URLSession?(_underlyingSession, task: self, didCompleteWithError: error)
case NSStreamEvent.EndEncountered:
case Stream.Event.errorOccurred:
self._error = aStream.streamError as NSError?
streamDelegate?.urlSession?(_underlyingSession, task: self, didCompleteWithError: error)
case Stream.Event.endEncountered:
break
case NSStreamEvent.None:
case Stream.Event():
break
case NSStreamEvent.OpenCompleted:
case Stream.Event.openCompleted:
break
case NSStreamEvent.HasBytesAvailable:
var buffer = [UInt8](count: 2048, repeatedValue: 0)
case Stream.Event.hasBytesAvailable:
var buffer = [UInt8](repeating: 0, count: 2048)
if (aStream == inputStream) {
while (inputStream!.hasBytesAvailable ?? false) {
while (inputStream!.hasBytesAvailable) {
let len = inputStream!.read(&buffer, maxLength: buffer.count)
if len > 0 {
dataReceived.appendBytes(&buffer, length: len)
dataReceived.append(&buffer, length: len)
self._countOfBytesRecieved += len
}
}
}
case NSStreamEvent.HasSpaceAvailable:
case Stream.Event.hasSpaceAvailable:
break
default:
break
@@ -453,23 +449,24 @@ public class FPSStreamTask: NSURLSessionTask, NSStreamDelegate {
}
}
extension NSURLSession {
extension URLSession {
/* Creates a bidirectional stream task to a given host and port.
*/
public func fpstreamTaskWithHostName(hostname: String, port: Int) -> FPSStreamTask {
public func fpstreamTaskWithHostName(_ hostname: String, port: Int) -> FPSStreamTask {
return FPSStreamTask(session: self, host: hostname, port: port)
}
/* Creates a bidirectional stream task with an NSNetService to identify the endpoint.
* The NSNetService will be resolved before any IO completes.
*/
public func fpstreamTaskWithNetService(service: NSNetService) -> FPSStreamTask {
public func fpstreamTaskWithNetService(_ service: NetService) -> FPSStreamTask {
return fpstreamTaskWithNetService(service)
}
}
@objc
public protocol FPSStreamDelegate : NSURLSessionTaskDelegate {
public protocol FPSStreamDelegate : URLSessionTaskDelegate {
/* Indiciates that the read side of a connection has been closed. Any
* outstanding reads complete, but future reads will immediately fail.
@@ -477,13 +474,15 @@ public protocol FPSStreamDelegate : NSURLSessionTaskDelegate {
* this delegate message is received, there may still be bytes
* available. You only know that no more bytes are available when you
* are able to read until EOF. */
optional func URLSession(session: NSURLSession, readClosedForStreamTask streamTask: FPSStreamTask)
@objc optional func urlSession(_ session: URLSession, readClosedFor streamTask: FPSStreamTask)
/* Indiciates that the write side of a connection has been closed.
* Any outstanding writes complete, but future writes will immediately
* fail.
*/
optional func URLSession(session: NSURLSession, writeClosedForStreamTask streamTask: FPSStreamTask)
@objc optional func urlSession(_ session: URLSession, writeClosedFor streamTask: FPSStreamTask)
/* A notification that the system has determined that a better route
* to the host has been detected (eg, a wi-fi interface becoming
@@ -492,7 +491,8 @@ public protocol FPSStreamDelegate : NSURLSessionTaskDelegate {
* there is no guarantee that the future task will be able to connect
* to the host, so callers should should be prepared for failure of
* reads and writes over any new interface. */
optional func URLSession(session: NSURLSession, betterRouteDiscoveredForStreamTask streamTask: FPSStreamTask)
@objc optional func urlSession(_ session: URLSession, betterRouteDiscoveredFor streamTask: FPSStreamTask)
/* The given task has been completed, and unopened NSInputStream and
* NSOutputStream objects are created from the underlying network
@@ -500,7 +500,7 @@ public protocol FPSStreamDelegate : NSURLSessionTaskDelegate {
* completed (including any necessary handshakes.) The streamTask
* will not receive any further delegate messages.
*/
optional func URLSession(session: NSURLSession, streamTask: FPSStreamTask, didBecomeInputStream inputStream: NSInputStream, outputStream: NSOutputStream)
@objc optional func urlSession(_ session: URLSession, streamTask: FPSStreamTask, didBecome inputStream: InputStream, outputStream: OutputStream)
}
private let ports = ["http": 80,
@@ -520,4 +520,4 @@ private let securePorts = ["https": 443,
"telnet": 992,
"pop": 995,
"smtp": 465,
"imap": 993]
"imap": 993]
+457 -333
View File
@@ -15,40 +15,456 @@ 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 typealias SimpleCompletionHandler = ((_ error: Error?) -> Void)?
public protocol FileProviderBasic: class {
static var type: String { get }
var isPathRelative: Bool { get }
var baseURL: URL? { get }
var currentPath: String { get set }
var dispatch_queue: DispatchQueue { get set }
var delegate: FileProviderDelegate? { get set }
var credential: URLCredential? { get }
public init(urlResourceTypeValue: String) {
switch urlResourceTypeValue {
case NSURLFileResourceTypeNamedPipe: self = .NamedPipe
case NSURLFileResourceTypeCharacterSpecial: self = .CharacterSpecial
case NSURLFileResourceTypeDirectory: self = .Directory
case NSURLFileResourceTypeBlockSpecial: self = .BlockSpecial
case NSURLFileResourceTypeRegular: self = .Regular
case NSURLFileResourceTypeSymbolicLink: self = .SymbolicLink
case NSURLFileResourceTypeSocket: self = .Socket
case NSURLFileResourceTypeUnknown: self = .Unknown
default: self = .Unknown
/**
*
*/
func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void))
func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void))
func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void))
}
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 }
@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?
@discardableResult
func copyItem(localFile: URL, to: String, completionHandler: SimpleCompletionHandler) -> OperationHandle?
@discardableResult
func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) -> OperationHandle?
}
public protocol FileProviderReadWrite: FileProviderBasic {
@discardableResult
func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle?
@discardableResult
func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle?
@discardableResult
func writeContents(path: String, contents: Data, atomically: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle?
func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void))
}
public protocol FileProviderMonitor: FileProviderBasic {
func registerNotifcation(path: String, eventHandler: @escaping (() -> Void))
func unregisterNotifcation(path: String)
func isRegisteredForNotification(path: String) -> Bool
}
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: ". /"))
}
public func absoluteURL(_ path: String? = nil) -> URL {
let rpath: String
if let path = path {
rpath = path
} else {
rpath = self.currentPath
}
if isPathRelative, let baseURL = baseURL {
if rpath.hasPrefix("/") && baseURL.absoluteString.hasSuffix("/") {
var npath = rpath
npath.remove(at: npath.startIndex)
return baseURL.appendingPathComponent(npath)
} else {
return baseURL.appendingPathComponent(rpath)
}
} else {
return URL(fileURLWithPath: rpath).standardizedFileURL
}
}
public init(fileTypeValue: String) {
public func relativePathOf(url: URL) -> String {
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.endIndex)
}
return p
}
public func fileByUniqueName(_ filePath: String) -> String {
let fileUrl = URL(fileURLWithPath: filePath)
let dirPath = fileUrl.deletingLastPathComponent().path
let fileName = fileUrl.deletingPathExtension().lastPathComponent
let fileExt = fileUrl.pathExtension
var result = fileName
let group = DispatchGroup()
group.enter()
self.contentsOfDirectory(path: dirPath) { (contents, error) in
var bareFileName = fileName
let number = Int(fileName.components(separatedBy: " ").filter {
!$0.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).isEmpty
}.last ?? "noname")
if let _ = number {
result = fileName.components(separatedBy: " ").filter {
!$0.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).isEmpty
}.dropLast().joined(separator: " ")
bareFileName = result
}
var i = number ?? 2
let similiar = contents.map {
$0.absoluteURL?.lastPathComponent ?? $0.name
}.filter {
$0.hasPrefix(result)
}
while similiar.contains(result + (!fileExt.isEmpty ? "." + fileExt : "")) {
result = "\(bareFileName) \(i)"
i += 1
}
group.leave()
}
_ = group.wait(timeout: DispatchTime.distantFuture)
let finalFile = result + (!fileExt.isEmpty ? "." + fileExt : "")
return (dirPath as NSString).appendingPathComponent(finalFile)
}
internal func throwError(_ path: String, code: FoundationErrorEnum) -> NSError {
let fileURL = self.absoluteURL(path)
let domain: String
switch code {
case is URLError:
domain = NSURLErrorDomain
default:
domain = NSCocoaErrorDomain
}
return NSError(domain: domain, code: code.rawValue, userInfo: [NSURLErrorFailingURLErrorKey: fileURL, NSURLErrorFailingURLStringErrorKey: fileURL.absoluteString])
}
internal func NotImplemented() {
assert(false, "method not implemented")
}
internal func resolve(dateString: String) -> Date? {
let dateFor: DateFormatter = DateFormatter()
dateFor.locale = Locale(identifier: "en_US")
dateFor.dateFormat = "EEE',' dd' 'MMM' 'yyyy HH':'mm':'ss zzz"
if let rfc1123 = dateFor.date(from: dateString) {
return rfc1123
}
dateFor.dateFormat = "EEEE',' dd'-'MMM'-'yy HH':'mm':'ss z"
if let rfc850 = dateFor.date(from: dateString) {
return rfc850
}
dateFor.dateFormat = "EEE MMM d HH':'mm':'ss yyyy"
if let asctime = dateFor.date(from: dateString) {
return asctime
}
dateFor.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssz"
if let isotime = dateFor.date(from: dateString) {
return isotime
}
return nil
}
}
public protocol ExtendedFileProvider: FileProvider {
func thumbnailOfFileSupported(path: String) -> Bool
func propertiesOfFileSupported(path: String) -> Bool
func thumbnailOfFile(path: String, dimension: CGSize, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void))
func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String: Any], _ keys: [String], _ error: Error?) -> Void))
}
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: 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"
}
}
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 {
return allValues[URLResourceKey.fileResourceTypeKey.rawValue] as? URLFileResourceType
}
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: 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: 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: FileOperationType) -> Bool
}
// THESE ARE METHODS TO PROVIDE COMPATIBILITY WITH SWIFT 2.3 SIMOULTANIOUSLY!
internal extension URL {
var uw_scheme: String {
return self.scheme ?? ""
}
}
internal class Weak<T: AnyObject> {
weak var value : T?
init (_ value: T) {
self.value = value
}
}
extension URLFileResourceType {
public init(fileTypeValue: FileAttributeType) {
switch fileTypeValue {
case NSFileTypeCharacterSpecial: self = .CharacterSpecial
case NSFileTypeDirectory: self = .Directory
case NSFileTypeBlockSpecial: self = .BlockSpecial
case NSFileTypeRegular: self = .Regular
case NSFileTypeSymbolicLink: self = .SymbolicLink
case NSFileTypeSocket: self = .Socket
case NSFileTypeUnknown: self = .Unknown
default: self = .Unknown
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
}
}
}
@@ -58,314 +474,22 @@ public protocol FoundationErrorEnum {
var rawValue: Int { get }
}
extension NSURLError: FoundationErrorEnum {}
extension NSCocoaError: FoundationErrorEnum {}
extension URLError.Code: FoundationErrorEnum {}
extension CocoaError.Code: FoundationErrorEnum {}
public class FileObject {
public let absoluteURL: NSURL?
public let name: String
public let path: String
public let size: Int64
public let createdDate: NSDate?
public let modifiedDate: NSDate?
public let fileType: FileType
public let isHidden: Bool
public let isReadOnly: Bool
public init(absoluteURL: NSURL? = nil, name: String, path: String, size: Int64 = -1, createdDate: NSDate? = nil, modifiedDate: NSDate? = 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
}
public var isDirectory: Bool {
return self.fileType == .Directory
}
public var isSymLink: Bool {
return self.fileType == .SymbolicLink
}
}
public typealias SimpleCompletionHandler = ((error: ErrorType?) -> Void)?
public protocol FileProviderBasic: class {
var type: String { get }
var isPathRelative: Bool { get }
var baseURL: NSURL? { get }
var currentPath: String { get set }
var dispatch_queue: dispatch_queue_t { get set }
var delegate: FileProviderDelegate? { get set }
var credential: NSURLCredential? { get }
/**
*
*/
func contentsOfDirectoryAtPath(path: String, completionHandler: ((contents: [FileObject], error: ErrorType?) -> Void))
func attributesOfItemAtPath(path: String, completionHandler: ((attributes: FileObject?, error: ErrorType?) -> Void))
func storageProperties(completionHandler: ((total: Int64, used: Int64) -> Void))
}
public protocol FileProviderOperations: FileProviderBasic {
var fileOperationDelegate : FileOperationDelegate? { get set }
func createFolder(folderName: String, atPath: String, completionHandler: SimpleCompletionHandler)
func createFile(fileAttribs: FileObject, atPath: String, contents data: NSData?, completionHandler: SimpleCompletionHandler)
func moveItemAtPath(path: String, toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler)
func copyItemAtPath(path: String, toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler)
func removeItemAtPath(path: String, completionHandler: SimpleCompletionHandler)
func copyLocalFileToPath(localFile: NSURL, toPath: String, completionHandler: SimpleCompletionHandler)
func copyPathToLocalFile(path: String, toLocalURL: NSURL, completionHandler: SimpleCompletionHandler)
}
public protocol FileProviderReadWrite: FileProviderBasic {
func contentsAtPath(path: String, completionHandler: ((contents: NSData?, error: ErrorType?) -> Void))
func contentsAtPath(path: String, offset: Int64, length: Int, completionHandler: ((contents: NSData?, error: ErrorType?) -> Void))
func writeContentsAtPath(path: String, contents data: NSData, atomically: Bool, completionHandler: SimpleCompletionHandler)
func searchFilesAtPath(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: ((files: [FileObject], error: ErrorType?) -> Void))
}
public protocol FileProviderMonitor: FileProviderBasic {
func registerNotifcation(path: String, eventHandler: (() -> Void))
func unregisterNotifcation(path: String)
func isRegisteredForNotification(path: String) -> Bool
}
public protocol FileProvider: FileProviderBasic, FileProviderOperations, FileProviderReadWrite {
}
extension FileProviderBasic {
public var bareCurrentPath: String {
return currentPath.stringByTrimmingCharactersInSet(NSCharacterSet(charactersInString: ". /"))
}
public func absoluteURL(path: String? = nil) -> NSURL {
let rpath: String
if let path = path {
rpath = path
} else {
rpath = self.currentPath
}
if isPathRelative, let baseURL = baseURL {
if rpath.hasPrefix("/") && baseURL.uw_absoluteString.hasSuffix("/") {
var npath = rpath
npath.removeAtIndex(npath.startIndex)
return baseURL.uw_URLByAppendingPathComponent(npath)
} else {
return baseURL.uw_URLByAppendingPathComponent(rpath)
}
} else {
return NSURL(fileURLWithPath: rpath).URLByStandardizingPath!
}
}
public func relativePathOf(url url: NSURL) -> String {
guard let baseURL = self.baseURL else { return url.uw_absoluteString }
return url.URLByStandardizingPath!.uw_absoluteString.stringByReplacingOccurrencesOfString(baseURL.uw_absoluteString, withString: "/").stringByRemovingPercentEncoding!
}
internal func correctPath(path: String?) -> String? {
guard let path = path else { return nil }
var p = path.hasPrefix("/") ? path : "/" + path
if p.hasSuffix("/") {
p.removeAtIndex(p.endIndex.predecessor())
}
return p
}
public func fileByUniqueName(filePath: String) -> String {
let fileUrl = NSURL(fileURLWithPath: filePath)
let dirPath = fileUrl.URLByDeletingLastPathComponent?.path ?? ""
guard let fileName = fileUrl.URLByDeletingPathExtension?.lastPathComponent else {
return filePath
}
let fileExt = fileUrl.pathExtension ?? ""
var result = fileName
let group = dispatch_group_create()
dispatch_group_enter(group)
self.contentsOfDirectoryAtPath(dirPath) { (contents, error) in
var bareFileName = fileName
let number = Int(fileName.componentsSeparatedByString(" ").filter {
!$0.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()).isEmpty
}.last ?? "noname")
if let _ = number {
result = fileName.componentsSeparatedByString(" ").filter {
!$0.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()).isEmpty
}.dropLast().joinWithSeparator(" ")
bareFileName = result
}
var i = number ?? 2
let similiar = contents.map {
$0.absoluteURL?.lastPathComponent ?? $0.name
}.filter {
$0.hasPrefix(result) && (!fileExt.isEmpty && $0.hasSuffix("." + fileExt))
}
while similiar.contains(result + (!fileExt.isEmpty ? "." + fileExt : "")) {
result = "\(bareFileName) \(i)"
i += 1
}
dispatch_group_leave(group)
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
let finalFile = result + (!fileExt.isEmpty ? "." + fileExt : "")
return (dirPath as NSString).stringByAppendingPathComponent(finalFile)
}
internal func throwError(path: String, code: FoundationErrorEnum) -> NSError {
let fileURL = self.absoluteURL(path)
let domain: String
switch code {
case is NSURLError:
domain = NSURLErrorDomain
default:
domain = NSCocoaErrorDomain
}
return NSError(domain: domain, code: code.rawValue, userInfo: [NSURLErrorFailingURLErrorKey: fileURL, NSURLErrorFailingURLStringErrorKey: fileURL.uw_absoluteString])
}
internal func NotImplemented() {
assert(false, "method not implemented")
}
internal func resolveDate(dateString: String) -> NSDate? {
let dateFor: NSDateFormatter = NSDateFormatter()
dateFor.locale = NSLocale(localeIdentifier: "en_US")
dateFor.dateFormat = "EEE',' dd' 'MMM' 'yyyy HH':'mm':'ss zzz"
if let rfc1123 = dateFor.dateFromString(dateString) {
return rfc1123
}
dateFor.dateFormat = "EEEE',' dd'-'MMM'-'yy HH':'mm':'ss z"
if let rfc850 = dateFor.dateFromString(dateString) {
return rfc850
}
dateFor.dateFormat = "EEE MMM d HH':'mm':'ss yyyy"
if let asctime = dateFor.dateFromString(dateString) {
return asctime
}
dateFor.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssz"
if let isotime = dateFor.dateFromString(dateString) {
return isotime
}
//self.init()
internal func jsonToDictionary(_ jsonString: String) -> [String: AnyObject]? {
guard let data = jsonString.data(using: .utf8) else {
return nil
}
internal func jsonToDictionary(jsonString: String) -> [String: AnyObject]? {
guard let data = jsonString.dataUsingEncoding(NSUTF8StringEncoding) else {
return nil
}
if let dic = try? NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) as? [String: AnyObject] {
return dic
}
return nil
}
internal func dictionaryToJSON(dictionary: [String: AnyObject]) -> String? {
if let data = try? NSJSONSerialization.dataWithJSONObject(dictionary, options: NSJSONWritingOptions()) {
return String(data: data, encoding: NSUTF8StringEncoding)
}
return nil
if let dic = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions()) as? [String: AnyObject] {
return dic
}
return nil
}
public protocol ExtendedFileProvider: FileProvider {
func thumbnailOfFileSupported(path: String) -> Bool
func propertiesOfFileSupported(path: String) -> Bool
func thumbnailOfFileAtPath(path: String, dimension: CGSize, completionHandler: ((image: ImageClass?, error: ErrorType?) -> Void))
func propertiesOfFileAtPath(path: String, completionHandler: ((propertiesDictionary: [String: AnyObject], keys: [String], error: ErrorType?) -> Void))
}
public enum FileOperation: 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)
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"
}
}
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 protocol FileProviderDelegate: class {
func fileproviderSucceed(fileProvider: FileProviderOperations, operation: FileOperation)
func fileproviderFailed(fileProvider: FileProviderOperations, operation: FileOperation)
func fileproviderProgress(fileProvider: FileProviderOperations, operation: FileOperation, 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
/// 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: ErrorType, operation: FileOperation) -> Bool
}
// THESE ARE METHODS TO PROVIDE COMPATIBILITY WITH SWIFT 2.3 SIMOULTANIOUSLY!
internal extension NSURL {
var uw_scheme: String {
#if swift(>=2.3)
return self.scheme ?? ""
#else
return self.scheme
#endif
}
var uw_absoluteString: String {
#if swift(>=2.3)
return self.absoluteString ?? ""
#else
return self.absoluteString
#endif
}
func uw_URLByAppendingPathComponent(pathComponent: String) -> NSURL {
#if swift(>=2.3)
return self.URLByAppendingPathComponent(pathComponent)!
#else
return self.URLByAppendingPathComponent(pathComponent)
#endif
}
func uw_URLByAppendingPathExtension(pathExtension: String) -> NSURL {
#if swift(>=2.3)
return self.URLByAppendingPathExtension(pathExtension)!
#else
return self.URLByAppendingPathExtension(pathExtension)
#endif
}
internal func dictionaryToJSON(_ dictionary: [String: AnyObject]) -> String? {
if let data = try? JSONSerialization.data(withJSONObject: dictionary, options: JSONSerialization.WritingOptions()) {
return String(data: data, encoding: .utf8)
}
return nil
}
+197 -350
View File
@@ -8,315 +8,291 @@
import Foundation
public final class LocalFileObject: FileObject {
public let allocatedSize: Int64
public init(absoluteURL: NSURL, name: String, path: String, size: Int64, allocatedSize: Int64, createdDate: NSDate?, modifiedDate: NSDate?, fileType: FileType, isHidden: Bool, isReadOnly: Bool) {
self.allocatedSize = allocatedSize
super.init(absoluteURL: absoluteURL, name: name, path: path, size: size, createdDate: createdDate, modifiedDate: modifiedDate, fileType: fileType, isHidden: isHidden, isReadOnly: isReadOnly)
}
}
public class LocalFileProvider: FileProvider, FileProviderMonitor {
public let type = "Local"
public var isPathRelative: Bool = true
public var baseURL: NSURL? = LocalFileProvider.defaultBaseURL()
public var currentPath: String = ""
public var dispatch_queue: dispatch_queue_t
public var operation_queue: dispatch_queue_t
public weak var delegate: FileProviderDelegate?
public let credential: NSURLCredential? = nil
open class LocalFileProvider: FileProvider, FileProviderMonitor {
open static let type = "Local"
open var isPathRelative: Bool = true
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
public let fileManager = NSFileManager()
public let opFileManager = NSFileManager()
private var fileProviderManagerDelegate: LocalFileProviderManagerDelegate? = nil
open private(set) var fileManager = FileManager()
open private(set) var opFileManager = FileManager()
fileprivate var fileProviderManagerDelegate: LocalFileProviderManagerDelegate? = nil
public init () {
dispatch_queue = dispatch_queue_create("FileProvider.\(type)", DISPATCH_QUEUE_CONCURRENT)
operation_queue = dispatch_queue_create("FileProvider.\(type).Operation", DISPATCH_QUEUE_SERIAL)
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: NSURL) {
public init (baseURL: URL) {
self.baseURL = baseURL
dispatch_queue = dispatch_queue_create("FileProvider.\(type)", DISPATCH_QUEUE_CONCURRENT)
operation_queue = dispatch_queue_create("FileProvider.\(type).Operation", DISPATCH_QUEUE_SERIAL)
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
}
private static func defaultBaseURL() -> NSURL {
let paths = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true);
return NSURL(fileURLWithPath: paths[0])
open static func defaultBaseURL() -> URL {
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true);
return URL(fileURLWithPath: paths[0])
}
public func contentsOfDirectoryAtPath(path: String, completionHandler: ((contents: [FileObject], error: ErrorType?) -> Void)) {
dispatch_async(dispatch_queue) {
open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {
dispatch_queue.async {
do {
let contents = try self.fileManager.contentsOfDirectoryAtURL(self.absoluteURL(path), includingPropertiesForKeys: [NSURLNameKey, NSURLFileSizeKey, NSURLFileAllocatedSizeKey, NSURLCreationDateKey, NSURLContentModificationDateKey, NSURLIsHiddenKey, NSURLVolumeIsReadOnlyKey, NSFileGroupOwnerAccountName], options: NSDirectoryEnumerationOptions.SkipsSubdirectoryDescendants)
let filesAttributes = contents.map({ (fileURL) -> LocalFileObject in
return self.attributesOfItemAtURL(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(contents: filesAttributes, error: nil)
completionHandler(filesAttributes, nil)
} catch let e as NSError {
completionHandler(contents: [], error: e)
completionHandler([], e)
}
}
}
internal func attributesOfItemAtURL(fileURL: NSURL) -> LocalFileObject {
var namev, sizev, allocated, filetypev, creationDatev, modifiedDatev, hiddenv, readonlyv: AnyObject?
_ = try? fileURL.getResourceValue(&namev, forKey: NSURLNameKey)
_ = try? fileURL.getResourceValue(&sizev, forKey: NSURLFileSizeKey)
_ = try? fileURL.getResourceValue(&allocated, forKey: NSURLFileAllocatedSizeKey)
_ = try? fileURL.getResourceValue(&creationDatev, forKey: NSURLCreationDateKey)
_ = try? fileURL.getResourceValue(&modifiedDatev, forKey: NSURLContentModificationDateKey)
_ = try? fileURL.getResourceValue(&filetypev, forKey: NSURLFileResourceTypeKey)
_ = try? fileURL.getResourceValue(&hiddenv, forKey: NSURLIsHiddenKey)
_ = try? fileURL.getResourceValue(&readonlyv, forKey: NSURLVolumeIsReadOnlyKey)
let path: String
if isPathRelative {
path = self.relativePathOf(url: fileURL)
} else {
path = fileURL.path!
}
let fileAttr = LocalFileObject(absoluteURL: fileURL, name: namev as! String, path: path, size: sizev?.longLongValue ?? -1, allocatedSize: allocated?.longLongValue ?? -1, createdDate: creationDatev as? NSDate, modifiedDate: modifiedDatev as? NSDate, fileType: FileType(urlResourceTypeValue: filetypev as? String ?? ""), 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?[.systemSize] as? NSNumber)?.int64Value ?? -1;
let freeSize = (dict?[.systemFreeSize] as? NSNumber)?.int64Value ?? 0;
completionHandler(totalSize, totalSize - freeSize)
}
public func storageProperties(completionHandler: ((total: Int64, used: Int64) -> Void)) {
let dict = (try? NSFileManager.defaultManager().attributesOfFileSystemForPath(baseURL?.path ?? "/")) as NSDictionary?;
let totalSize = dict?.objectForKey(NSFileSystemSize)?.longLongValue ?? -1;
let freeSize = dict?.objectForKey(NSFileSystemFreeSize)?.longLongValue ?? 0;
completionHandler(total: totalSize, used: totalSize - freeSize)
}
public func attributesOfItemAtPath(path: String, completionHandler: ((attributes: FileObject?, error: ErrorType?) -> Void)) {
dispatch_async(dispatch_queue) {
completionHandler(attributes: self.attributesOfItemAtURL(self.absoluteURL(path)), error: nil)
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
dispatch_queue.async {
completionHandler(LocalFileObject(fileWithPath: path, relativeTo: self.baseURL), nil)
}
}
public weak var fileOperationDelegate : FileOperationDelegate?
open weak var fileOperationDelegate : FileOperationDelegate?
public func createFolder(folderName: String, atPath: String, completionHandler: SimpleCompletionHandler) {
dispatch_async(operation_queue) {
@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.createDirectoryAtURL(self.absoluteURL(atPath).uw_URLByAppendingPathComponent(folderName), withIntermediateDirectories: true, attributes: [:])
completionHandler?(error: nil)
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderSucceed(self, operation: .Create(path: (atPath as NSString).stringByAppendingPathComponent(folderName) + "/"))
try self.opFileManager.createDirectory(at: self.absoluteURL(atPath).appendingPathComponent(folderName), withIntermediateDirectories: true, attributes: [:])
completionHandler?(nil)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderSucceed(self, operation: opType)
})
} catch let e as NSError {
completionHandler?(error: e)
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderFailed(self, operation: .Create(path: (atPath as NSString).stringByAppendingPathComponent(folderName) + "/"))
completionHandler?(e)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderFailed(self, operation: opType)
})
}
}
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
}
public func createFile(fileAttribs: FileObject, atPath: String, contents data: NSData?, completionHandler: SimpleCompletionHandler) {
dispatch_async(operation_queue) {
let fileURL = self.absoluteURL(atPath).uw_URLByAppendingPathComponent(fileAttribs.name)
var attributes = [String : AnyObject]()
if let createdDate = fileAttribs.createdDate {
attributes[NSFileCreationDate] = createdDate
}
if let modDate = fileAttribs.modifiedDate {
attributes[NSFileModificationDate] = modDate
}
if fileAttribs.isReadOnly {
attributes[NSFilePosixPermissions] = NSNumber(short: 365 /*555 o*/)
}
let success = self.opFileManager.createFileAtPath(fileURL.path!, contents: data, attributes: attributes)
@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).appendingPathComponent(fileName)
let success = self.opFileManager.createFile(atPath: fileURL.path, contents: data, attributes: nil)
if success {
do {
try fileURL.setResourceValue(fileAttribs.isHidden, forKey: NSURLIsHiddenKey)
} catch _ {}
completionHandler?(error: nil)
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderSucceed(self, operation: .Create(path: (atPath as NSString).stringByAppendingPathComponent(fileAttribs.name)))
completionHandler?(nil)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderSucceed(self, operation: opType)
})
} else {
completionHandler?(error: self.throwError(atPath, code: NSURLError.CannotCreateFile))
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderFailed(self, operation: .Create(path: (atPath as NSString).stringByAppendingPathComponent(fileAttribs.name)))
completionHandler?(self.throwError(atPath, code: URLError.cannotCreateFile as FoundationErrorEnum))
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderFailed(self, operation: opType)
})
}
}
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
}
public func moveItemAtPath(path: String, toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
// FIXME: progress
dispatch_async(operation_queue) {
if !overwrite && self.fileManager.fileExistsAtPath(self.absoluteURL(toPath).path ?? "") {
completionHandler?(error: self.throwError(toPath, code: NSURLError.CannotMoveFile))
@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))
return
}
do {
try self.opFileManager.moveItemAtURL(self.absoluteURL(path), toURL: self.absoluteURL(toPath))
completionHandler?(error: nil)
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderSucceed(self, operation: .Move(source: path, destination: toPath))
try self.opFileManager.moveItem(at: self.absoluteURL(path), to: self.absoluteURL(toPath))
completionHandler?(nil)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderSucceed(self, operation: opType)
})
} catch let e as NSError {
completionHandler?(error: e)
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderFailed(self, operation: .Move(source: path, destination: toPath))
completionHandler?(e)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderFailed(self, operation: opType)
})
}
}
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
}
public func copyItemAtPath(path: String, toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
// FIXME: progress, for files > 100mb, monitor file by another thread, for dirs check copied items count
dispatch_async(operation_queue) {
if !overwrite && self.fileManager.fileExistsAtPath(self.absoluteURL(toPath).path ?? "") {
completionHandler?(error: self.throwError(toPath, code: NSURLError.CannotWriteToFile))
@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))
return
}
do {
try self.opFileManager.copyItemAtURL(self.absoluteURL(path), toURL: self.absoluteURL(toPath))
completionHandler?(error: nil)
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderSucceed(self, operation: .Copy(source: path, destination: toPath))
try self.opFileManager.copyItem(at: self.absoluteURL(path), to: self.absoluteURL(toPath))
completionHandler?(nil)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderSucceed(self, operation: opType)
})
} catch let e as NSError {
completionHandler?(error: e)
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderFailed(self, operation: .Copy(source: path, destination: toPath))
completionHandler?(e)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderFailed(self, operation: opType)
})
}
}
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
}
public func removeItemAtPath(path: String, completionHandler: SimpleCompletionHandler) {
dispatch_async(operation_queue) {
@discardableResult
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let opType = FileOperationType.remove(path: path)
operation_queue.async {
do {
try self.opFileManager.removeItemAtURL(self.absoluteURL(path))
completionHandler?(error: nil)
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderSucceed(self, operation: .Remove(path: path))
try self.opFileManager.removeItem(at: self.absoluteURL(path))
completionHandler?(nil)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderSucceed(self, operation: opType)
})
} catch let e as NSError {
completionHandler?(error: e)
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderFailed(self, operation: .Remove(path: path))
completionHandler?(e)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderFailed(self, operation: opType)
})
}
}
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
}
public func copyLocalFileToPath(localFile: NSURL, toPath: String, completionHandler: SimpleCompletionHandler) {
dispatch_async(operation_queue) {
@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.copyItemAtURL(localFile, toURL: self.absoluteURL(toPath))
completionHandler?(error: nil)
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderSucceed(self, operation: .Copy(source: localFile.uw_absoluteString, destination: toPath))
try self.opFileManager.copyItem(at: localFile, to: self.absoluteURL(toPath))
completionHandler?(nil)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderSucceed(self, operation: opType)
})
} catch let e as NSError {
completionHandler?(error: e)
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderFailed(self, operation: .Copy(source: localFile.uw_absoluteString, destination: toPath))
completionHandler?(e)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderFailed(self, operation: opType)
})
}
}
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
}
public func copyPathToLocalFile(path: String, toLocalURL: NSURL, completionHandler: SimpleCompletionHandler) {
dispatch_async(operation_queue) {
@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.copyItemAtURL(self.absoluteURL(path), toURL: toLocalURL)
completionHandler?(error: nil)
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderSucceed(self, operation: .Copy(source: path, destination: toLocalURL.uw_absoluteString))
try self.opFileManager.copyItem(at: self.absoluteURL(path), to: toLocalURL)
completionHandler?(nil)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderSucceed(self, operation: opType)
})
} catch let e as NSError {
completionHandler?(error: e)
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderFailed(self, operation: .Copy(source: path, destination: toLocalURL.uw_absoluteString))
completionHandler?(e)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderFailed(self, operation: opType)
})
}
}
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
}
public func contentsAtPath(path: String, completionHandler: ((contents: NSData?, error: ErrorType?) -> Void)) {
dispatch_async(dispatch_queue) {
let data = self.fileManager.contentsAtPath(self.absoluteURL(path).path!)
completionHandler(contents: data, error: nil)
@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
}
public func contentsAtPath(path: String, offset: Int64, length: Int, completionHandler: ((contents: NSData?, error: ErrorType?) -> Void)) {
// Unfortunatlely there is no method provided in NSFileManager to read a segment of file.
// So we have to fallback to POSIX provided methods
dispatch_async(dispatch_queue) {
let aPath = self.absoluteURL(path).path!
if self.attributesOfItemAtURL(self.absoluteURL(path)).isDirectory {
self.throwError(path, code: NSURLError.FileIsDirectory)
@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.fileManager.fileExists(atPath: aPath) && !self.absoluteURL(path).fileIsDirectory else {
completionHandler(nil, self.throwError(path, code: URLError.cannotOpenFile as FoundationErrorEnum))
return
}
if !self.fileManager.fileExistsAtPath(aPath) {
self.throwError(path, code: NSURLError.FileDoesNotExist)
guard let handle = FileHandle(forReadingAtPath: aPath) else {
completionHandler(nil, self.throwError(path, code: URLError.cannotOpenFile as FoundationErrorEnum))
return
}
let fd_from = open(aPath, O_RDONLY)
if fd_from < 0 {
completionHandler(contents: nil, error: self.throwError(path, code: NSURLError.CannotOpenFile))
}
defer { precondition(close(fd_from) >= 0) }
lseek(fd_from, offset, SEEK_SET)
var buf = [UInt8](count: length, repeatedValue: 0)
let nread = read(fd_from, &buf, buf.count)
if nread < 0 { self.throwError(path, code: NSURLError.NoPermissionsToReadFile) }
if nread == 0 {
completionHandler(contents: nil, error: nil)
} else {
let data = NSData(bytesNoCopy: &buf, length: nread, freeWhenDone: true)
completionHandler(contents: data, error: 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)
}
public func writeContentsAtPath(path: String, contents data: NSData, atomically: Bool, completionHandler: SimpleCompletionHandler) {
dispatch_async(operation_queue) {
data.writeToURL(self.absoluteURL(path), atomically: atomically)
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderSucceed(self, operation: .Modify(path: path))
@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: opType)
})
}
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
}
public func searchFilesAtPath(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: ((files: [FileObject], error: ErrorType?) -> Void)) {
dispatch_async(dispatch_queue) {
let iterator = self.fileManager.enumeratorAtURL(self.absoluteURL(path), includingPropertiesForKeys: nil, options: recursive ? NSDirectoryEnumerationOptions() : .SkipsSubdirectoryDescendants) { (url, e) -> Bool in
completionHandler(files: [], error: e)
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 ? [] : [.skipsSubdirectoryDescendants, .skipsPackageDescendants]) { (url, e) -> Bool in
completionHandler([], e)
return true
}
var result = [LocalFileObject]()
while let fileURL = iterator?.nextObject() as? NSURL {
if fileURL.lastPathComponent?.lowercaseString.containsString(query.lowercaseString) ?? false {
let fileObject = self.attributesOfItemAtURL(fileURL)
result.append(self.attributesOfItemAtURL(fileURL))
foundItemHandler?(fileObject)
while let fileURL = iterator?.nextObject() as? URL {
if fileURL.lastPathComponent.lowercased().contains(query.lowercased()) {
let path = self.relativePathOf(url: fileURL)
if let fileObject = LocalFileObject(fileWithPath: path, relativeTo: self.baseURL) {
result.append(fileObject)
foundItemHandler?(fileObject)
}
}
}
completionHandler(files: result, error: nil)
completionHandler(result, nil)
}
}
private var monitors = [LocalFolderMonitor]()
fileprivate var monitors = [LocalFolderMonitor]()
public func registerNotifcation(path: String, eventHandler: (() -> Void)) {
self.unregisterNotifcation(path)
open func registerNotifcation(path: String, eventHandler: @escaping (() -> Void)) {
self.unregisterNotifcation(path: path)
let absurl = self.absoluteURL(path)
var isdirv: AnyObject?
do {
try absurl.getResourceValue(&isdirv, forKey: NSURLIsDirectoryKey)
} catch _ {
}
if !(isdirv?.boolValue ?? false) {
let isdir = (try? absurl.resourceValues(forKeys: [.isDirectoryKey]).isDirectory ?? false) ?? false
if !isdir {
return
}
let monitor = LocalFolderMonitor(url: absurl) {
@@ -326,175 +302,46 @@ public class LocalFileProvider: FileProvider, FileProviderMonitor {
monitors.append(monitor)
}
public func unregisterNotifcation(path: String) {
open func unregisterNotifcation(path: String) {
var removedMonitor: LocalFolderMonitor?
for (i, monitor) in monitors.enumerate() {
for (i, monitor) in monitors.enumerated() {
if self.relativePathOf(url: monitor.url) == path {
removedMonitor = monitors.removeAtIndex(i)
removedMonitor = monitors.remove(at: i)
break
}
}
removedMonitor?.stop()
}
public func isRegisteredForNotification(path: String) -> Bool {
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 {
public func createSymbolicLinkAtPath(path: String, withDestinationPath destPath: String, completionHandler: SimpleCompletionHandler) {
dispatch_async(operation_queue) {
public func create(symbolicLink path: String, withDestinationPath destPath: String, completionHandler: SimpleCompletionHandler) {
operation_queue.async {
do {
try self.opFileManager.createSymbolicLinkAtURL(self.absoluteURL(path), withDestinationURL: self.absoluteURL(destPath))
completionHandler?(error: nil)
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderSucceed(self, operation: .Link(link: path, target: destPath))
try self.opFileManager.createSymbolicLink(at: self.absoluteURL(path), withDestinationURL: self.absoluteURL(destPath))
completionHandler?(nil)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderSucceed(self, operation: .link(link: path, target: destPath))
})
} catch let e as NSError {
completionHandler?(error: e)
dispatch_async(dispatch_get_main_queue(), {
self.delegate?.fileproviderFailed(self, operation: .Link(link: path, target: destPath))
completionHandler?(e)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderFailed(self, operation: .link(link: path, target: destPath))
})
}
}
}
}
internal class LocalFileProviderManagerDelegate: NSObject, NSFileManagerDelegate {
weak var provider: LocalFileProvider?
init(provider: LocalFileProvider) {
self.provider = provider
}
func fileManager(fileManager: NSFileManager, shouldCopyItemAtURL srcURL: NSURL, toURL dstURL: NSURL) -> Bool {
guard let provider = self.provider, 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: NSFileManager, shouldMoveItemAtURL srcURL: NSURL, toURL dstURL: NSURL) -> Bool {
guard let provider = self.provider, 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: NSFileManager, shouldRemoveItemAtURL URL: NSURL) -> Bool {
guard let provider = self.provider, delegate = provider.fileOperationDelegate else {
return true
}
let path = provider.relativePathOf(url: URL)
return delegate.fileProvider(provider, shouldDoOperation: .Remove(path: path))
}
func fileManager(fileManager: NSFileManager, shouldLinkItemAtURL srcURL: NSURL, toURL dstURL: NSURL) -> Bool {
guard let provider = self.provider, 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: NSFileManager, shouldProceedAfterError error: NSError, copyingItemAtURL srcURL: NSURL, toURL dstURL: NSURL) -> Bool {
guard let provider = self.provider, 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: NSFileManager, shouldProceedAfterError error: NSError, movingItemAtURL srcURL: NSURL, toURL dstURL: NSURL) -> Bool {
guard let provider = self.provider, 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: NSFileManager, shouldProceedAfterError error: NSError, removingItemAtURL URL: NSURL) -> Bool {
guard let provider = self.provider, 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: NSFileManager, shouldProceedAfterError error: NSError, linkingItemAtURL srcURL: NSURL, toURL dstURL: NSURL) -> Bool {
guard let provider = self.provider, 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 {
private let source: dispatch_source_t
private let descriptor: CInt
private let qq: dispatch_queue_t = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
private var state: Bool = false
private var monitoredTime: NSTimeInterval = NSDate().timeIntervalSinceReferenceDate
var url: NSURL
/// Creates a folder monitor object with monitoring enabled.
init(url: NSURL, handler: ()->Void) {
self.url = url
descriptor = open(url.fileSystemRepresentation, O_EVTONLY)
source = dispatch_source_create(
DISPATCH_SOURCE_TYPE_VNODE,
UInt(descriptor),
DISPATCH_VNODE_WRITE,
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 NSDate().timeIntervalSinceReferenceDate < self.monitoredTime + 0.2 {
return
}
self.monitoredTime = NSDate().timeIntervalSinceReferenceDate
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(NSEC_PER_SEC) / 4), dispatch_get_main_queue(), {
handler()
})
}
dispatch_source_set_event_handler(source, main_handler)
dispatch_source_set_cancel_handler(source) {
close(self.descriptor)
}
start()
}
/// Starts sending notifications if currently stopped
func start() {
if !state {
state = true
dispatch_resume(source)
}
}
/// Stops sending notifications if currently enabled
func stop() {
if state {
state = false
dispatch_suspend(source)
}
}
deinit {
dispatch_source_cancel(source)
}
}
+298
View File
@@ -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)
}
}
+232
View File
@@ -0,0 +1,232 @@
//
// SessionDelegate.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2016 Mousavian. Distributed under MIT license.
//
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?
var credential: URLCredential?
var finishDownloadHandler: ((_ session: Foundation.URLSession, _ downloadTask: URLSessionDownloadTask, _ didFinishDownloadingToURL: URL) -> Void)?
var didSendDataHandler: ((_ session: Foundation.URLSession, _ task: URLSessionTask, _ bytesSent: Int64, _ totalBytesSent: Int64, _ totalBytesExpectedToSend: Int64) -> Void)?
var didReceivedData: ((_ session: Foundation.URLSession, _ downloadTask: URLSessionDownloadTask, _ bytesWritten: Int64, _ totalBytesWritten: Int64, _ totalBytesExpectedToWrite: Int64) -> Void)?
init(fileProvider: FileProvider, credential: URLCredential?) {
self.fileProvider = fileProvider
self.credential = credential
}
// codebeat:disable[ARITY]
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
self.finishDownloadHandler?(session, downloadTask, location)
return
}
func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
self.didSendDataHandler?(session, task, bytesSent, totalBytesSent, totalBytesExpectedToSend)
guard let desc = task.taskDescription, let json = jsonToDictionary(desc) else {
return
}
guard let type = json["type"] as? String, let source = json["source"] as? String else {
return
}
let dest = json["dest"] as? String
let op : FileOperationType
switch type {
case "Create":
op = .create(path: source)
case "Copy":
guard let dest = dest else { return }
op = .copy(source: source, destination: dest)
case "Move":
guard let dest = dest else { return }
op = .move(source: source, destination: dest)
case "Modify":
op = .modify(path: source)
case "Remove":
op = .remove(path: source)
case "Link":
guard let dest = dest else { return }
op = .link(link: source, target: dest)
default:
return
}
let progress = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
fileProvider?.delegate?.fileproviderProgress(fileProvider!, operation: op, progress: progress)
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
self.didReceivedData?(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite)
guard let desc = downloadTask.taskDescription, let json = jsonToDictionary(desc), let source = json["source"] as? String, let dest = json["dest"] as? String else {
return
}
fileProvider?.delegate?.fileproviderProgress(fileProvider!, operation: .copy(source: source, destination: dest), progress: Float(totalBytesWritten) / Float(totalBytesExpectedToWrite))
}
func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
let deposition: Foundation.URLSession.AuthChallengeDisposition = credential != nil ? .useCredential : .performDefaultHandling
completionHandler(deposition, credential)
}
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
let deposition: Foundation.URLSession.AuthChallengeDisposition = credential != nil ? .useCredential : .performDefaultHandling
completionHandler(deposition, credential)
}
}
public enum FileProviderHTTPErrorCode: Int {
case `continue` = 100
case switchingProtocols = 101
case processing = 102
case ok = 200
case created = 201
case accepted = 202
case nonAuthoritativeInformation = 203
case noContent = 204
case resetContent = 205
case partialContent = 206
case multiStatus = 207
case alreadyReported = 208
case imUsed = 226
case multipleChoices = 300
case movedPermanently = 301
case found = 302
case seeOther = 303
case notModified = 304
case useProxy = 305
case switchProxy = 306
case temporaryRedirect = 307
case permanentRedirect = 308
case badRequest = 400
case unauthorized = 401
case paymentRequired = 402
case forbidden = 403
case notFound = 404
case methodNotAllowed = 405
case notAcceptable = 406
case proxyAuthenticationRequired = 407
case requestTimeout = 408
case conflict = 409
case gone = 410
case lengthRequired = 411
case preconditionFailed = 412
case payloadTooLarge = 413
case uriTooLong = 414
case unsupportedMediaType = 415
case rangeNotSatisfiable = 416
case expectationFailed = 417
case misdirectedRequest = 421
case unprocessableEntity = 422
case locked = 423
case failedDependency = 424
case unorderedCollection = 425
case upgradeRequired = 426
case preconditionRequired = 428
case tooManyRequests = 429
case requestHeaderFieldsTooLarge = 431
case unavailableForLegalReasons = 451
case internalServerError = 500
case badGateway = 502
case serviceUnavailable = 503
case gatewayTimeout = 504
case httpVersionNotSupported = 505
case variantlsoNegotiates = 506
case insufficientStorage = 507
case loopDetected = 508
case bandwidthLimitExceeded = 509
case notExtended = 510
case networkAuthenticationRequired = 511
fileprivate static let status1xx = [100: "Continue", 101: "Switching Protocols", 102: "Processing"]
fileprivate static let status2xx = [200: "OK", 201: "Created", 202: "Accepted", 203: "Non-Authoritative Information", 204: "No Content", 205: "Reset Content", 206: "Partial Content", 207: "Multi-Status", 208: "Already Reported", 226: "IM Used"]
fileprivate static let status3xx = [300: "Multiple Choices", 301: "Moved Permanently", 302: "Found", 303: "See Other", 304: "Not Modified", 305: "Use Proxy", 306: "Switch Proxy", 307: "Temporary Redirect", 308: "Permanent Redirect"]
fileprivate static let status4xx = [400: "Bad Request", 401: "Unauthorized/Expired Session", 402: "Payment Required", 403: "Forbidden", 404: "Not Found", 405: "Method Not Allowed", 406: "Not Acceptable", 407: "Proxy Authentication Required", 408: "Request Timeout", 409: "Conflict", 410: "Gone", 411: "Length Required", 412: "Precondition Failed", 413: "Payload Too Large", 414: "URI Too Long", 415: "Unsupported Media Type", 416: "Range Not Satisfiable", 417: "Expectation Failed", 421: "Misdirected Request", 422: "Unprocessable Entity", 423: "Locked", 424: "Failed Dependency", 425: "Unordered Collection", 426: "Upgrade Required", 428: "Precondition Required", 429: "Too Many Requests", 431: "Request Header Fields Too Large", 451: "Unavailable For Legal Reasons"]
fileprivate static let status5xx = [500: "Internal Server Error", 501: "Not Implemented", 502: "Bad Gateway", 503: "Service Unavailable", 504: "Gateway Timeout", 505: "HTTP Version Not Supported", 506: "Variant Also Negotiates", 507: "Insufficient Storage", 508: "Loop Detected", 509: "Bandwidth Limit Exceeded", 510: "Not Extended", 511: "Network Authentication Required"]
public var description: String {
switch self.rawValue {
case 100...102: return FileProviderHTTPErrorCode.status1xx[self.rawValue]!
case 200...208, 226: return FileProviderHTTPErrorCode.status2xx[self.rawValue]!
case 300...308: return FileProviderHTTPErrorCode.status3xx[self.rawValue]!
case 400...417, 421...426: fallthrough
case 428, 429, 431, 451: return FileProviderHTTPErrorCode.status4xx[self.rawValue]!
case 500...511: return FileProviderHTTPErrorCode.status5xx[self.rawValue]!
default: return typeDescription
}
}
public var typeDescription: String {
switch self.rawValue {
case 100...199: return "Informational"
case 200...299: return "Success"
case 300...399: return "Redirection"
case 400...499: return "Client Error"
case 500...599: return "Server Error"
default: return "Server Error"
}
}
}
+85 -100
View File
@@ -8,60 +8,53 @@
import Foundation
internal func encode<T>(inout value: T) -> NSData {
return withUnsafePointer(&value) { p in
NSData(bytes: p, length: sizeofValue(value))
}
}
internal func encode<T>(value: T) -> NSData {
var value = value
return withUnsafePointer(&value) { p in
NSData(bytes: p, length: sizeofValue(value))
}
}
internal func decode<T>(data: NSData) -> T {
let pointer = UnsafeMutablePointer<T>.alloc(sizeof(T.Type))
data.getBytes(pointer, length: sizeof(T.Type))
return pointer.move()
}
// This client implementation is for little-endian platform, namely x86, x64 & arm
// 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?
func sendNegotiate(completionHandler: SimpleCompletionHandler) -> UInt64 {
let mId = messageId()
let smbHeader = SMB2.Header(command: .NEGOTIATE, creditRequestResponse: 126, messageId: mId, treeId: 0, sessionId: 0)
let smbHeader = SMB2.Header(command: .NEGOTIATE, creditRequestResponse: UInt16(126), messageId: mId, treeId: UInt32(0), sessionId: UInt64(0))
let msg = SMB2.NegotiateRequest()
let data = createSMB2Message(smbHeader, message: msg)
let data = createSMB2Message(header: smbHeader, message: msg)
self.writeData(data, timeout: 0, completionHandler: { (e) in
completionHandler?(error: e)
completionHandler?(e)
})
return mId
}
func sendSessionSetup(completionHandler: SimpleCompletionHandler) -> UInt64 {
let mId = messageId()
let smbHeader = SMB2.Header(command: .SESSION_SETUP, creditRequestResponse: sessionId > 0 ? 124 : 125, messageId: mId, treeId: 0, sessionId: sessionId)
let credit = UInt16(sessionId > 0 ? 124 : 125)
let smbHeader = SMB2.Header(command: SMB2.Command.SESSION_SETUP, creditRequestResponse: credit, messageId: mId, treeId: UInt32(0), sessionId: sessionId)
let msg = SMB2.SessionSetupRequest(singing: [])
let data = createSMB2Message(smbHeader, message: msg)
let data = createSMB2Message(header: smbHeader, message: msg)
self.writeData(data, timeout: 0, completionHandler: { (e) in
if self.sessionId == 0 {
self.readDataOfMinLength(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?(error: e2 ?? e)
completionHandler?(e2 ?? e)
})
}
})
@@ -70,111 +63,109 @@ class SMB2ProtocolClient: FPSStreamTask {
func sendTreeConnect(completionHandler: SimpleCompletionHandler) -> UInt64 {
let req = self.currentRequest ?? self.originalRequest
guard let url = req?.URL, let host = url.host else {
guard let url = req?.url, let host = url.host else {
return 0
}
let mId = messageId()
let smbHeader = SMB2.Header(command: .TREE_CONNECT, creditRequestResponse: 123, messageId: mId, treeId: 0, sessionId: sessionId)
var share = ""
if let cmp = url.pathComponents where cmp.count > 0 {
let cmp = url.pathComponents
if cmp.count > 0 {
share = cmp[0]
}
let tcHeader = SMB2.TreeConnectRequest.Header(flags: [])
let msg = SMB2.TreeConnectRequest(header: tcHeader, host: host, share: share)
let data = createSMB2Message(smbHeader, message: msg!)
let data = createSMB2Message(header: smbHeader, message: msg!)
self.writeData(data, timeout: 0, completionHandler: { (e) in
completionHandler?(error: e)
completionHandler?(e)
})
return mId
}
func sendTreeDisconnect(treeId: UInt32, completionHandler: SimpleCompletionHandler) -> UInt64 {
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)
let msg = SMB2.TreeDisconnect()
let data = createSMB2Message(smbHeader, message: msg)
let data = createSMB2Message(header: smbHeader, message: msg)
self.writeData(data, timeout: 0, completionHandler: { (e) in
completionHandler?(error: e)
completionHandler?(e)
})
return mId
}
func sendLogoff(treeId: UInt32, completionHandler: SimpleCompletionHandler) -> UInt64 {
func sendLogoff(id treeId: UInt32, completionHandler: SimpleCompletionHandler) -> UInt64 {
let mId = messageId()
let smbHeader = SMB2.Header(command: .LOGOFF, creditRequestResponse: 0, messageId: mId, treeId: 0, sessionId: sessionId)
let msg = SMB2.LogOff()
let data = createSMB2Message(smbHeader, message: msg)
let data = createSMB2Message(header: smbHeader, message: msg)
self.writeData(data, timeout: 0, completionHandler: { (e) in
completionHandler?(error: e)
completionHandler?(e)
})
return mId
}
func messageId() -> UInt64 {
defer {
currentMessageID += 1
}
return currentMessageID
func reset() {
}
// MARK: create and analyse messages
func determineSMBVersion(data: NSData) -> Float {
var smbverChar: Int8 = 0
data.getBytes(&smbverChar, length: 1)
}
// MARK: create and analyse messages
extension SMB2ProtocolClient {
func determineSMBVersion(_ data: Data) -> Float {
let smbverChar: Int8 = Int8(bitPattern: data.first ?? 0)
let version = 0 - smbverChar
return Float(version)
}
func digestSMBMessage(data: NSData) throws -> (header: SMB1.Header, blocks: [(params: [UInt16], message: NSData?)]) {
guard data.length > 30 else {
throw NSURLError.BadServerResponse
func digestSMBMessage(_ data: Data) throws -> (header: SMB1.Header, blocks: [(params: [UInt16], message: Data?)]) {
guard data.count > 30 else {
throw URLError(.badServerResponse)
}
var buffer = [UInt8](count: data.length, repeatedValue: 0)
var buffer = [UInt8](repeating: 0, count: data.count)
guard determineSMBVersion(data) == 1 else {
throw SMBFileProviderError.IncompatibleHeader
throw SMBFileProviderError.incompatibleHeader
}
let headersize = sizeof(SMB1.Header.self)
let header: SMB1.Header = decode(data)
var blocks = [(params: [UInt16], message: NSData?)]()
let headersize = MemoryLayout<SMB1.Header>.size
let header: SMB1.Header = data.scanValue()!
var blocks = [(params: [UInt16], message: Data?)]()
var offset = headersize
while offset < data.length {
while offset < data.count {
let paramWords: [UInt16]
let paramWordsCount = Int(buffer[offset])
guard data.length > (paramWordsCount * 2 + offset) else {
throw SMBFileProviderError.IncorrectParamsLength
guard data.count > (paramWordsCount * 2 + offset) else {
throw SMBFileProviderError.incorrectParamsLength
}
offset += sizeof(UInt8)
offset += MemoryLayout<UInt8>.size
var rawParamWords = [UInt8](buffer[offset..<(offset + paramWordsCount * 2)])
let paramData = NSData(bytesNoCopy: &rawParamWords, length: rawParamWords.count)
paramWords = decode(paramData)
let paramData = Data(bytesNoCopy: UnsafeMutablePointer<UInt8>(&rawParamWords), count: rawParamWords.count, deallocator: .free)
paramWords = paramData.scanValue()!
offset += paramWordsCount * 2
let messageBytesCountLittleEndian = [UInt8](buffer[offset...(offset + 1)])
let messageBytesCount = Int(UnsafePointer<UInt16>(messageBytesCountLittleEndian).memory)
offset += sizeof(UInt16)
guard data.length >= (offset + messageBytesCount) else {
throw SMBFileProviderError.IncorrectMessageLength
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)
let message = Data(bytes: rawMessage)
blocks.append((params: paramWords, message: message))
}
return (header, blocks)
}
func digestSMB2Message(data: NSData) throws -> (header: SMB2.Header, message: SMBResponse?)? {
guard data.length > 65 else {
throw NSURLError.BadServerResponse
func digestSMB2Message(_ data: Data) throws -> SMBResponse? {
guard data.count > 65 else {
throw URLError(.badServerResponse)
}
guard determineSMBVersion(data) == 2 else {
throw SMBFileProviderError.IncompatibleHeader
throw SMBFileProviderError.incompatibleHeader
}
let headersize = sizeof(SMB2.Header.self)
let headerData = data.subdataWithRange(NSRange(location: 0, length: headersize))
let messageSize = data.length - headersize
let messageData = data.subdataWithRange(NSRange(location: headersize, length: messageSize))
let header: SMB2.Header = decode(headerData)
let headersize = MemoryLayout<SMB2.Header>.size
let headerData = data.subdata(in: 0..<headersize)
let messageSize = data.count - headersize
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))
@@ -215,37 +206,31 @@ class SMB2ProtocolClient: FPSStreamTask {
case .OPLOCK_BREAK:
return (header, nil) // FIXME:
case .INVALID:
throw SMBFileProviderError.InvalidCommand
throw SMBFileProviderError.invalidCommand
}
}
func createSMBMessage(header: SMB1.Header, blocks: [(params: NSData?, message: NSData?)]) -> NSData {
var headerv = header
let result = NSMutableData(data: encode(&headerv))
func createSMBMessage(header: SMB1.Header, blocks: [(params: Data?, message: Data?)]) -> Data {
var result = Data(value: header)
for block in blocks {
var paramWordsCount = UInt8(block.params?.length ?? 0)
result.appendBytes(&paramWordsCount, length: sizeofValue(paramWordsCount))
var paramWordsCount = UInt8(block.params?.count ?? 0)
result.append(&paramWordsCount, count: MemoryLayout.size(ofValue: paramWordsCount))
if let params = block.params {
result.appendData(params)
result.append(params)
}
var messageLen = UInt16(block.message?.length ?? 0)
result.appendBytes(&messageLen, length: sizeofValue(messageLen))
var messageLen = UInt16(block.message?.count ?? 0)
let b = UnsafeBufferPointer(start: &messageLen, count: MemoryLayout.size(ofValue: messageLen))
result.append(b)
if let message = block.message {
result.appendData(message)
result.append(message)
}
}
return result
}
func createSMB2Message(header: SMB2.Header, message: SMBRequest) -> NSData {
var headerv = header
let result = NSMutableData(data: encode(&headerv))
result.appendData(message.data())
func createSMB2Message(header: SMB2.Header, message: SMBRequestBody) -> Data {
var result = Data(value: header)
result.append(message.data())
return result
}
}
protocol FileProviderSMBHeader {
var protocolID: UInt32 { get }
static var protocolConst: UInt32 { get }
}
+86 -71
View File
@@ -8,108 +8,123 @@
import Foundation
public class SMBFileProvider: FileProvider, FileProviderMonitor {
public var type: String = "Samba"
public var isPathRelative: Bool = true
public var baseURL: NSURL?
public var currentPath: String = ""
public var dispatch_queue: dispatch_queue_t
public weak var delegate: FileProviderDelegate?
public let credential: NSURLCredential?
class SMBFileProvider: FileProvider, FileProviderMonitor {
open static var type: String = "Samba"
open var isPathRelative: Bool = true
open var baseURL: URL?
open var currentPath: String = ""
open var dispatch_queue: DispatchQueue
open weak var delegate: FileProviderDelegate?
open let credential: URLCredential?
public typealias FileObjectClass = FileObject
public init? (baseURL: NSURL, credential: NSURLCredential, afterInitialized: SimpleCompletionHandler) {
guard baseURL.uw_scheme.lowercaseString == "smb" else {
public init? (baseURL: URL, credential: URLCredential, afterInitialized: SimpleCompletionHandler) {
guard baseURL.uw_scheme.lowercased() == "smb" else {
return nil
}
self.baseURL = baseURL
dispatch_queue = dispatch_queue_create("FileProvider.\(type)", DISPATCH_QUEUE_CONCURRENT)
dispatch_queue = DispatchQueue(label: "FileProvider.\(SMBFileProvider.type)", attributes: DispatchQueue.Attributes.concurrent)
//let url = baseURL.uw_absoluteString
self.credential = credential
}
public func contentsOfDirectoryAtPath(path: String, completionHandler: ((contents: [FileObjectClass], error: ErrorType?) -> Void)) {
NotImplemented()
dispatch_async(dispatch_queue) {
}
}
public func attributesOfItemAtPath(path: String, completionHandler: ((attributes: FileObjectClass?, error: ErrorType?) -> Void)) {
open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObjectClass], _ error: Error?) -> Void)) {
NotImplemented()
}
public func storageProperties(completionHandler: ((total: Int64, used: Int64) -> Void)) {
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObjectClass?, _ error: Error?) -> Void)) {
NotImplemented()
}
public weak var fileOperationDelegate: FileOperationDelegate?
public func createFolder(folderName: String, atPath: String, completionHandler: SimpleCompletionHandler) {
open func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void)) {
NotImplemented()
}
public func createFile(fileAttribs: FileObject, atPath: String, contents data: NSData?, completionHandler: SimpleCompletionHandler) {
open weak var fileOperationDelegate: FileOperationDelegate?
open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
NotImplemented()
return nil
}
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) -> OperationHandle? {
NotImplemented()
return nil
}
open func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
NotImplemented()
return nil
}
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
NotImplemented()
return nil
}
open func copyItem(localFile: URL, to toPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
NotImplemented()
return nil
}
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)) -> OperationHandle? {
NotImplemented()
return nil
}
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) -> OperationHandle? {
NotImplemented()
return nil
}
open func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler:((FileObjectClass) -> Void)?, completionHandler: @escaping ((_ files: [FileObjectClass], _ error: Error?) -> Void)) {
NotImplemented()
}
public func moveItemAtPath(path: String, toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
open func registerNotifcation(path: String, eventHandler: @escaping (() -> Void)) {
NotImplemented()
}
public func copyItemAtPath(path: String, toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
open func unregisterNotifcation(path: String) {
NotImplemented()
}
public func removeItemAtPath(path: String, completionHandler: SimpleCompletionHandler) {
NotImplemented()
}
public func copyLocalFileToPath(localFile: NSURL, toPath: String, completionHandler: SimpleCompletionHandler) {
NotImplemented()
}
public func copyPathToLocalFile(path: String, toLocalURL: NSURL, completionHandler: SimpleCompletionHandler) {
NotImplemented()
}
public func contentsAtPath(path: String, completionHandler: ((contents: NSData?, error: ErrorType?) -> Void)) {
NotImplemented()
}
public func contentsAtPath(path: String, offset: Int64, length: Int, completionHandler: ((contents: NSData?, error: ErrorType?) -> Void)) {
NotImplemented()
}
public func writeContentsAtPath(path: String, contents data: NSData, atomically: Bool, completionHandler: SimpleCompletionHandler) {
NotImplemented()
}
public func searchFilesAtPath(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObjectClass) -> Void)?, completionHandler: ((files: [FileObjectClass], error: ErrorType?) -> Void)) {
NotImplemented()
}
public func registerNotifcation(path: String, eventHandler: (() -> Void)) {
NotImplemented()
}
public func unregisterNotifcation(path: String) {
NotImplemented()
}
public func isRegisteredForNotification(path: String) -> Bool {
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
public enum SMBFileProviderError: Int, ErrorType, CustomStringConvertible {
case BadHeader
case IncompatibleHeader
case IncorrectParamsLength
case IncorrectMessageLength
case InvalidCommand
public enum SMBFileProviderError: Int, Error, CustomStringConvertible {
case badHeader
case incompatibleHeader
case incorrectParamsLength
case incorrectMessageLength
case invalidCommand
public var description: String {
return "SMB message structure is invalid"
@@ -117,8 +132,8 @@ public enum SMBFileProviderError: Int, ErrorType, CustomStringConvertible {
}
private extension SMBFileProvider {
private func getPID() -> UInt32 {
return UInt32(NSProcessInfo.processInfo().processIdentifier)
func getPID() -> UInt32 {
return UInt32(ProcessInfo.processInfo.processIdentifier)
}
}
+8 -8
View File
@@ -14,7 +14,7 @@ struct SMB1 {
// header is always \u{ff}SMB
let protocolID: UInt32
static let protocolConst: UInt32 = 0x424d53ff
private var _command: UInt8
fileprivate var _command: UInt8
var command: Command {
get {
return Command(rawValue: _command) ?? .INVALID
@@ -24,7 +24,7 @@ struct SMB1 {
}
}
// error messages from the server to the client
private var _status: (UInt8, UInt8, UInt8, UInt8)
fileprivate var _status: (UInt8, UInt8, UInt8, UInt8)
var error: (Class: UInt8, code: UInt16) {
get {
return (_status.0, UInt16(_status.2) + (UInt16(_status.3) << 8))
@@ -47,7 +47,7 @@ struct SMB1 {
var flags2: Flags2
var pidHigh: UInt16
// encryption key used for validating messages over connectionless transports
private var _securityKey: (UInt16, UInt16)
fileprivate var _securityKey: (UInt16, UInt16)
var securityKey: UInt32 {
get {
return UInt32(_securityKey.1) << 16 + UInt32(_securityKey.0)
@@ -60,7 +60,7 @@ struct SMB1 {
var securityCID: UInt16
/// Identifier of the sequence of a message over connectionless transports
var securitySequenceNumber: UInt16
private var ununsed: UInt16
fileprivate var ununsed: UInt16
var treeId: UInt16
var pidLow: UInt16
var userId: UInt16
@@ -75,7 +75,7 @@ struct SMB1 {
}
}
init(command: Command, ntStatus: UInt32 = 0, flags: Flags, flags2: Flags2 = [.LONG_NAMES, .ERR_STATUS, .UNICODE], securityKey: UInt32 = 0, securityCID: UInt16 = 0, securitySequenceNumber: UInt16 = 0, treeId: UInt16, pid: UInt32, userId: UInt16, multiplexId: UInt16) {
init(command: Command, treeId: UInt16, pid: UInt32, userId: UInt16, multiplexId: UInt16, flags: Flags, flags2: Flags2 = [.LONG_NAMES, .ERR_STATUS, .UNICODE], ntStatus: UInt32 = 0, securityKey: UInt32 = 0, securityCID: UInt16 = 0, securitySequenceNumber: UInt16 = 0) {
self.protocolID = Header.protocolConst
self._command = command.rawValue
_status = (UInt8(ntStatus & 0xff), UInt8(ntStatus >> 8 & 0xff), UInt8(ntStatus >> 16 & 0xff), UInt8(ntStatus >> 24 & 0xff))
@@ -93,7 +93,7 @@ struct SMB1 {
}
}
struct Flags: OptionSetType {
struct Flags: OptionSet {
let rawValue: UInt8
init(rawValue: UInt8) {
@@ -111,7 +111,7 @@ struct SMB1 {
static let REPLY = Flags(rawValue: 0x80)
}
struct Flags2: OptionSetType {
struct Flags2: OptionSet {
let rawValue: UInt16
init(rawValue: UInt16) {
@@ -214,4 +214,4 @@ struct SMB1 {
case WRITE_BULK_DATA = 0xDA
case INVALID = 0xFE
}
}
}
+28 -10
View File
@@ -8,17 +8,35 @@
import Foundation
protocol SMBRequest {
func data() -> NSData
protocol SMBRequestBody {
func data() -> Data
}
protocol SMBResponse {
init? (data: NSData)
extension SMBRequestBody {
func data() -> Data {
return Data(value: self)
}
}
protocol SMBResponseBody {
init? (data: Data)
}
protocol IOCtlRequestProtocol: SMBRequest {}
protocol IOCtlResponseProtocol: SMBResponse {}
extension SMBResponseBody {
init? (data: Data) {
if let v: Self = data.scanValue() {
self = v
} else {
return nil
}
}
}
typealias SMBRequest = (header: SMB2.Header, body: SMBRequestBody?)
typealias SMBResponse = (header: SMB2.Header, body: SMBResponseBody?)
protocol IOCtlRequestProtocol: SMBRequestBody {}
protocol IOCtlResponseProtocol: SMBResponseBody {}
struct SMBTime {
@@ -32,11 +50,11 @@ struct SMBTime {
self.time = (Int64(unixTime) + 11644473600) * 10000000
}
init(timeIntervalSince1970: NSTimeInterval) {
init(timeIntervalSince1970: TimeInterval) {
self.time = Int64((timeIntervalSince1970 + 11644473600) * 10000000)
}
init(date: NSDate) {
init(date: Date) {
self.init(timeIntervalSince1970: date.timeIntervalSince1970)
}
@@ -44,7 +62,7 @@ struct SMBTime {
return UInt(self.time / 10000000 - 11644473600)
}
var date: NSDate {
return NSDate(timeIntervalSince1970: Double(self.time) / 10000000 - 11644473600)
var date: Date {
return Date(timeIntervalSince1970: Double(self.time) / 10000000 - 11644473600)
}
}
+71 -93
View File
@@ -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]
@@ -22,15 +22,15 @@ extension SMB2 {
self.contexts = contexts
}
func data() -> NSData {
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.dataUsingEncoding(NSUTF16StringEncoding) {
var body = Data()
if let name = self.name, let nameData = name.data(using: .utf16) {
header.nameOffset = UInt16(offset)
header.nameLength = UInt16(nameData.length)
offset += nameData.length
body.appendData(nameData)
header.nameLength = UInt16(nameData.count)
offset += nameData.count
body.append(nameData)
}
if contexts.count > 0 {
// TODO: Context CreateRequest implementation, 8 bit allign offset
@@ -40,15 +40,15 @@ extension SMB2 {
header.contextLength = 0
//result.appendData(nameData)
}
let result = NSMutableData(data: encode(&header))
result.appendData(body)
var result = Data(value: header)
result.append(body)
return result
}
struct Header {
let size: UInt16
private let securityFlags: UInt8
private var _requestedOplockLevel: UInt8
fileprivate let securityFlags: UInt8
fileprivate var _requestedOplockLevel: UInt8
var requestedOplockLevel: OplockLevel {
get {
return OplockLevel(rawValue: _requestedOplockLevel)!
@@ -57,7 +57,7 @@ extension SMB2 {
_requestedOplockLevel = newValue.rawValue
}
}
private var _impersonationLevel: UInt32
fileprivate var _impersonationLevel: UInt32
var impersonationLevel: ImpersonationLevel {
get {
return ImpersonationLevel(rawValue: _impersonationLevel)!
@@ -66,12 +66,12 @@ extension SMB2 {
_impersonationLevel = newValue.rawValue
}
}
private let flags: UInt64
private let reserved: UInt64
fileprivate let flags: UInt64
fileprivate let reserved: UInt64
let access: FileAccessMask
let fileAttributes: FileAttributes
let shareAccess: ShareAccess
private var _desposition: UInt32
fileprivate var _desposition: UInt32
var desposition: CreateDisposition {
get {
return CreateDisposition(rawValue: _desposition)!
@@ -86,7 +86,7 @@ extension SMB2 {
var contextOffset: UInt32
var contextLength: UInt32
init(requestedOplockLevel: OplockLevel = .NONE, impersonationLevel: ImpersonationLevel = .Anonymous, access: FileAccessMask = [.GENERIC_ALL], fileAttributes: FileAttributes = [], shareAccess: ShareAccess = [.READ], desposition: CreateDisposition = .OPEN_IF, options: CreateOptions = []) {
init(requestedOplockLevel: OplockLevel = .NONE, impersonationLevel: ImpersonationLevel = .anonymous, access: FileAccessMask = [.GENERIC_ALL], fileAttributes: FileAttributes = [], shareAccess: ShareAccess = [.READ], desposition: CreateDisposition = .OPEN_IF, options: CreateOptions = []) {
self.size = 57
self.securityFlags = 0
self._requestedOplockLevel = requestedOplockLevel.rawValue
@@ -105,7 +105,7 @@ extension SMB2 {
}
}
struct CreateOptions: OptionSetType {
struct CreateOptions: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
@@ -125,14 +125,14 @@ extension SMB2 {
static let NO_COMPRESSION = CreateOptions(rawValue: 0x00008000)
static let OPEN_REPARSE_POINT = CreateOptions(rawValue: 0x00200000)
static let OPEN_NO_RECALL = CreateOptions(rawValue: 0x00400000)
private static let SYNCHRONOUS_IO_ALERT = CreateOptions(rawValue: 0x00000010)
private static let SYNCHRONOUS_IO_NONALERT = CreateOptions(rawValue: 0x00000020)
private static let COMPLETE_IF_OPLOCKED = CreateOptions(rawValue: 0x00000100)
private static let REMOTE_INSTANCE = CreateOptions(rawValue: 0x00000400)
private static let OPEN_FOR_FREE_SPACE_QUERY = CreateOptions(rawValue: 0x00800000)
private static let OPEN_REQUIRING_OPLOCK = CreateOptions(rawValue: 0x00010000)
private static let DISALLOW_EXCLUSIVE = CreateOptions(rawValue: 0x00020000)
private static let RESERVE_OPFILTER = CreateOptions(rawValue: 0x00100000)
fileprivate static let SYNCHRONOUS_IO_ALERT = CreateOptions(rawValue: 0x00000010)
fileprivate static let SYNCHRONOUS_IO_NONALERT = CreateOptions(rawValue: 0x00000020)
fileprivate static let COMPLETE_IF_OPLOCKED = CreateOptions(rawValue: 0x00000100)
fileprivate static let REMOTE_INSTANCE = CreateOptions(rawValue: 0x00000400)
fileprivate static let OPEN_FOR_FREE_SPACE_QUERY = CreateOptions(rawValue: 0x00800000)
fileprivate static let OPEN_REQUIRING_OPLOCK = CreateOptions(rawValue: 0x00010000)
fileprivate static let DISALLOW_EXCLUSIVE = CreateOptions(rawValue: 0x00020000)
fileprivate static let RESERVE_OPFILTER = CreateOptions(rawValue: 0x00100000)
}
enum CreateDisposition: UInt32 {
@@ -151,21 +151,21 @@ extension SMB2 {
}
enum ImpersonationLevel: UInt32 {
case Anonymous = 0x00000000
case Identification = 0x00000001
case Impersonation = 0x00000002
case Delegate = 0x00000003
case anonymous = 0x00000000
case identification = 0x00000001
case impersonation = 0x00000002
case delegate = 0x00000003
}
}
struct CreateResponse: SMBResponse {
struct CreateResponse: SMBResponseBody {
struct Header {
let size: UInt16
private let _oplockLevel: UInt8
fileprivate let _oplockLevel: UInt8
var oplockLevel: OplockLevel {
return OplockLevel(rawValue: _oplockLevel)!
}
private let reserved: UInt32
fileprivate let reserved: UInt32
let creationTime: SMBTime
let lastAccessTime: SMBTime
let lastWriteTime: SMBTime
@@ -173,7 +173,7 @@ extension SMB2 {
let allocationSize: UInt64
let endOfFile: UInt64
let fileAttributes: FileAttributes
private let reserved2: UInt32
fileprivate let reserved2: UInt32
let fileId: FileId
let contextsOffset: UInt32
let ContextsLength: UInt32
@@ -182,27 +182,22 @@ extension SMB2 {
let header: CreateResponse.Header
let contexts: [CreateContext]
init? (data: NSData) {
guard data.length >= sizeof(CreateResponse.Header.self) else {
init? (data: Data) {
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) - sizeof(SMB2.Header.self)
var contextOffset = Int(self.header.contextsOffset) - MemoryLayout<SMB2.Header>.size
while contextOffset > 0 {
guard contextOffset < data.length else {
guard contextOffset < data.count else {
self.contexts = contexts
return
}
let contextDataHeader = data.subdataWithRange(NSRange(location: contextOffset, length: sizeof(CreateContext.Header.self)))
if let lastContextHeader = CreateContext(data: contextDataHeader) {
let lastContextLen = Int(lastContextHeader.header.dataOffset) + Int(lastContextHeader.header.dataLength) - contextOffset
let lastContextData = data.subdataWithRange(NSRange(location: contextOffset, length: lastContextLen))
if let newContext = CreateContext(data: lastContextData) {
contexts.append(newContext)
}
contextOffset = Int(lastContextHeader.header.next) - sizeof(SMB2.Header.self)
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
@@ -217,40 +212,39 @@ extension SMB2 {
var next: UInt32
let nameOffset: UInt16
let nameLength: UInt16
private let reserved: UInt16
fileprivate let reserved: UInt16
let dataOffset: UInt16
let dataLength: UInt32
}
var header: CreateContext.Header
let buffer: NSData
let buffer: Data
init(name: ContextNames, data: NSData) {
let nameData = NSMutableData(data: (name.rawValue).dataUsingEncoding(NSUTF16StringEncoding)!)
self.header = CreateContext.Header(next: 0, nameOffset: 32, nameLength: UInt16(nameData.length), reserved: 0, dataOffset: UInt16(nameData.length), dataLength: UInt32(data.length))
init(name: ContextNames, data: 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
}
init(name: NSUUID, data: NSData) {
var uuid = uuid_t(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)
name.getUUIDBytes(&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.length))
init(name: UUID, data: Data) {
let uuid = name.uuid
var nameData = Data(value: uuid)
self.header = CreateContext.Header(next: 0, nameOffset: 32, nameLength: UInt16(nameData.count), reserved: 0, dataOffset: UInt16(nameData.count), dataLength: UInt32(data.count))
self.buffer = data
}
init? (data: NSData) {
let headersize = sizeof(Header)
guard data.length > headersize else {
init? (data: Data) {
let headersize = MemoryLayout<Header>.size
guard data.count > headersize else {
return nil
}
self.header = decode(data)
self.buffer = data.subdataWithRange(NSRange(location: headersize, length: data.length - headersize))
self.header = data.scanValue()!
self.buffer = data.subdata(in: headersize..<data.count)
}
func data() -> NSData {
let result = NSMutableData(data: encode(header))
result.appendData(buffer)
func data() -> Data {
var result = Data(value: header)
result.append(buffer)
return result
}
@@ -284,7 +278,7 @@ extension SMB2 {
case LEASE = 0xFF
}
struct ShareAccess: OptionSetType {
struct ShareAccess: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
@@ -296,7 +290,7 @@ extension SMB2 {
static let DELETE = ShareAccess(rawValue: 0x00000004)
}
struct FileAccessMask: OptionSetType {
struct FileAccessMask: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
@@ -332,7 +326,7 @@ extension SMB2 {
static let GENERIC_READ = FileAccessMask(rawValue: 0x80000000)
}
struct FileAttributes: OptionSetType {
struct FileAttributes: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
@@ -363,10 +357,10 @@ extension SMB2 {
// MARK: SMB2 Close
struct CloseRequest: SMBRequest {
struct CloseRequest: SMBRequestBody {
let size: UInt16
let flags: CloseFlags
private let reserved2: UInt32
fileprivate let reserved2: UInt32
let filePersistantId: UInt64
let fileVolatileId: UInt64
@@ -377,16 +371,12 @@ extension SMB2 {
self.flags = []
self.reserved2 = 0
}
func data() -> NSData {
return encode(self)
}
}
struct CloseResponse: SMBResponse {
struct CloseResponse: SMBResponseBody {
let size: UInt16
let flags: CloseFlags
private let reserved: UInt32
fileprivate let reserved: UInt32
let creationTime: SMBTime
let lastAccessTime: SMBTime
let lastWriteTime: SMBTime
@@ -394,13 +384,9 @@ extension SMB2 {
let allocationSize: UInt64
let endOfFile: UInt64
let fileAttributes: FileAttributes
init? (data: NSData) {
self = decode(data)
}
}
struct CloseFlags: OptionSetType {
struct CloseFlags: OptionSet {
let rawValue: UInt16
init(rawValue: UInt16) {
@@ -412,10 +398,10 @@ extension SMB2 {
// MARK: SMB2 Flush
struct FlushRequest: SMBRequest {
struct FlushRequest: SMBRequestBody {
let size: UInt16
private let reserved: UInt16
private let reserved2: UInt32
fileprivate let reserved: UInt16
fileprivate let reserved2: UInt32
let filePersistantId: UInt64
let fileVolatileId: UInt64
@@ -426,13 +412,9 @@ extension SMB2 {
self.reserved = 0
self.reserved2 = 0
}
func data() -> NSData {
return encode(self)
}
}
struct FlushResponse: SMBResponse {
struct FlushResponse: SMBResponseBody {
let size: UInt16
let reserved: UInt16
@@ -440,9 +422,5 @@ extension SMB2 {
self.size = 4
self.reserved = 0
}
init? (data: NSData) {
self = decode(data)
}
}
}
}
+47 -71
View File
@@ -11,22 +11,22 @@ import Foundation
extension SMB2 {
// MARK: SMB2 Read
struct ReadRequest: SMBRequest {
struct ReadRequest: SMBRequestBody {
let size: UInt16
private let padding: UInt8
fileprivate let padding: UInt8
let flags: ReadRequest.Flags
let length: UInt32
let offset: UInt64
let fileId: FileId
let minimumLength: UInt32
private let _channel: UInt32
fileprivate let _channel: UInt32
var channel: Channel {
return Channel(rawValue: _channel) ?? .NONE
}
let remainingBytes: UInt32
private let channelInfoOffset: UInt16
private let channelInfoLength: UInt16
private let channelBuffer: UInt8
fileprivate let channelInfoOffset: UInt16
fileprivate let channelInfoLength: UInt16
fileprivate let channelBuffer: UInt8
init (fileId: FileId, offset: UInt64, length: UInt32, flags: ReadRequest.Flags = [], minimumLength: UInt32 = 0, remainingBytes: UInt32 = 0, channel: Channel = .NONE) {
self.size = 49
@@ -43,11 +43,7 @@ extension SMB2 {
self.channelBuffer = 0
}
func data() -> NSData {
return encode(read)
}
struct Flags: OptionSetType {
struct Flags: OptionSet {
let rawValue: UInt8
init(rawValue: UInt8) {
@@ -58,26 +54,26 @@ extension SMB2 {
}
}
struct ReadRespone: SMBResponse {
struct ReadRespone: SMBResponseBody {
struct Header {
let size: UInt16
let offset: UInt8
private let reserved: UInt8
fileprivate let reserved: UInt8
let length: UInt32
let remaining: UInt32
private let reserved2: UInt32
fileprivate let reserved2: UInt32
}
let header: ReadRespone.Header
let buffer: NSData
let buffer: Data
init?(data: NSData) {
guard data.length > 16 else {
init?(data: Data) {
guard data.count > 16 else {
return nil
}
self.header = decode(data)
let headersize = sizeof(Header)
self.buffer = data.subdataWithRange(NSRange(location: headersize, length: data.length - headersize))
self.header = data.scanValue()!
let headersize = MemoryLayout<Header>.size
self.buffer = data.subdata(in: headersize..<data.count)
}
}
@@ -89,10 +85,10 @@ extension SMB2 {
// MARK: SMB2 Write
struct WriteRequest: SMBRequest {
struct WriteRequest: SMBRequestBody {
let header: WriteRequest.Header
let channelInfo: ChannelInfo?
let fileData: NSData
let fileData: Data
struct Header {
let size: UInt16
@@ -100,7 +96,7 @@ extension SMB2 {
let length: UInt32
let offset: UInt64
let fileId: FileId
private let _channel: UInt32
fileprivate let _channel: UInt32
var channel: Channel {
return Channel(rawValue: _channel) ?? .NONE
}
@@ -110,29 +106,29 @@ extension SMB2 {
let flags: WriteRequest.Flags
}
init(fileId: FileId, offset: UInt64, remainingBytes: UInt32 = 0, data: NSData, channel: Channel = .NONE, channelInfo: ChannelInfo? = nil, flags: WriteRequest.Flags = []) {
init(fileId: FileId, offset: UInt64, remainingBytes: UInt32 = 0, data: Data, channel: Channel = .NONE, channelInfo: ChannelInfo? = nil, flags: WriteRequest.Flags = []) {
var channelInfoOffset: UInt16 = 0
var channelInfoLength: UInt16 = 0
if channel != .NONE, let channelInfo = channelInfo {
channelInfoOffset = UInt16(sizeof(SMB2.Header.self) + sizeof(WriteRequest.Header.self))
channelInfoLength = UInt16(sizeof(channelInfo.dynamicType))
if channel != .NONE, let _ = channelInfo {
channelInfoOffset = UInt16(MemoryLayout<SMB2.Header>.size + MemoryLayout<WriteRequest.Header>.size)
channelInfoLength = UInt16(MemoryLayout<SMB2.ChannelInfo>.size)
}
let dataOffset = UInt16(sizeof(SMB2.Header.self) + sizeof(WriteRequest.Header.self)) + channelInfoLength
self.header = WriteRequest.Header(size: UInt16(49), dataOffset: dataOffset, length: UInt32(data.length), offset: offset, fileId: fileId, _channel: channel.rawValue, remainingBytes: remainingBytes, channelInfoOffset: channelInfoOffset, channelInfoLength: channelInfoLength, flags: flags)
let dataOffset = UInt16(MemoryLayout<SMB2.Header>.size + MemoryLayout<WriteRequest.Header>.size) + channelInfoLength
self.header = WriteRequest.Header(size: UInt16(49), dataOffset: dataOffset, length: UInt32(data.count), offset: offset, fileId: fileId, _channel: channel.rawValue, remainingBytes: remainingBytes, channelInfoOffset: channelInfoOffset, channelInfoLength: channelInfoLength, flags: flags)
self.channelInfo = channelInfo
self.fileData = data
}
func data() -> NSData {
let result = NSMutableData(data: encode(self.header))
func data() -> Data {
var result = Data(value: self.header)
if let channelInfo = channelInfo {
result.appendData(channelInfo.data())
result.append(channelInfo.data())
}
result.appendData(fileData)
result.append(fileData)
return result
}
struct Flags: OptionSetType {
struct Flags: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
@@ -144,42 +140,30 @@ extension SMB2 {
}
}
struct WriteResponse: SMBResponse {
struct WriteResponse: SMBResponseBody {
let size: UInt16
private let reserved: UInt16
fileprivate let reserved: UInt16
let writtenBytes: UInt32
private let remaining: UInt32
private let channelInfoOffset: UInt16
private let channelInfoLength: UInt16
init?(data: NSData) {
self = decode(data)
}
fileprivate let remaining: UInt32
fileprivate let channelInfoOffset: UInt16
fileprivate let channelInfoLength: UInt16
}
struct ChannelInfo: SMBRequest {
struct ChannelInfo: SMBRequestBody {
let offset: UInt64
let token: UInt32
let length: UInt32
func data() -> NSData {
return encode(data)
}
}
// MARK: SMB2 Lock
struct LockElement: SMBRequest {
struct LockElement: SMBRequestBody {
let offset: UInt64
let length: UInt64
let flags: LockElement.Flags
private let reserved: UInt32
fileprivate let reserved: UInt32
func data() -> NSData {
return encode(self)
}
struct Flags: OptionSetType {
struct Flags: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
@@ -193,7 +177,7 @@ extension SMB2 {
}
}
struct LockRequest: SMBRequest {
struct LockRequest: SMBRequestBody {
let header: LockRequest.Header
let locks: [LockElement]
@@ -202,23 +186,23 @@ extension SMB2 {
self.locks = locks
}
func data() -> NSData {
let result = NSMutableData(data: encode(header))
func data() -> Data {
var result = Data(value: header)
for lock in locks {
result.appendData(encode(lock))
result.append(Data(value: lock))
}
return result
}
struct Header {
let size: UInt16
private let lockCount: UInt16
fileprivate let lockCount: UInt16
let lockSequence: UInt32
let fileId : FileId
}
}
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: NSData) {
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() -> NSData {
return encode(self)
}
}
}
}
+53 -86
View File
@@ -15,28 +15,28 @@ 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?
init(fileId: FileId ,ctlCode: IOCtlCode, requestData: IOCtlRequestProtocol?, flags: IOCtlRequest.Flags = []) {
let offset = requestData != nil ? UInt32(sizeof(SMB2.Header.self) + sizeof(IOCtlRequest.Header.self)) : 0
self.header = Header(size: 57, reserved: 0, _ctlCode: ctlCode.rawValue, fileId: fileId, inputOffset: offset, inputCount: UInt32((requestData?.data().length ?? 0)), maxInputResponse: 0, outputOffset: offset, outputCount: 0, maxOutputResponse: UInt32(Int32.max), flags: flags, reserved2: 0)
let offset = requestData != nil ? UInt32(MemoryLayout<SMB2.Header>.size + MemoryLayout<IOCtlRequest.Header>.size) : 0
self.header = Header(size: 57, reserved: 0, _ctlCode: ctlCode.rawValue, fileId: fileId, inputOffset: offset, inputCount: UInt32((requestData?.data().count ?? 0)), maxInputResponse: 0, outputOffset: offset, outputCount: 0, maxOutputResponse: UInt32(Int32.max), flags: flags, reserved2: 0)
self.requestData = requestData
}
func data() -> NSData {
let result = NSMutableData(data: encode(self.header))
func data() -> Data {
var result = Data(value: self.header)
if let reqData = requestData?.data() {
result.appendData(reqData)
result.append(reqData)
}
return result
}
struct Header {
let size: UInt16
private let reserved: UInt16
private let _ctlCode: UInt32
fileprivate let reserved: UInt16
fileprivate let _ctlCode: UInt32
var ctlCode: IOCtlCode {
return IOCtlCode(rawValue: _ctlCode)!
}
@@ -48,10 +48,10 @@ extension SMB2 {
let outputCount: UInt32
let maxOutputResponse: UInt32
let flags: IOCtlRequest.Flags
private let reserved2: UInt32
fileprivate let reserved2: UInt32
}
struct Flags: OptionSetType {
struct Flags: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
@@ -63,14 +63,14 @@ extension SMB2 {
}
}
struct IOCtlResponse: SMBResponse {
struct IOCtlResponse: SMBResponseBody {
let header: Header
let responseData: IOCtlResponseProtocol?
init?(data: NSData) {
self.header = decode(data)
let responseRange = NSRange(location: Int(self.header.outputOffset - 64), length: Int(self.header.outputCount))
let response = data.subdataWithRange(responseRange)
init?(data: Data) {
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)
@@ -91,8 +91,8 @@ extension SMB2 {
struct Header {
let size: UInt16
private let reserved: UInt16
private let _ctlCode: UInt32
fileprivate let reserved: UInt16
fileprivate let _ctlCode: UInt32
var ctlCode: IOCtlCode {
return IOCtlCode(rawValue: _ctlCode)!
}
@@ -101,8 +101,8 @@ extension SMB2 {
let inputCount: UInt32
let outputOffset: UInt32
let outputCount: UInt32
private let flags: UInt32
private let reserved2: UInt32
fileprivate let flags: UInt32
fileprivate let reserved2: UInt32
}
}
@@ -139,23 +139,19 @@ extension SMB2 {
let chunkCount: UInt32
let chunks: [Chunk]
func data() -> NSData {
let result = NSMutableData(data: encode(sourceKey))
result.appendData(encode(chunkCount))
var reserved: UInt32 = 0
result.appendData(encode(&reserved))
return NSData()
func data() -> Data {
var result = Data(value: sourceKey)
result.append(Data(value: chunkCount))
let reserved: UInt32 = 0
result.append(Data(value: reserved))
return Data()
}
struct Chunk {
let sourceOffset: UInt64
let targetOffset: UInt64
let length: UInt32
private let reserved: UInt32
func data() -> NSData {
return encode(self)
}
fileprivate let reserved: UInt32
}
}
@@ -182,25 +178,17 @@ extension SMB2 {
self.length = length
self.offset = offset
}
func data() -> NSData {
return encode(self)
}
}
struct ResilencyRequest: IOCtlRequestProtocol {
let timeout: UInt32
private let reserved: UInt32
fileprivate let reserved: UInt32
/// The requested time the server holds the file open after a disconnect before releasing it. This time is in milliseconds.
init(timeout: UInt32) {
self.timeout = timeout
self.reserved = 0
}
func data() -> NSData {
return encode(self)
}
}
struct ValidateNegotiateInfo: IOCtlRequestProtocol {
@@ -212,9 +200,9 @@ extension SMB2 {
self.dialects = dialects
}
func data() -> NSData {
let result = NSMutableData(data: encode(self.header))
dialects.forEach { result.appendData(encode($0)) }
func data() -> Data {
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: NSData) {
self = decode(data)
}
}
// SRV_ENUMERATE_SNAPSHOTS
@@ -246,20 +230,21 @@ extension SMB2 {
let returnedCount: UInt32
let snapshots: [SMBTime]
init?(data: NSData) {
self.count = decode(data)
self.returnedCount = decode(data.subdataWithRange(NSRange(location: 4, length: 4)))
init?(data: Data) {
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 = NSDateFormatter()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "'@GMT-'yyyy'.'MM'.'dd'-'HH'.'mm'.'ss"
for i in 0..<Int(returnedCount) {
let offset = 24 + i * 48
if data.length < offset + 48 {
if data.count < offset + 48 {
return nil
}
let datestring = String(data: data.subdataWithRange(NSRange(location: offset, length: 48)), encoding: NSUTF16StringEncoding)
if let datestring = datestring, let date = dateFormatter.dateFromString(datestring) {
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))
}
}
@@ -269,34 +254,23 @@ extension SMB2 {
struct ResumeKey: IOCtlResponseProtocol {
let key: (UInt64, UInt64, UInt64)
private let contextLength: UInt32
private let context: UInt32
init?(data: NSData) {
self = decode(data)
}
fileprivate let contextLength: UInt32
fileprivate let context: UInt32
}
struct ReadHash: IOCtlResponseProtocol {
// TODO: Implement IOCTL READ_HASH
init?(data: NSData) {
self = decode(data)
}
}
struct NetworkInterfaceInfo: IOCtlResponseProtocol {
let items: [NetworkInterfaceInfo.Item]
init?(data: NSData) {
let count = data.length / sizeof(Item)
guard count > 0 else {
return nil
}
init?(data: Data) {
var items = [Item]()
for i in 0..<count {
let itemdata = data.subdataWithRange(NSRange(location: i * sizeof(Item), length: sizeof(Item)))
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
}
@@ -307,10 +281,11 @@ extension SMB2 {
/// specifies the network interface index.
let ifIndex: UInt32
let capability: IOCtlCapabilities
private let reserved: UInt32
fileprivate let reserved: UInt32
/// Speed of the network interface in bits per second
let linkSpeed: UInt64
private 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 = NSData(bytes: &sockaddrStorage, length: 16)
return decode(data)
return Data.mapMemory(from: self.sockaddrStorage)!
}
var sockaddr6: sockaddr_in6 {
var sockaddrStorage = self.sockaddrStorage
let data = NSData(bytes: &sockaddrStorage, length: 28)
return decode(data)
return Data.mapMemory(from: self.sockaddrStorage)!
}
}
}
@@ -351,18 +322,14 @@ extension SMB2 {
let capabilities: IOCtlCapabilities
let guid: uuid_t
let securityMode: UInt16
private let _dialect: UInt16
fileprivate let _dialect: UInt16
var dialect: (major: Int, minor: Int) {
return (major: Int(_dialect & 0xFF), minor: Int(_dialect >> 8))
}
init?(data: NSData) {
self = decode(data)
}
}
}
struct IOCtlCapabilities: OptionSetType {
struct IOCtlCapabilities: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
@@ -386,4 +353,4 @@ extension SMB2 {
case HASH_BASED = 0x00000001
case FILE_BASED = 0x00000002
}
}
}
+14 -21
View File
@@ -11,13 +11,13 @@ import Foundation
extension SMB2 {
// MARK: SMB2 Change Notify
struct ChangeNotifyRequest: SMBRequest {
struct ChangeNotifyRequest: SMBRequestBody {
let size: UInt16
let flags: ChangeNotifyRequest.Flags
let outputBufferLength: UInt32
let fileId: FileId
let completionFilters: CompletionFilter
private let reserved: UInt32
fileprivate let reserved: UInt32
init(fileId: FileId, completionFilters: CompletionFilter, flags: ChangeNotifyRequest.Flags = [], outputBufferLength: UInt32 = 65535) {
self.size = 32
@@ -28,11 +28,7 @@ extension SMB2 {
self.reserved = 0
}
func data() -> NSData {
return encode(self)
}
struct Flags: OptionSetType {
struct Flags: OptionSet {
let rawValue: UInt16
init(rawValue: UInt16) {
@@ -42,7 +38,7 @@ extension SMB2 {
static let WATCH_TREE = Flags(rawValue: 0x0001)
}
struct CompletionFilter: OptionSetType {
struct CompletionFilter: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
@@ -79,30 +75,27 @@ extension SMB2 {
}
}
struct ChangeNotifyResponse: SMBResponse {
struct ChangeNotifyResponse: SMBResponseBody {
let notifications: [(action: FileNotifyAction, fileName: String)]
init?(data: NSData) {
init?(data: Data) {
let maxLoop = 1000
var i = 0
var result = [(action: FileNotifyAction, fileName: String)]()
var offset: UInt32 = 0
var offset = 0
while i < maxLoop {
let actionData = data.subdataWithRange(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.subdataWithRange(NSRange(location: Int(offset + 8), length: 4))
let fileNameLen: UInt32 = decode(fileLenData)
let fileNameData = data.subdataWithRange(NSRange(location: Int(offset + 12), length: Int(12 + fileNameLen)))
let fileName = String(data: fileNameData, encoding: NSUTF16StringEncoding) ?? ""
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.subdataWithRange(NSRange(location: Int(offset), length: 4))
let nextOffset: UInt32 = decode(nextOffsetData)
offset += nextOffset
offset += Int(nextOffset)
if nextOffset == 0 {
break
}
@@ -137,4 +130,4 @@ extension SMB2 {
/// An attempt to tunnel object ID information to a file being renamed failed because the file already has an object ID. This notification is only sent when the directory being monitored is the special directory "\$Extend\$ObjId:$O:$INDEX_ALLOCATION".
case TUNNELLED_ID_COLLISION = 0x0000000B
}
}
}
+95 -109
View File
@@ -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?
@@ -20,17 +20,17 @@ extension SMB2 {
/// - **fileIndex:** The byte offset within the directory, indicating the position at which to resume the enumeration.
init(fileId: FileId, infoClass: FileInformationEnum, flags: Flags, bufferLength: UInt32 = 65535, searchPattern: String? = nil, fileIndex: UInt32 = 0) {
assert(FileInformationEnum.queryDirectory.contains(infoClass), "Invalid FileInformationClass used for QueryDirectoryRequest")
let searchPatternOffset = searchPattern != nil ? sizeof(SMB2.Header.self) + sizeof(QueryDirectoryRequest.Header.self) : 0
let nflags = flags.intersect(fileIndex > 0 ? [.INDEX_SPECIFIED] : [])
let searchPatternLength = searchPattern?.dataUsingEncoding(NSUTF16StringEncoding)?.length ?? 0
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: .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() -> NSData {
let result = NSMutableData(data: encode(header))
if let patternData = searchPattern?.dataUsingEncoding(NSUTF16StringEncoding) {
result.appendData(patternData)
func data() -> Data {
var result = Data(value: header)
if let patternData = searchPattern?.data(using: .utf16) {
result.append(patternData)
}
return result
}
@@ -46,7 +46,7 @@ extension SMB2 {
let bufferLength: UInt32
}
struct Flags: OptionSetType {
struct Flags: OptionSet {
let rawValue: UInt8
init(rawValue: UInt8) {
@@ -60,44 +60,32 @@ extension SMB2 {
}
}
struct QueryDirectoryResponse: SMBResponse {
let buffer: NSData
struct QueryDirectoryResponse: SMBResponseBody {
let buffer: Data
func parseAs(type type: FileInformationEnum) -> [(header: SMB2FilesInformationHeader, fileName: String)] {
func parseAs(type: FileInformationEnum) -> [(header: SMB2FilesInformationHeader, fileName: String)] {
var offset = 0
var result = [(header: SMB2FilesInformationHeader, fileName: String)]()
while true {
let header: SMB2FilesInformationHeader
switch type {
case .FileDirectoryInformation:
let headerData = buffer.subdataWithRange(NSRange(location: offset, length: sizeof(FileDirectoryInformationHeader)))
let h: FileDirectoryInformationHeader = decode(headerData)
header = h
case .FileFullDirectoryInformation:
let headerData = buffer.subdataWithRange(NSRange(location: offset, length: sizeof(FileFullDirectoryInformationHeader)))
let h: FileFullDirectoryInformationHeader = decode(headerData)
header = h
case .FileIdFullDirectoryInformation:
let headerData = buffer.subdataWithRange(NSRange(location: offset, length: sizeof(FileIdFullDirectoryInformationHeader)))
let h: FileIdFullDirectoryInformationHeader = decode(headerData)
header = h
case .FileBothDirectoryInformation:
let headerData = buffer.subdataWithRange(NSRange(location: offset, length: sizeof(FileBothDirectoryInformationHeader)))
let h: FileBothDirectoryInformationHeader = decode(headerData)
header = h
case .FileIdBothDirectoryInformation:
let headerData = buffer.subdataWithRange(NSRange(location: offset, length: sizeof(FileIdBothDirectoryInformationHeader)))
let h: FileIdBothDirectoryInformationHeader = decode(headerData)
header = h
case .FileNamesInformation:
let headerData = buffer.subdataWithRange(NSRange(location: offset, length: sizeof(FileNamesInformationHeader)))
let h: FileNamesInformationHeader = decode(headerData)
header = h
case .fileDirectoryInformation:
header = buffer.scanValue(start: offset) as FileDirectoryInformationHeader!
case .fileFullDirectoryInformation:
header = buffer.scanValue(start: offset) as FileFullDirectoryInformationHeader!
case .fileIdFullDirectoryInformation:
header = buffer.scanValue(start: offset) as FileIdFullDirectoryInformationHeader!
case .fileBothDirectoryInformation:
header = buffer.scanValue(start: offset) as FileBothDirectoryInformationHeader!
case .fileIdBothDirectoryInformation:
header = buffer.scanValue(start: offset) as FileIdBothDirectoryInformationHeader!
case .fileNamesInformation:
header = buffer.scanValue(start: offset) as FileNamesInformationHeader!
default:
return []
}
let fnData = buffer.subdataWithRange(NSRange(location: offset + sizeofValue(header), length: Int(header.fileNameLength)))
let fileName = String(data: fnData, usingEncoding: NSUTF16StringEncoding)
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
@@ -107,21 +95,21 @@ extension SMB2 {
return result
}
init? (data: NSData) {
let offset: UInt16 = decode(data.subdataWithRange(NSRange(location: 2, length: 2)))
let length: UInt32 = decode(data.subdataWithRange(NSRange(location: 4, length: 4)))
guard data.length > Int(offset) + Int(length) else {
init? (data: Data) {
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.subdataWithRange(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: NSData?
let buffer: Data?
init(fileId: FileId, infoClass: FileInformationEnum, outputBufferLength: UInt32 = 65535) {
self.header = Header(size: 41, infoType: 1, infoClass: infoClass.rawValue, outputBufferLength: outputBufferLength, inputBufferOffset: 0, reserved: 0, inputBufferLength: 0, additionalInformation: [], flags: [], fileId: fileId)
@@ -129,23 +117,25 @@ 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.dataUsingEncoding(NSASCIIStringEncoding)!
let strLength = UInt8(strData.length)
let nextOffset = UInt32(4 + 1 + strData.length)
let data = encode(nextOffset).mutableCopy() as! NSMutableData
data.appendData(encode(strLength))
data.appendData(strData)
data.length += 1
let padSize = (data.length) % 4
data.length += padSize
buffer.appendData(data)
guard let strData = ea.data(using: .ascii) else {
continue
}
let strLength = UInt8(strData.count)
let nextOffset = UInt32(4 + 1 + strData.count)
var data = Data(value: nextOffset)
data.append(Data(value: strLength))
data.append(strData)
data.count += 1
let padSize = (data.count) % 4
data.count += padSize
buffer.append(data as Data)
}
let bufferOffset = UInt16(sizeof(SMB2.Header.self) + sizeof(QueryInfoRequest.Header.self))
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.buffer = buffer
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.count), additionalInformation: [], flags: flags, fileId: fileId)
self.buffer = buffer as Data
}
init(fileId: FileId, infoClass: FileSystemInformationEnum, outputBufferLength: UInt32 = 65535) {
@@ -160,11 +150,10 @@ extension SMB2 {
// TODO: Implement QUOTA_INFO init
func data() -> NSData {
let headerData = encode(header)
let result = NSMutableData(data: headerData)
func data() -> Data {
var result = Data(value: header)
if let buffer = buffer {
result.appendData(buffer)
result.append(buffer)
}
return result
}
@@ -175,14 +164,14 @@ extension SMB2 {
let infoClass: UInt8
let outputBufferLength: UInt32
let inputBufferOffset: UInt16
private let reserved: UInt16
fileprivate let reserved: UInt16
let inputBufferLength: UInt32
let additionalInformation: FileSecurityInfo
let flags: QueryInfoRequest.Flags
let fileId: FileId
}
struct Flags: OptionSetType {
struct Flags: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
@@ -195,12 +184,11 @@ extension SMB2 {
}
}
struct QueryInfoResponse: SMBResponse {
let buffer: NSData
struct QueryInfoResponse: SMBResponseBody {
let buffer: Data
init?(data: NSData) {
let structSizeData = data.subdataWithRange(NSRange(location: 0, length: 2))
let structSize: UInt16 = decode(structSizeData)
init?(data: Data) {
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.subdataWithRange(NSRange(location: 4, length: 4))
let length: UInt32 = decode(lengthData)
let length = Int(data.scanValue(start: 4) as UInt32!)
guard data.length >= 8 + Int(length) else {
guard data.count >= 8 + length else {
return nil
}
self.buffer = data.subdataWithRange(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.subdataWithRange(NSRange(location: sizeof(FileAllInformationHeader), length: Int(header.nameLength)))
let name = String(data: nameData, encoding: NSUTF16StringEncoding) ?? ""
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 = UnsafePointer<CChar>(buffer.bytes)
return String(CString: b, encoding: NSUTF16StringEncoding) ?? ""
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.subdataWithRange(NSRange(location: sizeof(FileStreamInformationHeader), length: Int(header.streamNameLength)))
let name = String(data: nameData, encoding: NSUTF16StringEncoding) ?? ""
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.subdataWithRange(NSRange(location: sizeof(FileFsVolumeInformationHeader), length: Int(header.labelLength)))
let name = String(data: nameData, encoding: NSUTF16StringEncoding) ?? ""
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.subdataWithRange(NSRange(location: sizeof(FileFsAttributeInformationHeader), length: Int(header.nameLength)))
let name = String(data: nameData, encoding: NSUTF16StringEncoding) ?? ""
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()!
}
}
}
}
+112 -138
View File
@@ -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,90 +16,90 @@ protocol SMB2FilesInformationHeader: SMBResponse {
extension SMB2 {
enum FileInformationEnum: UInt8 {
case Nil = 0x00
case FileDirectoryInformation = 0x01
case FileFullDirectoryInformation = 0x02
case FileBothDirectoryInformation = 0x03
case FileBasicInformation = 0x04
case FileStandardInformation = 0x05
case FileInternalInformation = 0x06
case FileEaInformation = 0x07
case FileAccessInformation = 0x08
case FileNameInformation = 0x09
case FileRenameInformation = 0x0A
case FileLinkInformation = 0x0B
case FileNamesInformation = 0x0C
case FileDispositionInformation = 0x0D
case FilePositionInformation = 0x0E
case FileFullEaInformation = 0x0F
case FileModeInformation = 0x10
case FileAlignmentInformation = 0x11
case FileAllInformation = 0x12
case FileAllocationInformation = 0x13
case FileEndOfFileInformation = 0x14
case FileAlternateNameInformation = 0x15
case FileStreamInformation = 0x16
case FilePipeInformation = 0x17
case FilePipeLocalInformation = 0x18
case FilePipeRemoteInformation = 0x19
case FileMailslotQueryInformation = 0x1A
case FileMailslotSetInformation = 0x1B
case FileCompressionInformation = 0x1C
case FileObjectIdInformation = 0x1D
case FileCompletionInformation = 0x1E
case FileMoveClusterInformation = 0x1F
case FileQuotaInformation = 0x20
case FileReparsePointInformation = 0x21
case FileNetworkOpenInformation = 0x22
case FileAttributeTagInformation = 0x23
case FileTrackingInformation = 0x24
case FileIdBothDirectoryInformation = 0x25
case FileIdFullDirectoryInformation = 0x26
case FileValidDataLengthInformation = 0x27
case FileShortNameInformation = 0x28
case FileIoCompletionNotificationInformation = 0x29
case FileIoStatusBlockRangeInformation = 0x2A
case FileIoPriorityHintInformation = 0x2B
case FileSfioReserveInformation = 0x2C
case FileSfioVolumeInformation = 0x2D
case FileHardLinkInformation = 0x2E
case FileProcessIdsUsingFileInformation = 0x2F
case FileNormalizedNameInformation = 0x30
case FileNetworkPhysicalNameInformation = 0x31
case FileIdGlobalTxDirectoryInformation = 0x32
case FileIsRemoteDeviceInformation = 0x33
case FileUnusedInformation = 0x34
case FileNumaNodeInformation = 0x35
case FileStandardLinkInformation = 0x36
case FileRemoteProtocolInformation = 0x37
case FileRenameInformationBypassAccessCheck = 0x38
case FileLinkInformationBypassAccessCheck = 0x39
case FileVolumeNameInformation = 0x3A
case FileIdInformation = 0x3B
case FileIdExtdDirectoryInformation = 0x3C
case FileReplaceCompletionInformation = 0x3D
case FileHardLinkFullIdInformation = 0x3E
case FileIdExtdBothDirectoryInformation = 0x3F
case FileMaximumInformation = 0x40
case none = 0x00
case fileDirectoryInformation = 0x01
case fileFullDirectoryInformation = 0x02
case fileBothDirectoryInformation = 0x03
case fileBasicInformation = 0x04
case fileStandardInformation = 0x05
case fileInternalInformation = 0x06
case fileEaInformation = 0x07
case fileAccessInformation = 0x08
case fileNameInformation = 0x09
case fileRenameInformation = 0x0A
case fileLinkInformation = 0x0B
case fileNamesInformation = 0x0C
case fileDispositionInformation = 0x0D
case filePositionInformation = 0x0E
case fileFullEaInformation = 0x0F
case fileModeInformation = 0x10
case fileAlignmentInformation = 0x11
case fileAllInformation = 0x12
case fileAllocationInformation = 0x13
case fileEndOfFileInformation = 0x14
case fileAlternateNameInformation = 0x15
case fileStreamInformation = 0x16
case filePipeInformation = 0x17
case filePipeLocalInformation = 0x18
case filePipeRemoteInformation = 0x19
case fileMailslotQueryInformation = 0x1A
case fileMailslotSetInformation = 0x1B
case fileCompressionInformation = 0x1C
case fileObjectIdInformation = 0x1D
case fileCompletionInformation = 0x1E
case fileMoveClusterInformation = 0x1F
case fileQuotaInformation = 0x20
case fileReparsePointInformation = 0x21
case fileNetworkOpenInformation = 0x22
case fileAttributeTagInformation = 0x23
case fileTrackingInformation = 0x24
case fileIdBothDirectoryInformation = 0x25
case fileIdFullDirectoryInformation = 0x26
case fileValidDataLengthInformation = 0x27
case fileShortNameInformation = 0x28
case fileIoCompletionNotificationInformation = 0x29
case fileIoStatusBlockRangeInformation = 0x2A
case fileIoPriorityHintInformation = 0x2B
case fileSfioReserveInformation = 0x2C
case fileSfioVolumeInformation = 0x2D
case fileHardLinkInformation = 0x2E
case fileProcessIdsUsingFileInformation = 0x2F
case fileNormalizedNameInformation = 0x30
case fileNetworkPhysicalNameInformation = 0x31
case fileIdGlobalTxDirectoryInformation = 0x32
case fileIsRemoteDeviceInformation = 0x33
case fileUnusedInformation = 0x34
case fileNumaNodeInformation = 0x35
case fileStandardLinkInformation = 0x36
case fileRemoteProtocolInformation = 0x37
case fileRenameInformationBypassAccessCheck = 0x38
case fileLinkInformationBypassAccessCheck = 0x39
case fileVolumeNameInformation = 0x3A
case fileIdInformation = 0x3B
case fileIdExtdDirectoryInformation = 0x3C
case fileReplaceCompletionInformation = 0x3D
case fileHardLinkFullIdInformation = 0x3E
case fileIdExtdBothDirectoryInformation = 0x3F
case fileMaximumInformation = 0x40
static let queryDirectory: [FileInformationEnum] = [.FileDirectoryInformation, .FileFullDirectoryInformation, .FileIdFullDirectoryInformation, .FileBothDirectoryInformation, .FileIdBothDirectoryInformation, .FileNamesInformation]
static let queryDirectory: [FileInformationEnum] = [.fileDirectoryInformation, .fileFullDirectoryInformation, .fileIdFullDirectoryInformation, .fileBothDirectoryInformation, .fileIdBothDirectoryInformation, .fileNamesInformation]
static let queryInfoFile: [FileInformationEnum] = [.FileAccessInformation, .FileAlignmentInformation, .FileAllInformation, .FileAlternateNameInformation, .FileAttributeTagInformation, .FileBasicInformation, .FileCompressionInformation, FileEaInformation, .FileFullEaInformation, .FileInternalInformation, .FileModeInformation, .FileNetworkOpenInformation, .FilePipeInformation, .FilePipeLocalInformation, .FilePipeRemoteInformation, .FilePositionInformation, .FileStandardInformation, .FileStreamInformation]
static let queryInfoFile: [FileInformationEnum] = [.fileAccessInformation, .fileAlignmentInformation, .fileAllInformation, .fileAlternateNameInformation, .fileAttributeTagInformation, .fileBasicInformation, .fileCompressionInformation, fileEaInformation, .fileFullEaInformation, .fileInternalInformation, .fileModeInformation, .fileNetworkOpenInformation, .filePipeInformation, .filePipeLocalInformation, .filePipeRemoteInformation, .filePositionInformation, .fileStandardInformation, .fileStreamInformation]
}
enum FileSystemInformationEnum: UInt8 {
case Nil = 0
case FileFsAttributeInformation
case FileFsControlInformation
case FileFsDeviceInformation
case FileFsFullSizeInformation
case FileFsObjectIdInformation
case FileFsSectorSizeInformation
case FileFsSizeInformation
case FileFsVolumeInformation
case none = 0
case fileFsAttributeInformation
case fileFsControlInformation
case fileFsDeviceInformation
case fileFsFullSizeInformation
case fileFsObjectIdInformation
case fileFsSectorSizeInformation
case fileFsSizeInformation
case fileFsVolumeInformation
}
struct FileSecurityInfo: OptionSetType {
struct FileSecurityInfo: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
@@ -127,10 +127,6 @@ extension SMB2 {
let allocationSize: UInt64
let fileAttributes: FileAttributes
let fileNameLength : UInt32
init?(data: NSData) {
self = decode(data)
}
}
struct FileFullDirectoryInformationHeader: SMB2FilesInformationHeader {
@@ -145,10 +141,6 @@ extension SMB2 {
let fileAttributes: FileAttributes
let fileNameLength : UInt32
let extendedAttributesSize: UInt32
init?(data: NSData) {
self = decode(data)
}
}
struct FileIdFullDirectoryInformationHeader: SMB2FilesInformationHeader {
@@ -163,12 +155,8 @@ extension SMB2 {
let fileAttributes: FileAttributes
let fileNameLength : UInt32
let extendedAttributesSize: UInt32
private let reserved: UInt32
fileprivate let reserved: UInt32
let fileId: FileId
init?(data: NSData) {
self = decode(data)
}
}
struct FileBothDirectoryInformationHeader: SMB2FilesInformationHeader {
@@ -183,18 +171,13 @@ extension SMB2 {
let fileAttributes: FileAttributes
let fileNameLength : UInt32
let extendedAttributesSize: UInt32
private let shortNameLen: UInt8
private let reserved: UInt8
private let _shortName: FileShortNameType
fileprivate let shortNameLen: UInt8
fileprivate let reserved: UInt8
fileprivate let _shortName: FileShortNameType
var shortName: String? {
let s = encode(_shortName)
let d = NSMutableData(data: s)
d.length = Int(shortNameLen)
return String(data: d, encoding: NSUTF16StringEncoding)
}
init?(data: NSData) {
self = decode(data)
var data = Data(value: _shortName)
data.count = Int(shortNameLen)
return String(data: data, encoding: .utf16)
}
}
@@ -210,31 +193,22 @@ extension SMB2 {
let fileAttributes: FileAttributes
let fileNameLength : UInt32
let extendedAttributesSize: UInt32
private let shortNameLen: UInt8
private let reserved: UInt8
private let _shortName: FileShortNameType
fileprivate let shortNameLen: UInt8
fileprivate let reserved: UInt8
fileprivate let _shortName: FileShortNameType
var shortName: String? {
let s = encode(_shortName)
let d = NSMutableData(data: s)
d.length = Int(shortNameLen)
return String(data: d, encoding: NSUTF16StringEncoding)
var data = Data(value: _shortName)
data.count = Int(shortNameLen)
return String(data: data, encoding: .utf16)
}
private let reserved2: UInt16
fileprivate let reserved2: UInt16
let fileId : FileId
init?(data: NSData) {
self = decode(data)
}
}
struct FileNamesInformationHeader: SMB2FilesInformationHeader {
let nextEntryOffset: UInt32
let fileIndex: UInt32
let fileNameLength : UInt32
init?(data: NSData) {
self = decode(data)
}
}
typealias FileShortNameType = (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
@@ -246,7 +220,7 @@ extension SMB2 {
}
struct FileAlignmentInformation {
private let _alignment: UInt32
fileprivate let _alignment: UInt32
var alignmentLength: UInt32 {
return _alignment + 1
}
@@ -275,7 +249,7 @@ extension SMB2 {
let lastWriteTime: SMBTime
let changeTime: SMBTime
let fileAttributes: FileAttributes
private let reserved: UInt32 = 0
fileprivate let reserved: UInt32 = 0
}
struct FileCompressionInformation {
@@ -285,7 +259,7 @@ extension SMB2 {
let compressionUnitShift: UInt8
let chunkShift: UInt8
let clusterShift: UInt8
private let reserved: (UInt8, UInt16)
fileprivate let reserved: (UInt8, UInt16)
}
struct FileEaInformation {
@@ -303,7 +277,7 @@ extension SMB2 {
struct FileModeInformation {
let mode: Mode
struct Mode: OptionSetType {
struct Mode: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
@@ -325,15 +299,15 @@ extension SMB2 {
let lastWriteTime: SMBTime
let changeTime: SMBTime
let fileAttributes: FileAttributes
private let reserved: UInt32
fileprivate let reserved: UInt32
}
struct FilePipeInformation {
private let _readMode: UInt32
fileprivate let _readMode: UInt32
var readMode: ReadMode {
return ReadMode(rawValue: _readMode) ?? .BYTE_STREAM_MODE
}
private let _completionMode: UInt32
fileprivate let _completionMode: UInt32
var completionMode: CompletionMode {
return CompletionMode(rawValue: _completionMode) ?? .QUEUE_OPERATION
}
@@ -350,11 +324,11 @@ extension SMB2 {
}
struct FilePipeLocalInformation {
private let _namedPipeType: UInt32
fileprivate let _namedPipeType: UInt32
var namedPipeType: Type {
return Type(rawValue: _namedPipeType) ?? .BYTE_STREAM_TYPE
}
private let _namedPipeConfiguration: UInt32
fileprivate let _namedPipeConfiguration: UInt32
var namedPipeConfiguration: Configuration {
return Configuration(rawValue: _namedPipeConfiguration) ?? .INBOUND
}
@@ -364,16 +338,16 @@ extension SMB2 {
let readDataAvailable: UInt32
let outboundQuota: UInt32
let writeQuotaAvailable: UInt32
private let _namedPipeState: UInt32
fileprivate let _namedPipeState: UInt32
var namedPipeState: State {
return State(rawValue: _namedPipeState) ?? .DISCONNECTED_STATE
}
private let _namedPipeEnd: UInt32
fileprivate let _namedPipeEnd: UInt32
var namedPipeEnd: End {
return End(rawValue: _namedPipeEnd) ?? .CLIENT_END
}
enum Type: UInt32 {
enum `Type`: UInt32 {
case BYTE_STREAM_TYPE = 0x00000000
case MESSAGE_TYPE = 0x00000001
}
@@ -412,7 +386,7 @@ extension SMB2 {
let numberOfLinks: UInt32
let deletePending: Bool
let directory: Bool
private let reserved: UInt16
fileprivate let reserved: UInt16
}
struct FileStreamInformationHeader {
@@ -438,7 +412,7 @@ extension SMB2 {
}
struct FileFsDeviceInformation {
private let _deviceType: UInt32
fileprivate let _deviceType: UInt32
var deviceType: DeviceType {
return DeviceType(rawValue: _deviceType) ?? .DISK
}
@@ -449,7 +423,7 @@ extension SMB2 {
case DISK = 0x00000007
}
struct Charactristics: OptionSetType {
struct Charactristics: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
@@ -488,7 +462,7 @@ extension SMB2 {
let maximumFileNameLength: Int32
let nameLength: UInt32
struct Attributes: OptionSetType {
struct Attributes: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
@@ -551,9 +525,9 @@ extension SMB2 {
let defaultQuotaThreshold: UInt64
let defaultQuotaLimit: UInt64
let flags: Flags
private let padding: UInt32 = 0
fileprivate let padding: UInt32 = 0
struct Flags: OptionSetType {
struct Flags: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
@@ -603,7 +577,7 @@ extension SMB2 {
let byteOffsetForSectorAlignment: UInt32
let byteOffsetForPartitionAlignment: UInt32
struct Flags: OptionSetType {
struct Flags: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
@@ -620,4 +594,4 @@ extension SMB2 {
static let TRIM_ENABLED = Flags(rawValue: 0x00000010)
}
}
}
}
+56 -74
View File
@@ -11,47 +11,45 @@ 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: NSData)]
let contexts: [(type: NegotiateContextType, data: Data)]
init(header: NegotiateRequest.Header, dialects: [UInt16] = [0x0202], contexts: [(type: NegotiateContextType, data: NSData)] = []) {
init(header: NegotiateRequest.Header, dialects: [UInt16] = [0x0202], contexts: [(type: NegotiateContextType, data: Data)] = []) {
self.header = header
self.dialects = dialects
self.contexts = contexts
}
init(dialects: [UInt16] = [0x0202], contexts: [(type: NegotiateContextType, data: NSData)] = [],capabilities: GlobalCapabilities = [], clientStartTime: SMBTime? = nil, guid: uuid_t? = nil, signing: NegotiateSinging = [.ENABLED]) {
init(dialects: [UInt16] = [0x0202], contexts: [(type: NegotiateContextType, data: Data)] = [],capabilities: GlobalCapabilities = [], clientStartTime: SMBTime? = nil, guid: uuid_t? = nil, signing: NegotiateSinging = [.ENABLED]) {
self.header = Header(capabilities: capabilities, clientStartTime: clientStartTime, guid: guid, signing: signing)
self.dialects = dialects
self.contexts = contexts
}
func data() -> NSData {
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.appendBytes(&dialect, length: 2)
dialectData.append(UnsafeBufferPointer(start: &dialect, count: 2))
}
let pad = ((1024 - dialectData.length) % 8)
dialectData.increaseLengthBy(pad)
header.contextOffset = UInt32(sizeof(header.dynamicType.self)) + 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.appendBytes(&contextType, length: 2)
var dataLen = UInt16(context.data.length)
contextData.increaseLengthBy(4)
contextData.appendBytes(&dataLen, length: 2)
contextData.append(Data(value: context.type.rawValue))
contextData.count += 4
contextData.append(Data(value: UInt16(context.data.count)))
}
let result = NSMutableData(data: encode(&header))
result.appendData(dialectData)
result.appendData(contextData)
var result = Data(value: header)
result.append(dialectData as Data)
result.append(contextData as Data)
return result
}
@@ -59,12 +57,12 @@ extension SMB2 {
var size: UInt16
var dialectCount: UInt16
let signing: NegotiateSinging
private let reserved: UInt16
fileprivate let reserved: UInt16
let capabilities: GlobalCapabilities
let guid: uuid_t
var contextOffset: UInt32
var contextCount: UInt16
private let reserved2: UInt16
fileprivate let reserved2: UInt16
var clientStartTime: SMBTime {
let time = Int64(contextOffset) + (Int64(contextCount) << 32) + (Int64(contextCount) << 48)
return SMBTime(time: time)
@@ -91,28 +89,28 @@ extension SMB2 {
}
}
struct NegotiateResponse: SMBResponse {
struct NegotiateResponse: SMBResponseBody {
let header: NegotiateResponse.Header
let buffer: NSData?
let contexts: [(type: NegotiateContextType, data: NSData)]
let buffer: Data?
let contexts: [(type: NegotiateContextType, data: Data)]
init? (data: NSData) {
if data.length < 64 {
init? (data: Data) {
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) - sizeof(SMB2.Header.self)
let bufOffset = Int(self.header.bufferOffset) - MemoryLayout<SMB2.Header>.size
let bufLen = Int(self.header.bufferLength)
if bufOffset > 0 && bufLen > 0 && data.length >= bufOffset + bufLen {
self.buffer = data.subdataWithRange(NSRange(location: bufOffset, length: bufLen))
if bufOffset > 0 && bufLen > 0 && data.count >= bufOffset + bufLen {
self.buffer = data.subdata(in: bufOffset..<(bufOffset + bufLen))
} else {
self.buffer = nil
}
let contextCount = Int(self.header.contextCount)
let contextOffset = Int(self.header.contextOffset) - sizeof(SMB2.Header.self)
let contextOffset = Int(self.header.contextOffset) - MemoryLayout<SMB2.Header>.size
if contextCount > 0 && contextOffset > 0 {
// TODO: NegotiateResponse context support for SMB3
self.contexts = []
@@ -139,7 +137,7 @@ extension SMB2 {
}
}
struct NegotiateSinging: OptionSetType {
struct NegotiateSinging: OptionSet {
let rawValue: UInt16
init(rawValue: UInt16) {
@@ -149,7 +147,7 @@ extension SMB2 {
static let REQUIRED = NegotiateSinging(rawValue: 0x0002)
}
struct NegotiateContextType: OptionSetType {
struct NegotiateContextType: OptionSet {
let rawValue: UInt16
init(rawValue: UInt16) {
@@ -159,7 +157,7 @@ extension SMB2 {
static let ENCRYPTION_CAPABILITIES = NegotiateContextType(rawValue: 0x0002)
}
struct GlobalCapabilities: OptionSetType {
struct GlobalCapabilities: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
@@ -176,27 +174,27 @@ extension SMB2 {
// MARK: SMB2 Session Setup
struct SessionSetupRequest: SMBRequest {
struct SessionSetupRequest: SMBRequestBody {
let header: SessionSetupRequest.Header
let buffer: NSData?
let buffer: Data?
init(header: SessionSetupRequest.Header, buffer: NSData) {
init(header: SessionSetupRequest.Header, buffer: Data) {
self.header = header
self.buffer = buffer
}
init(sessionId: UInt64 = 0, flags: SessionSetupRequest.Flags = [], singing: SessionSetupSinging = [.ENABLED], capabilities: GlobalCapabilities = [], securityData: NSData? = nil) {
init(sessionId: UInt64 = 0, flags: SessionSetupRequest.Flags = [], singing: SessionSetupSinging = [.ENABLED], capabilities: GlobalCapabilities = [], securityData: Data? = nil) {
self.header = Header(sessionId: sessionId, flags: flags, singing: singing, capabilities: capabilities)
self.buffer = securityData
}
func data() -> NSData {
func data() -> Data {
var header = self.header
header.bufferOffset = UInt16(sizeof(SMB2.Header.self) + sizeof(SessionSetupRequest.Header.self))
header.bufferLength = UInt16(buffer?.length ?? 0)
let result = NSMutableData(data: encode(&header))
header.bufferOffset = UInt16(MemoryLayout<SMB2.Header>.size + MemoryLayout<SessionSetupRequest.Header>.size)
header.bufferLength = UInt16(buffer?.count ?? 0)
var result = Data(value: header)
if let buffer = self.buffer {
result.appendData(buffer)
result.append(buffer)
}
return result
}
@@ -206,7 +204,7 @@ extension SMB2 {
let flags: SessionSetupRequest.Flags
let signing: SessionSetupSinging
let capabilities: GlobalCapabilities
private let channel: UInt32
fileprivate let channel: UInt32
var bufferOffset: UInt16
var bufferLength: UInt16
let sessionId: UInt64
@@ -224,7 +222,7 @@ extension SMB2 {
}
/// Works the client implements the SMB 3.x dialect family
struct Flags: OptionSetType {
struct Flags: OptionSet {
let rawValue: UInt8
init(rawValue: UInt8) {
@@ -235,22 +233,22 @@ extension SMB2 {
}
}
struct SessionSetupResponse: SMBResponse {
struct SessionSetupResponse: SMBResponseBody {
let header: SessionSetupResponse.Header
let buffer: NSData?
let buffer: Data?
init? (data: NSData) {
if data.length < 64 {
init? (data: Data) {
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) - sizeof(SMB2.Header.self)
let bufOffset = Int(self.header.bufferOffset) - MemoryLayout<SMB2.Header>.size
let bufLen = Int(self.header.bufferLength)
if bufOffset > 0 && bufLen > 0 && data.length >= bufOffset + bufLen {
self.buffer = data.subdataWithRange(NSRange(location: bufOffset, length: bufLen))
if bufOffset > 0 && bufLen > 0 && data.count >= bufOffset + bufLen {
self.buffer = data.subdata(in: bufOffset..<(bufOffset + bufLen))
} else {
self.buffer = nil
}
@@ -263,7 +261,7 @@ extension SMB2 {
let bufferLength: UInt16
}
struct Flags: OptionSetType {
struct Flags: OptionSet {
let rawValue: UInt16
init(rawValue: UInt16) {
@@ -276,7 +274,7 @@ extension SMB2 {
}
}
struct SessionSetupSinging: OptionSetType {
struct SessionSetupSinging: OptionSet {
let rawValue: UInt8
init(rawValue: UInt8) {
@@ -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: NSData) {
self = decode(data)
}
func data() -> NSData {
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: NSData) {
self = decode(data)
}
func data() -> NSData {
return encode(self)
}
}
}
}
+10 -14
View File
@@ -10,37 +10,33 @@ import Foundation
extension SMB2 {
// MARK: SMB2 Set Info
struct SetInfoRequest: SMBRequest {
struct SetInfoRequest: SMBRequestBody {
let header: Header
let buffer: NSData?
let buffer: Data?
func data() -> NSData {
return NSData()
func data() -> Data {
var result = Data(value: header)
result.append(buffer ?? Data())
return result
}
struct Header {
let size: UInt16 = 33
let infoType: UInt8
private let infoClass: UInt8
fileprivate let infoClass: UInt8
let bufferLength: UInt32
let bufferOffset: UInt16
private let reserved: UInt16
fileprivate let reserved: UInt16
let securityInfo: FileSecurityInfo
let fileId: FileId
}
}
struct SetInfoResponse: SMBResponse {
struct SetInfoResponse: SMBResponseBody {
let size: UInt16
init() {
self.size = 2
}
init? (data: NSData) {
self = decode(data)
}
}
}
}
+17 -32
View File
@@ -11,9 +11,9 @@ import Foundation
extension SMB2 {
// MARK: SMB2 Tree Connect
struct TreeConnectRequest: SMBRequest {
struct TreeConnectRequest: SMBRequestBody {
let header: TreeConnectRequest.Header
let buffer: NSData?
let buffer: Data?
var path: String {
return ""
}
@@ -22,21 +22,21 @@ extension SMB2 {
}
init? (header: TreeConnectRequest.Header, host: String, share: String) {
guard !host.containsString("/") && !share.containsString("/") else {
guard !host.contains("/") && !share.contains("/") else {
return nil
}
self.header = header
let path = "\\\\\(host)\\\(share)"
self.buffer = path.dataUsingEncoding(NSUTF16StringEncoding)
self.buffer = path.data(using: .utf16)
}
func data() -> NSData {
func data() -> Data {
var header = self.header
header.pathOffset = UInt16(sizeof(SMB2.Header.self) + sizeof(TreeConnectRequest.Header.self))
header.pathLength = UInt16(buffer?.length ?? 0)
let result = NSMutableData(data: encode(&header))
header.pathOffset = UInt16(MemoryLayout<SMB2.Header>.size + MemoryLayout<TreeConnectRequest.Header>.size)
header.pathLength = UInt16(buffer?.count ?? 0)
var result = Data(value: header)
if let buffer = self.buffer {
result.appendData(buffer)
result.append(buffer)
}
return result
}
@@ -55,7 +55,7 @@ extension SMB2 {
}
}
struct Flags: OptionSetType {
struct Flags: OptionSet {
let rawValue: UInt16
init(rawValue: UInt16) {
@@ -66,24 +66,17 @@ extension SMB2 {
}
}
struct TreeConnectResponse: SMBResponse {
struct TreeConnectResponse: SMBResponseBody {
let size: UInt16 // = 16
private let _type: UInt8
fileprivate let _type: UInt8
var type: ShareType {
return ShareType(rawValue: _type) ?? .UNKNOWN
}
private let reserved: UInt8
fileprivate let reserved: UInt8
let flags: TreeConnectResponse.ShareFlags
let capabilities: TreeConnectResponse.Capabilities
let maximalAccess: FileAccessMask
init? (data: NSData) {
if data.length != 16 {
return nil
}
self = decode(data)
}
enum ShareType: UInt8 {
case UNKNOWN = 0x00
case DISK = 0x01
@@ -91,7 +84,7 @@ extension SMB2 {
case PRINT = 0x03
}
struct ShareFlags: OptionSetType {
struct ShareFlags: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
@@ -114,7 +107,7 @@ extension SMB2 {
static let ENCRYPT_DATA = ShareFlags(rawValue: 0x00008000)
}
struct Capabilities: OptionSetType {
struct Capabilities: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
@@ -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: NSData) {
self = decode(data)
}
func data() -> NSData {
return encode(self)
}
}
}
}
+44 -7
View File
@@ -8,6 +8,40 @@
import Foundation
extension Data {
init<T>(value: T) {
var value = value
self = Data(buffer: UnsafeBufferPointer(start: &value, count: 1))
}
func scanValue<T>() -> T? {
guard MemoryLayout<T>.size <= self.count else { return nil }
return self.withUnsafeBytes { $0.pointee }
}
func scanValue<T>(start: Int) -> T? {
let length = MemoryLayout<T>.size
guard self.count >= start + length else { return nil }
return self.subdata(in: start..<start+length).withUnsafeBytes { $0.pointee }
}
func scanString(start: Int = 0, length: Int, encoding: String.Encoding) -> String? {
guard self.count >= start + length else { return nil }
return String(data: self.subdata(in: start..<start+length), encoding: encoding)
}
static func mapMemory<T, U>(from: T) -> U? {
guard MemoryLayout<T>.size >= MemoryLayout<U>.size else { return nil }
let data = Data(value: from)
return data.scanValue()
}
}
protocol FileProviderSMBHeader {
var protocolID: UInt32 { get }
static var protocolConst: UInt32 { get }
}
// SMB2 Types
struct SMB2 {
struct Header: FileProviderSMBHeader { // 64 bytes
@@ -19,13 +53,13 @@ struct SMB2 {
// error messages from the server to the client
let status: UInt32
enum StatusSeverity: UInt8 {
case Success = 0, Information, Warning, Error
case success = 0, information, warning, error
}
var statusDetails: (severity: StatusSeverity, customer: Bool, facility: UInt16, code: UInt16) {
let severity = StatusSeverity(rawValue: UInt8(status >> 30))!
return (severity, status & 0x20000000 != 0, UInt16((status & 0x0FFF0000) >> 16), UInt16(status & 0x0000FFFF))
}
private let _command: UInt16
fileprivate let _command: UInt16
var command: Command {
get {
return Command(rawValue: _command) ?? .INVALID
@@ -35,7 +69,7 @@ struct SMB2 {
let flags: Flags
var nextCommand: UInt32
let messageId: UInt64
private let reserved: UInt32
fileprivate let reserved: UInt32
let treeId: UInt32
var asyncId: UInt64 {
get {
@@ -45,8 +79,9 @@ struct SMB2 {
let sessionId: UInt64
let signature: (UInt64, UInt64)
// codebeat:disable[ARITY]
init(command: Command, status: NTStatus = .SUCCESS, creditCharge: UInt16 = 0, creditRequestResponse: UInt16, flags: Flags = [], nextCommand: UInt32 = 0, messageId: UInt64, treeId: UInt32, sessionId: UInt64, signature: (UInt64, UInt64) = (0, 0)) {
self.protocolID = self.dynamicType.protocolConst
self.protocolID = type(of: self).protocolConst
self.size = 64
self.status = status.rawValue
self._command = command.rawValue
@@ -62,7 +97,7 @@ struct SMB2 {
}
init(asyncCommand: Command, status: NTStatus = .SUCCESS, creditCharge: UInt16 = 0, creditRequestResponse: UInt16, flags: Flags = [.ASYNC_COMMAND], nextCommand: UInt32 = 0, messageId: UInt64, asyncId: UInt64, sessionId: UInt64, signature: (UInt64, UInt64) = (0, 0)) {
self.protocolID = self.dynamicType.protocolConst
self.protocolID = type(of: self).protocolConst
self.size = 64
self.status = status.rawValue
self._command = asyncCommand.rawValue
@@ -76,9 +111,10 @@ struct SMB2 {
self.sessionId = sessionId
self.signature = signature
}
// codebeat:enable[ARITY]
}
struct Flags: OptionSetType {
struct Flags: OptionSet {
var rawValue: UInt32
init(rawValue: UInt32) {
@@ -98,7 +134,7 @@ struct SMB2 {
static let ASYNC_COMMAND = Flags(rawValue: 0x00000002)
static let RELATED_OPERATIONS = Flags(rawValue: 0x00000004)
static let SIGNED = Flags(rawValue: 0x00000008)
private static let PRIORITY_MASK = Flags(rawValue: 0x00000070)
fileprivate static let PRIORITY_MASK = Flags(rawValue: 0x00000070)
static let DFS_OPERATIONS = Flags(rawValue: 0x10000000)
static let REPLAY_OPERATION = Flags(rawValue: 0x20000000)
}
@@ -128,4 +164,5 @@ struct SMB2 {
// MARK: SMB2 Oplock Break
}
+36 -36
View File
@@ -10,7 +10,7 @@ import Foundation
/// Error Types and Description
public enum NTStatus: UInt32, ErrorType, CustomStringConvertible {
enum NTStatus: UInt32, Error, CustomStringConvertible {
case SUCCESS = 0x00000000
case NOT_IMPLEMENTED = 0xC0000002
case INVALID_DEVICE_REQUEST = 0xC0000010
@@ -133,76 +133,76 @@ public enum NTStatus: UInt32, ErrorType, CustomStringConvertible {
public var description: String {
switch self {
case NOT_IMPLEMENTED, INVALID_DEVICE_REQUEST, ILLEGAL_FUNCTION:
case .NOT_IMPLEMENTED, .INVALID_DEVICE_REQUEST, .ILLEGAL_FUNCTION:
return "Invalid Function."
case NO_SUCH_FILE, NO_SUCH_DEVICE, OBJECT_NAME_NOT_FOUND:
case .NO_SUCH_FILE, .NO_SUCH_DEVICE, .OBJECT_NAME_NOT_FOUND:
return "File not found."
case OBJECT_PATH_INVALID, OBJECT_PATH_NOT_FOUND, OBJECT_PATH_SYNTAX_BAD, DFS_EXIT_PATH_FOUND, REDIRECTOR_NOT_STARTED:
case .OBJECT_PATH_INVALID, .OBJECT_PATH_NOT_FOUND, .OBJECT_PATH_SYNTAX_BAD, .DFS_EXIT_PATH_FOUND, .REDIRECTOR_NOT_STARTED:
return "A component in the path prefix is not a directory."
case TOO_MANY_OPENED_FILES:
case .TOO_MANY_OPENED_FILES:
return "Too many open files. No FIDs are available."
case ACCESS_DENIED, INVALID_LOCK_SEQUENCE, INVALID_VIEW_SIZE, ALREADY_COMMITTED, PORT_CONNECTION_REFUSED, THREAD_IS_TERMINATING, DELETE_PENDING, PRIVILEGE_NOT_HELD, LOGON_FAILURE, FILE_IS_A_DIRECTORY, FILE_RENAMED, PROCESS_IS_TERMINATING, CANNOT_DELETE, FILE_DELETED:
case .ACCESS_DENIED, .INVALID_LOCK_SEQUENCE, .INVALID_VIEW_SIZE, .ALREADY_COMMITTED, .PORT_CONNECTION_REFUSED, .THREAD_IS_TERMINATING, .DELETE_PENDING, .PRIVILEGE_NOT_HELD, .LOGON_FAILURE, .FILE_IS_A_DIRECTORY, .FILE_RENAMED, .PROCESS_IS_TERMINATING, .CANNOT_DELETE, .FILE_DELETED:
return "Access denied."
case SMB_BAD_FID, INVALID_HANDLE, OBJECT_TYPE_MISMATCH, PORT_DISCONNECTED, INVALID_PORT_HANDLE, FILE_CLOSED, HANDLE_NOT_CLOSABLE:
case .SMB_BAD_FID, .INVALID_HANDLE, .OBJECT_TYPE_MISMATCH, .PORT_DISCONNECTED, .INVALID_PORT_HANDLE, .FILE_CLOSED, .HANDLE_NOT_CLOSABLE:
return "Invalid FID."
case SECTION_TOO_BIG, TOO_MANY_PAGING_FILES, INSUFF_SERVER_RESOURCES:
case .SECTION_TOO_BIG, .TOO_MANY_PAGING_FILES, .INSUFF_SERVER_RESOURCES:
return "Insufficient server memory to perform the requested operation."
case OS2_INVALID_ACCESS:
case .OS2_INVALID_ACCESS:
return "Invalid open mode."
case DATA_ERROR:
case .DATA_ERROR:
return "Bad data. (May be generated by IOCTL calls on the server.)"
case DIRECTORY_NOT_EMPTY:
case .DIRECTORY_NOT_EMPTY:
return "Remove of directory failed because it was not empty."
case NOT_SAME_DEVICE:
case .NOT_SAME_DEVICE:
return "A file system operation (such as a rename) across two devices was attempted."
case NO_MORE_FILES:
case .NO_MORE_FILES:
return "No (more) files found following a file search command."
case UNSUCCESSFUL:
case .UNSUCCESSFUL:
return "General error."
case SHARING_VIOLATION:
case .SHARING_VIOLATION:
return "Sharing violation. A requested open mode conflicts with the sharing mode of an existing file handle."
case FILE_LOCK_CONFLICT, LOCK_NOT_GRANTED:
case .FILE_LOCK_CONFLICT, .LOCK_NOT_GRANTED:
return "A lock request specified an invalid locking mode, or conflicted with an existing file lock."
case END_OF_FILE:
case .END_OF_FILE:
return "Attempted to read beyond the end of the file."
case NOT_SUPPORTED:
case .NOT_SUPPORTED:
return "This command is not supported by the server."
case OBJECT_NAME_COLLISION:
case .OBJECT_NAME_COLLISION:
return "An attempt to create a file or directory failed because an object with the same pathname already exists."
case INVALID_PARAMETER:
case .INVALID_PARAMETER:
return "A parameter supplied with the message is invalid."
case OS2_INVALID_LEVEL:
case .OS2_INVALID_LEVEL:
return "Invalid information level."
case OS2_NEGATIVE_SEEK:
case .OS2_NEGATIVE_SEEK:
return "An attempt was made to seek to a negative absolute offset within a file."
case RANGE_NOT_LOCKED:
case .RANGE_NOT_LOCKED:
return "The byte range specified in an unlock request was not locked."
case OS2_NO_MORE_SIDS:
case .OS2_NO_MORE_SIDS:
return "Maximum number of searches has been exhausted."
case OS2_CANCEL_VIOLATION:
case .OS2_CANCEL_VIOLATION:
return "No lock request was outstanding for the supplied cancel region."
case OS2_ATOMIC_LOCKS_NOT_SUPPORTED:
case .OS2_ATOMIC_LOCKS_NOT_SUPPORTED:
return "The file system does not support atomic changes to the lock type."
case INVALID_INFO_CLASS, INVALID_PIPE_STATE, INVALID_READ_MODE:
case .INVALID_INFO_CLASS, .INVALID_PIPE_STATE, .INVALID_READ_MODE:
return "Invalid named pipe."
case OS2_CANNOT_COPY:
case .OS2_CANNOT_COPY:
return "The copy functions cannot be used."
case INSTANCE_NOT_AVAILABLE, PIPE_NOT_AVAILABLE, PIPE_BUSY:
case .INSTANCE_NOT_AVAILABLE, .PIPE_NOT_AVAILABLE, .PIPE_BUSY:
return "All instances of the designated named pipe are busy."
case PIPE_CLOSING, PIPE_EMPTY:
case .PIPE_CLOSING, .PIPE_EMPTY:
return "The designated named pipe is in the process of being closed."
case PIPE_DISCONNECTED:
case .PIPE_DISCONNECTED:
return "The designated named pipe exists, but there is no server process listening on the server side."
case BUFFER_OVERFLOW, MORE_PROCESSING_REQUIRED:
case .BUFFER_OVERFLOW, .MORE_PROCESSING_REQUIRED:
return "There is more data available to read on the designated named pipe."
case EA_TOO_LARGE, OS2_EAS_DIDNT_FIT:
case .EA_TOO_LARGE, .OS2_EAS_DIDNT_FIT:
return "Either there are no extended attributes, or the available extended attributes did not fit into the response."
case EAS_NOT_SUPPORTED:
case .EAS_NOT_SUPPORTED:
return "The server file system does not support Extended Attributes."
case OS2_EA_ACCESS_DENIED:
case .OS2_EA_ACCESS_DENIED:
return "Access to the extended attribute was denied."
default:
return ""
}
}
}
}
+337 -416
View File
@@ -9,365 +9,420 @@
import Foundation
public final class WebDavFileObject: FileObject {
public let contentType: String
public let entryTag: String?
internal init(absoluteURL: URL, name: String, path: String) {
super.init(absoluteURL: absoluteURL, name: name, path: path)
}
public init(absoluteURL: NSURL, name: String, path: String, size: Int64, contentType: String, createdDate: NSDate?, modifiedDate: NSDate?, fileType: FileType, isHidden: Bool, isReadOnly: Bool, entryTag: String?) {
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)
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
}
}
}
// 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.
public class WebDAVFileProvider: NSObject, FileProviderBasic {
public let type: String = "WebDAV"
public let isPathRelative: Bool = true
public let baseURL: NSURL?
public var currentPath: String = ""
public var dispatch_queue: dispatch_queue_t {
open class WebDAVFileProvider: NSObject, FileProviderBasicRemote {
open static let type: String = "WebDAV"
open let isPathRelative: Bool = true
open let baseURL: URL?
open var currentPath: String = ""
public var dispatch_queue: DispatchQueue {
willSet {
assert(_session == nil, "It's not effective to change dispatch_queue property after session is initialized.")
}
}
public weak var delegate: FileProviderDelegate?
public let credential: NSURLCredential?
open let credential: URLCredential?
open private(set) var cache: URLCache?
public var useCache: Bool = false
public var validatingCache: Bool = true
private var _session: NSURLSession?
private var session: NSURLSession {
fileprivate var _session: URLSession?
fileprivate var sessionDelegate: SessionDelegate?
public var session: URLSession {
if _session == nil {
let queue = NSOperationQueue()
self.sessionDelegate = SessionDelegate(fileProvider: self, credential: credential)
let queue = OperationQueue()
//queue.underlyingQueue = dispatch_queue
_session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), delegate: self, 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: NSURL, credential: NSURLCredential?) {
if !["http", "https"].contains(baseURL.uw_scheme.lowercaseString) {
public init? (baseURL: URL, credential: URLCredential?, cache: URLCache? = nil) {
if !["http", "https"].contains(baseURL.uw_scheme.lowercased()) {
return nil
}
self.baseURL = baseURL
dispatch_queue = dispatch_queue_create("FileProvider.\(type)", DISPATCH_QUEUE_CONCURRENT)
dispatch_queue = DispatchQueue(label: "FileProvider.\(WebDAVFileProvider.type)", attributes: DispatchQueue.Attributes.concurrent)
//let url = baseURL.uw_absoluteString
self.credential = credential
self.cache = cache
}
deinit {
_session?.invalidateAndCancel()
}
public func contentsOfDirectoryAtPath(path: String, completionHandler: ((contents: [FileObject], error: ErrorType?) -> Void)) {
open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {
let opType = FileOperationType.fetch(path: path)
let url = absoluteURL(path)
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "PROPFIND"
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>".dataUsingEncoding(NSUTF8StringEncoding)
request.setValue(String(request.HTTPBody!.length), forHTTPHeaderField: "Content-Length")
let task = session.dataTaskWithRequest(request) { (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)
request.setValue(String(request.httpBody!.count), forHTTPHeaderField: "Content-Length")
runDataTask(with: request, operationHandle: RemoteOperationHandle(operationType: opType, tasks: []), completionHandler: { (data, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
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(contents: fileObjects, error: responseError ?? error)
return
}
completionHandler(contents: [], error: responseError ?? error)
}
task.resume()
completionHandler(fileObjects, responseError ?? error)
})
}
public func attributesOfItemAtPath(path: String, completionHandler: ((attributes: FileObject?, error: ErrorType?) -> Void)) {
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
let url = absoluteURL(path)
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "PROPFIND"
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>".dataUsingEncoding(NSUTF8StringEncoding)
request.setValue(String(request.HTTPBody!.length), forHTTPHeaderField: "Content-Length")
let task = session.dataTaskWithRequest(request) { (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)
request.setValue(String(request.httpBody!.count), forHTTPHeaderField: "Content-Length")
runDataTask(with: request, completionHandler: { (data, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
if let code = (response as? HTTPURLResponse)?.statusCode, code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: url)
}
if let data = data {
let xresponse = self.parseXMLResponse(data)
if let attr = xresponse.first {
completionHandler(attributes: self.mapToFileObject(attr), error: responseError ?? error)
completionHandler(self.mapToFileObject(attr), responseError ?? error)
return
}
}
completionHandler(attributes: nil, error: responseError ?? error)
}
task.resume()
completionHandler(nil, responseError ?? error)
})
}
public func storageProperties(completionHandler: ((total: Int64, used: Int64) -> Void)) {
open func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void)) {
// Not all WebDAV clients implements RFC2518 which allows geting storage quota.
// In this case you won't get error. totalSize is NSURLSessionTransferSizeUnknown
// and used space is zero.
guard let baseURL = baseURL else {
return
}
let request = NSMutableURLRequest(URL: baseURL)
request.HTTPMethod = "PROPFIND"
var request = URLRequest(url: baseURL)
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>".dataUsingEncoding(NSUTF8StringEncoding)
request.setValue(String(request.HTTPBody!.length), forHTTPHeaderField: "Content-Length")
let task = session.dataTaskWithRequest(request) { (data, response, error) in
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")
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(total: totalSize ?? -1, used: usedSize ?? 0)
return
totalSize = Int64(attr.prop["quota-available-bytes"] ?? "") ?? -1
usedSize = Int64(attr.prop["quota-used-bytes"] ?? "") ?? 0
}
}
completionHandler(total: -1, used: 0)
}
task.resume()
completionHandler(totalSize, usedSize)
})
}
public weak var fileOperationDelegate: FileOperationDelegate?
open weak var fileOperationDelegate: FileOperationDelegate?
}
extension WebDAVFileProvider: FileProviderOperations {
public func createFolder(folderName: String, atPath: String, completionHandler: SimpleCompletionHandler) {
let url = absoluteURL((atPath as NSString).stringByAppendingPathComponent(folderName) + "/")
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "MKCOL"
let task = session.dataTaskWithRequest(request) { (data, response, error) in
@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"
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
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? NSHTTPURLResponse, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) where code != .OK {
completionHandler?(error: FileProviderWebDavError(code: code, url: url))
return
}
completionHandler?(error: responseError ?? error)
self.delegateNotify(.Create(path: (atPath as NSString).stringByAppendingPathComponent(folderName) + "/"), error: responseError ?? error)
}
completionHandler?(responseError ?? error)
self.delegateNotify(opType, error: responseError ?? error)
})
task.taskDescription = opType.json
task.resume()
return RemoteOperationHandle(operationType: opType, tasks: [task])
}
public func createFile(fileAttribs: FileObject, atPath path: String, contents data: NSData?, completionHandler: SimpleCompletionHandler) {
let url = absoluteURL(path)
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "PUT"
let task = session.uploadTaskWithRequest(request, fromData: data) { (data, response, error) in
@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
var responseError: FileProviderWebDavError?
if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: url)
}
completionHandler?(error: responseError ?? error)
self.delegateNotify(.Create(path: (path as NSString).stringByAppendingPathComponent(fileAttribs.name)), error: responseError ?? error)
}
task.taskDescription = self.dictionaryToJSON(["type": "Create", "source": (path as NSString).stringByAppendingPathComponent(fileAttribs.name)])
completionHandler?(responseError ?? error)
self.delegateNotify(opType, error: responseError ?? error)
})
task.taskDescription = opType.json
task.resume()
return RemoteOperationHandle(operationType: opType, tasks: [task])
}
public func moveItemAtPath(path: String, toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
self.copyMoveItemAtPath(true, path: path, toPath: toPath, overwrite: overwrite, completionHandler: completionHandler)
}
public func copyItemAtPath(path: String, toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
self.copyMoveItemAtPath(false, path: path, toPath: toPath, overwrite: overwrite, completionHandler: completionHandler)
}
private func copyMoveItemAtPath(move:Bool, path: String, toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) {
let url = absoluteURL(path)
let request = NSMutableURLRequest(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.dataTaskWithRequest(request) { (data, response, error) in
if let response = response as? NSHTTPURLResponse, 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 code == .MultiStatus, let data = data {
let xresponses = self.parseXMLResponse(data)
for xresponse in xresponses where xresponse.status >= 300 {
completionHandler?(error: FileProviderWebDavError(code: code, url: url))
}
} else {
completionHandler?(error: FileProviderWebDavError(code: code, url: url))
}
return
}
completionHandler?(error: error)
}
task.resume()
}
public func removeItemAtPath(path: String, completionHandler: SimpleCompletionHandler) {
let url = absoluteURL(path)
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "DELETE"
let task = session.dataTaskWithRequest(request) { (data, response, error) in
if let response = response as? NSHTTPURLResponse, 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?(error: FileProviderWebDavError(code: code, url: url))
}
} else {
completionHandler?(error: FileProviderWebDavError(code: code, url: url))
}
return
}
completionHandler?(error: error)
}
task.resume()
}
public func copyLocalFileToPath(localFile: NSURL, toPath: String, completionHandler: SimpleCompletionHandler) {
let url = absoluteURL(toPath)
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "PUT"
let task = session.uploadTaskWithRequest(request, fromFile: localFile) { (data, response, error) in
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
if let response = response as? HTTPURLResponse, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) {
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 ?? 0) >= 300 {
completionHandler?(FileProviderWebDavError(code: code, url: sourceURL))
}
}
}
if (response as? HTTPURLResponse)?.statusCode ?? 0 != FileProviderHTTPErrorCode.multiStatus.rawValue {
completionHandler?(responseError ?? error)
}
self.delegateNotify(opType, error: responseError ?? error)
})
task.taskDescription = opType.json
task.resume()
return RemoteOperationHandle(operationType: opType, tasks: [task])
}
@discardableResult
public func copyItem(localFile: URL, to toPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
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"
let task = session.uploadTask(with: request, fromFile: localFile, 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?(error: responseError ?? error)
self.delegateNotify(.Move(source: localFile.uw_absoluteString, destination: toPath), error: responseError ?? error)
}
task.taskDescription = self.dictionaryToJSON(["type": "Copy", "source": localFile.uw_absoluteString, "dest": toPath])
completionHandler?(responseError ?? error)
self.delegateNotify(opType, error: responseError ?? error)
})
task.taskDescription = opType.json
task.resume()
return RemoteOperationHandle(operationType: opType, tasks: [task])
}
public func copyPathToLocalFile(path: String, toLocalURL: NSURL, 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 = NSMutableURLRequest(URL: url)
let task = session.downloadTaskWithRequest(request) { (sourceFileURL, response, error) in
let request = URLRequest(url: url)
let task = session.downloadTask(with: request, completionHandler: { (sourceFileURL, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: url)
}
if let sourceFileURL = sourceFileURL {
do {
try NSFileManager.defaultManager().copyItemAtURL(sourceFileURL, toURL: toLocalURL)
try FileManager.default.copyItem(at: sourceFileURL, to: toLocalURL)
} catch let e {
completionHandler?(error: e)
completionHandler?(e)
return
}
}
completionHandler?(error: responseError ?? error)
}
task.taskDescription = self.dictionaryToJSON(["type": "Copy", "source": path, "dest": toLocalURL.uw_absoluteString])
completionHandler?(responseError ?? error)
self.delegateNotify(opType, error: responseError ?? error)
})
task.taskDescription = opType.json
task.resume()
return RemoteOperationHandle(operationType: opType, tasks: [task])
}
}
extension WebDAVFileProvider: FileProviderReadWrite {
public func contentsAtPath(path: String, completionHandler: ((contents: NSData?, error: ErrorType?) -> Void)) {
self.contentsAtPath(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 contentsAtPath(path: String, offset: Int64, length: Int, completionHandler: ((contents: NSData?, error: ErrorType?) -> 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)
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "GET"
var request = URLRequest(url: url)
request.httpMethod = "GET"
if length > 0 {
request.setValue("bytes=\(offset)-\(offset + length)", forHTTPHeaderField: "Range")
} else if offset > 0 && length < 0 {
request.setValue("bytes=\(offset)-", forHTTPHeaderField: "Range")
}
let task = session.dataTaskWithRequest(request) { (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? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: url)
}
completionHandler(contents: data, error: responseError ?? error)
}
task.resume()
completionHandler(data, responseError ?? error)
})
return handle
}
public func writeContentsAtPath(path: String, contents data: NSData, 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 request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "PUT"
let task = session.uploadTaskWithRequest(request, fromData: data) { (data, response, error) in
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
var responseError: FileProviderWebDavError?
if let code = (response as? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
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: error)
completionHandler?(error)
return
}
if atomically {
self.moveItemAtPath((path as NSString).stringByAppendingPathExtension("tmp")!, toPath: path, completionHandler: completionHandler)
self.moveItem(path: (path as NSString).appendingPathExtension("tmp")!, to: path, completionHandler: completionHandler)
}
}
task.taskDescription = self.dictionaryToJSON(["type": "Modify", "source": path])
})
task.taskDescription = opType.json
task.resume()
return RemoteOperationHandle(operationType: opType, tasks: [task])
}
public func searchFilesAtPath(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: ((files: [FileObject], error: ErrorType?) -> Void)) {
public func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
let url = absoluteURL(path)
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "PROPFIND"
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>".dataUsingEncoding(NSUTF8StringEncoding)
request.setValue(String(request.HTTPBody!.length), forHTTPHeaderField: "Content-Length")
let task = session.dataTaskWithRequest(request) { (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? NSHTTPURLResponse)?.statusCode where code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: url)
}
if let data = data {
let xresponse = self.parseXMLResponse(data)
var fileObjects = [WebDavFileObject]()
for attr in xresponse {
if let path = attr.href.path where !((path as NSString).lastPathComponent.containsString(query)) {
let path = attr.href.path
if !((path as NSString).lastPathComponent.contains(query)) {
continue
}
let fileObject = self.mapToFileObject(attr)
fileObjects.append(fileObject)
foundItemHandler?(fileObject)
}
completionHandler(files: fileObjects, error: responseError ?? error)
completionHandler(fileObjects, responseError ?? error)
return
}
completionHandler(files: [], error: responseError ?? error)
}
task.resume()
completionHandler([], responseError ?? error)
})
}
private func registerNotifcation(path: String, eventHandler: (() -> Void)) {
fileprivate func registerNotifcation(path: String, eventHandler: (() -> Void)) {
/* There is no unified api for monitoring WebDAV server content change/update
* Microsoft Exchange uses SUBSCRIBE method, Apple uses push notification system.
* while both is unavailable in a mobile platform.
@@ -375,81 +430,50 @@ extension WebDAVFileProvider: FileProviderReadWrite {
* with previous results
*/
}
private func unregisterNotifcation(path: String) {
fileprivate func unregisterNotifcation(path: String) {
}
// 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
internal extension WebDAVFileProvider {
struct DavResponse {
let href: NSURL
let href: URL
let hrefString: String
let status: Int?
let prop: [String: String]
}
private func parseXMLResponse(response: NSData) -> [DavResponse] {
fileprivate func parseXMLResponse(_ response: Data) -> [DavResponse] {
var result = [DavResponse]()
do {
let xml = try AEXMLDocument(xmlData: response)
let xml = try AEXMLDocument(xml: response)
var rootnode = xml.root
var responsetag = "response"
for node in rootnode.all ?? [] where node.name.lowercaseString.hasSuffix("multistatus") {
for node in rootnode.all ?? [] where node.name.lowercased().hasSuffix("multistatus") {
rootnode = node
}
for node in rootnode.children ?? [] where node.name.lowercaseString.hasSuffix("response") {
for node in rootnode.children where node.name.lowercased().hasSuffix("response") {
responsetag = node.name
break
}
for responseNode in rootnode[responsetag].all ?? [] {
var hreftag = "href"
var statustag = "status"
var propstattag = "propstat"
for node in responseNode.children ?? [] {
if node.name.lowercaseString.hasSuffix("href") {
hreftag = node.name
}
if node.name.lowercaseString.hasSuffix("status") {
statustag = node.name
}
if node.name.lowercaseString.hasSuffix("propstat") {
propstattag = node.name
}
}
let href = responseNode[hreftag].value
if let href = href, hrefURL = NSURL(string: href) {
var status: Int?
let statusDesc = (responseNode[statustag].stringValue).componentsSeparatedByString(" ")
if statusDesc.count > 2 {
status = Int(statusDesc[1])
}
var propDic = [String: String]()
let propStatNode = responseNode[propstattag]
for node in propStatNode.children ?? [] where node.name.lowercaseString.hasSuffix("status"){
statustag = node.name
break
}
let statusDesc2 = (propStatNode[statustag].stringValue).componentsSeparatedByString(" ")
if statusDesc2.count > 2 {
status = Int(statusDesc2[1])
}
var proptag = "prop"
for tnode in propStatNode.children ?? [] where tnode.name.lowercaseString.hasSuffix("prop") {
proptag = tnode.name
break
}
for propItemNode in propStatNode[proptag].children ?? [] {
propDic[propItemNode.name.componentsSeparatedByString(":").last!.lowercaseString] = propItemNode.value
if propItemNode.name.hasSuffix("resourcetype") && propItemNode.xmlString.containsString("collection") {
propDic["getcontenttype"] = "httpd/unix-directory"
}
}
result.append(DavResponse(href: hrefURL, hrefString: href, status: status, prop: propDic))
if let davResponse = mapNodeToDavResponse(responseNode) {
result.append(davResponse)
}
}
} catch _ {
@@ -457,23 +481,72 @@ internal extension WebDAVFileProvider {
return result
}
private func mapToFileObject(davResponse: DavResponse) -> WebDavFileObject {
var href = davResponse.href
if href.baseURL == nil {
href = absoluteURL(href.path ?? "")
fileprivate func mapNodeToDavResponse(_ node: AEXMLElement) -> DavResponse? {
var hreftag = "href"
var statustag = "status"
var propstattag = "propstat"
for node in node.children {
if node.name.lowercased().hasSuffix("href") {
hreftag = node.name
}
if node.name.lowercased().hasSuffix("status") {
statustag = node.name
}
if node.name.lowercased().hasSuffix("propstat") {
propstattag = node.name
}
}
let name = davResponse.prop["displayname"] ?? (davResponse.hrefString.stringByRemovingPercentEncoding! as NSString).lastPathComponent
let size = Int64(davResponse.prop["getcontentlength"] ?? "-1") ?? NSURLSessionTransferSizeUnknown
let createdDate = self.resolveDate(davResponse.prop["creationdate"] ?? "")
let modifiedDate = self.resolveDate(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 ?? name, size: size, contentType: contentType, createdDate: createdDate, modifiedDate: modifiedDate, fileType: isDirectory ? .Directory : .Regular, isHidden: false, isReadOnly: false, entryTag: entryTag)
let href = node[hreftag].value
if let href = href, let hrefURL = URL(string: href) {
var status: Int?
let statusDesc = (node[statustag].string).components(separatedBy: " ")
if statusDesc.count > 2 {
status = Int(statusDesc[1])
}
var propDic = [String: String]()
let propStatNode = node[propstattag]
for node in propStatNode.children where node.name.lowercased().hasSuffix("status"){
statustag = node.name
break
}
let statusDesc2 = (propStatNode[statustag].string).components(separatedBy: " ")
if statusDesc2.count > 2 {
status = Int(statusDesc2[1])
}
var proptag = "prop"
for tnode in propStatNode.children where tnode.name.lowercased().hasSuffix("prop") {
proptag = tnode.name
break
}
for propItemNode in propStatNode[proptag].children {
propDic[propItemNode.name.components(separatedBy: ":").last!.lowercased()] = propItemNode.value
if propItemNode.name.hasSuffix("resourcetype") && propItemNode.xml.contains("collection") {
propDic["getcontenttype"] = "httpd/unix-directory"
}
}
return DavResponse(href: hrefURL, hrefString: href, status: status, prop: propDic)
}
return nil
}
private func delegateNotify(operation: FileOperation, error: ErrorType?) {
dispatch_async(dispatch_get_main_queue(), {
fileprivate func mapToFileObject(_ davResponse: DavResponse) -> WebDavFileObject {
var href = davResponse.href
if href.baseURL == nil {
href = absoluteURL(href.path)
}
let name = davResponse.prop["displayname"] ?? (davResponse.hrefString.removingPercentEncoding! as NSString).lastPathComponent
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: FileOperationType, error: Error?) {
DispatchQueue.main.async(execute: {
if error == nil {
self.delegate?.fileproviderSucceed(self, operation: operation)
} else {
@@ -483,163 +556,11 @@ internal extension WebDAVFileProvider {
}
}
// MARK: URLSession delegate
extension WebDAVFileProvider: NSURLSessionDataDelegate, NSURLSessionDownloadDelegate {
public func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {
return
}
public func URLSession(session: NSURLSession, task: NSURLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
guard let desc = task.taskDescription, let json = jsonToDictionary(desc) else {
return
}
guard let type = json["type"] as? String, let source = json["source"] as? String else {
return
}
let dest = json["dest"] as? String
let op : FileOperation
switch type {
case "Create":
op = .Create(path: source)
case "Copy":
guard let dest = dest else { return }
op = .Copy(source: source, destination: dest)
case "Move":
guard let dest = dest else { return }
op = .Move(source: source, destination: dest)
case "Modify":
op = .Modify(path: source)
case "Remove":
op = .Remove(path: source)
case "Link":
guard let dest = dest else { return }
op = .Link(link: source, target: dest)
default:
return
}
let progress = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
self.delegate?.fileproviderProgress(self, operation: op, progress: progress)
}
public func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
guard let desc = downloadTask.taskDescription, let json = jsonToDictionary(desc), let source = json["source"] as? String, dest = json["dest"] as? String else {
return
}
self.delegate?.fileproviderProgress(self, operation: .Copy(source: source, destination: dest), progress: Float(totalBytesWritten) / Float(totalBytesExpectedToWrite))
}
public func URLSession(session: NSURLSession, task: NSURLSessionTask, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
let deposition: NSURLSessionAuthChallengeDisposition = credential != nil ? .UseCredential : .PerformDefaultHandling
completionHandler(deposition, credential)
}
public func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
let deposition: NSURLSessionAuthChallengeDisposition = credential != nil ? .UseCredential : .PerformDefaultHandling
completionHandler(deposition, credential)
}
}
public struct FileProviderWebDavError: ErrorType, CustomStringConvertible {
public struct FileProviderWebDavError: Error, CustomStringConvertible {
public let code: FileProviderHTTPErrorCode
public let url: NSURL
public let url: URL
public var description: String {
return code.description
}
}
public enum FileProviderHTTPErrorCode: Int {
case Continue = 100
case SwitchingProtocols = 101
case Processing = 102
case OK = 200
case Created = 201
case Accepted = 202
case NonAuthoritativeInformation = 203
case NoContent = 204
case ResetContent = 205
case PartialContent = 206
case MultiStatus = 207
case AlreadyReported = 208
case IMUsed = 226
case MultipleChoices = 300
case MovedPermanently = 301
case Found = 302
case SeeOther = 303
case NotModified = 304
case UseProxy = 305
case SwitchProxy = 306
case TemporaryRedirect = 307
case PermanentRedirect = 308
case BadRequest = 400
case Unauthorized = 401
case PaymentRequired = 402
case Forbidden = 403
case NotFound = 404
case MethodNotAllowed = 405
case NotAcceptable = 406
case ProxyAuthenticationRequired = 407
case RequestTimeout = 408
case Conflict = 409
case Gone = 410
case LengthRequired = 411
case PreconditionFailed = 412
case PayloadTooLarge = 413
case URITooLong = 414
case UnsupportedMediaType = 415
case RangeNotSatisfiable = 416
case ExpectationFailed = 417
case MisdirectedRequest = 421
case UnprocessableEntity = 422
case Locked = 423
case FailedDependency = 424
case UnorderedCollection = 425
case UpgradeRequired = 426
case PreconditionRequired = 428
case TooManyRequests = 429
case RequestHeaderFieldsTooLarge = 431
case UnavailableForLegalReasons = 451
case InternalServerError = 500
case BadGateway = 502
case ServiceUnavailable = 503
case GatewayTimeout = 504
case HTTPVersionNotSupported = 505
case VariantlsoNegotiates = 506
case InsufficientStorage = 507
case LoopDetected = 508
case BandwidthLimitExceeded = 509
case NotExtended = 510
case NetworkAuthenticationRequired = 511
private static let status1xx = [100: "Continue", 101: "Switching Protocols", 102: "Processing"]
private static let status2xx = [200: "OK", 201: "Created", 202: "Accepted", 203: "Non-Authoritative Information", 204: "No Content", 205: "Reset Content", 206: "Partial Content", 207: "Multi-Status", 208: "Already Reported", 226: "IM Used"]
private static let status3xx = [300: "Multiple Choices", 301: "Moved Permanently", 302: "Found", 303: "See Other", 304: "Not Modified", 305: "Use Proxy", 306: "Switch Proxy", 307: "Temporary Redirect", 308: "Permanent Redirect"]
private static let status4xx = [400: "Bad Request", 401: "Unauthorized/Expired Session", 402: "Payment Required", 403: "Forbidden", 404: "Not Found", 405: "Method Not Allowed", 406: "Not Acceptable", 407: "Proxy Authentication Required", 408: "Request Timeout", 409: "Conflict", 410: "Gone", 411: "Length Required", 412: "Precondition Failed", 413: "Payload Too Large", 414: "URI Too Long", 415: "Unsupported Media Type", 416: "Range Not Satisfiable", 417: "Expectation Failed", 421: "Misdirected Request", 422: "Unprocessable Entity", 423: "Locked", 424: "Failed Dependency", 425: "Unordered Collection", 426: "Upgrade Required", 428: "Precondition Required", 429: "Too Many Requests", 431: "Request Header Fields Too Large", 451: "Unavailable For Legal Reasons"]
private static let status5xx = [500: "Internal Server Error", 501: "Not Implemented", 502: "Bad Gateway", 503: "Service Unavailable", 504: "Gateway Timeout", 505: "HTTP Version Not Supported", 506: "Variant Also Negotiates", 507: "Insufficient Storage", 508: "Loop Detected", 509: "Bandwidth Limit Exceeded", 510: "Not Extended", 511: "Network Authentication Required"]
public var description: String {
switch self.rawValue {
case 100...102: return FileProviderHTTPErrorCode.status1xx[self.rawValue]!
case 200...208, 226: return FileProviderHTTPErrorCode.status2xx[self.rawValue]!
case 300...308: return FileProviderHTTPErrorCode.status3xx[self.rawValue]!
case 400...417, 421...426: fallthrough
case 428, 429, 431, 451: return FileProviderHTTPErrorCode.status4xx[self.rawValue]!
case 500...511: return FileProviderHTTPErrorCode.status5xx[self.rawValue]!
default: return typeDescription
}
}
public var typeDescription: String {
switch self.rawValue {
case 100...199: return "Informational"
case 200...299: return "Success"
case 300...399: return "Redirection"
case 400...499: return "Client Error"
case 500...599: return "Server Error"
default: return "Server Error"
}
}
}