Compare commits
83 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1a44df3fd7 | |||
| eff3725680 | |||
| 9368f636d3 | |||
| 3f2fda638f | |||
| 6737f98978 | |||
| 3e55cc60f7 | |||
| d7ae709cf7 | |||
| 4f6e3c7bd5 | |||
| b121fe6182 | |||
| 29b7f720f5 | |||
| 067e14b76f | |||
| 09e39ada82 | |||
| 6889b302b7 | |||
| 96ce12f226 | |||
| 091fd14a88 | |||
| 10aac532f7 | |||
| 3aac0a88c4 | |||
| 24355a4c6c | |||
| d2cf657ab2 | |||
| f2d54c7f92 | |||
| 0e9a82a288 | |||
| ea7e089718 | |||
| 19ea14ebc1 | |||
| 47885c0a4e | |||
| ac44aa190f | |||
| 50f6a393a0 | |||
| 779b38f381 | |||
| a374841883 | |||
| 0123d8b117 | |||
| fdc4bb818d | |||
| a08a9fe7a0 | |||
| 792ac6b015 | |||
| 8bad5944bc | |||
| 6ef2ab11c4 | |||
| 7f27d46c70 | |||
| b1ec99b1b8 | |||
| c4b8065cd3 | |||
| 2d8454c711 | |||
| 3b35c066de | |||
| 7d8de9cefe | |||
| e84fef20ea | |||
| 4a9a3196a2 | |||
| f4e6d277ae | |||
| 1328a8e9e2 | |||
| 5bf620916f | |||
| 4f56e20441 | |||
| 9dda618b73 | |||
| da60c05188 | |||
| 4366855d54 | |||
| fe05fd83fe | |||
| 826d207e6b | |||
| 66fc1e1284 | |||
| a1d489f5a5 | |||
| ad2699cd19 | |||
| 97ae86cedb | |||
| 71b07cac1b | |||
| a15f8f3809 | |||
| dc1270d8d1 | |||
| 68b1e23be3 | |||
| 057c9fd940 | |||
| 6d63322779 | |||
| fc6b46d17a | |||
| bb9c08e309 | |||
| 315ead606e | |||
| d63e9c7f04 | |||
| a5fe28c18d | |||
| 542c18bab6 | |||
| dca5dddbfd | |||
| 6764c23f1b | |||
| 955a86372b | |||
| 32a20fbc49 | |||
| 921ba19afa | |||
| 8625b0464c | |||
| 1bf9d1eadd | |||
| cbf774ba9e | |||
| 5e2f911cda | |||
| f423834021 | |||
| 0772c87122 | |||
| 011c535760 | |||
| 468a2bc9e9 | |||
| 33be492499 | |||
| c4049b961f | |||
| 940c7c1028 |
@@ -0,0 +1 @@
|
||||
3.0
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
language: objective-c
|
||||
|
||||
osx_image: xcode8.2
|
||||
|
||||
xcode_project: FileProvider.xcodeproj
|
||||
env:
|
||||
global:
|
||||
- PROJECTNAME="FileProvider"
|
||||
- FRAMEWORK_NAME="FileProvider.framework"
|
||||
matrix:
|
||||
- SHCEME="FileProvider OSX" SDK="macosx" ACTION="build"
|
||||
- SHCEME="FileProvider iOS" SDK="iphonesimulator" ACTION="build"
|
||||
- SHCEME="FileProvider tvOS" SDK="appletvsimulator" ACTION="build"
|
||||
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 $PROJECT.xcodeproj -scheme "$SCHEME" -sdk $SDK $ACTION 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 $PROJECTNAME
|
||||
deploy:
|
||||
file: $PROJECTNAME.framework.zip
|
||||
skip_cleanup: true
|
||||
on:
|
||||
tags: true
|
||||
@@ -16,8 +16,8 @@ Pod::Spec.new do |s|
|
||||
#
|
||||
|
||||
s.name = "FileProvider"
|
||||
s.version = "0.3.4"
|
||||
s.summary = "NSFileManager replacement for Local and Remote (WebDAV/Dropbox/SMB2) files on iOS and MacOS."
|
||||
s.version = "0.11.3"
|
||||
s.summary = "FileManager replacement for Local and Remote (WebDAV/Dropbox/OneDrive/SMB2) files on iOS and macOS."
|
||||
|
||||
# This description is used to generate tags and improve search results.
|
||||
# * Think: What does it do? Why did you write it? What is the focus?
|
||||
@@ -26,8 +26,8 @@ Pod::Spec.new do |s|
|
||||
# * Finally, don't worry about the indent, CocoaPods strips it!
|
||||
s.description = <<-DESC
|
||||
This Swift library provide a swifty way to deal with local and remote files
|
||||
and directories in same way. For now Local and WebDAV providers are ready to use
|
||||
and SMB2, Dropbox, FTP and AmazonS3 is planned for future.
|
||||
and directories in same way. For now Local, WebDAV and Dropbox providers are ready to use.
|
||||
SMB2, FTP and AmazonS3 is planned for future.
|
||||
DESC
|
||||
|
||||
s.homepage = "https://github.com/amosavian/FileProvider"
|
||||
|
||||
@@ -7,9 +7,46 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
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 */; };
|
||||
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 */; };
|
||||
79480FF61E3ABDD0007E7275 /* CloudFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79480FF51E3ABDD0007E7275 /* CloudFileProvider.swift */; };
|
||||
79480FF71E3ABDD0007E7275 /* CloudFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79480FF51E3ABDD0007E7275 /* CloudFileProvider.swift */; };
|
||||
79480FF81E3ABDD0007E7275 /* CloudFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79480FF51E3ABDD0007E7275 /* CloudFileProvider.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 */; };
|
||||
794C220A1D5893F800EC49B8 /* SMB2Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C22091D5893F800EC49B8 /* SMB2Notification.swift */; };
|
||||
794C220B1D5893F800EC49B8 /* SMB2Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C22091D5893F800EC49B8 /* SMB2Notification.swift */; };
|
||||
794C220C1D5893F800EC49B8 /* SMB2Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C22091D5893F800EC49B8 /* SMB2Notification.swift */; };
|
||||
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 */; };
|
||||
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 */; };
|
||||
@@ -19,9 +56,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 */; };
|
||||
@@ -58,22 +92,54 @@
|
||||
799396DA1D48C02300086753 /* SMBErrorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A41D48C02300086753 /* SMBErrorType.swift */; };
|
||||
799396DB1D48C02300086753 /* SMBErrorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A41D48C02300086753 /* SMBErrorType.swift */; };
|
||||
799396DC1D48C02300086753 /* SMBErrorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A41D48C02300086753 /* SMBErrorType.swift */; };
|
||||
799396DD1D48C02300086753 /* TCPSocketClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A51D48C02300086753 /* TCPSocketClient.swift */; };
|
||||
799396DE1D48C02300086753 /* TCPSocketClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A51D48C02300086753 /* TCPSocketClient.swift */; };
|
||||
799396DF1D48C02300086753 /* TCPSocketClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A51D48C02300086753 /* TCPSocketClient.swift */; };
|
||||
799396E01D48C02300086753 /* WebDAVFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A61D48C02300086753 /* WebDAVFileProvider.swift */; };
|
||||
799396E11D48C02300086753 /* WebDAVFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A61D48C02300086753 /* WebDAVFileProvider.swift */; };
|
||||
799396E21D48C02300086753 /* WebDAVFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A61D48C02300086753 /* WebDAVFileProvider.swift */; };
|
||||
79BD638C1E2CC2300035128C /* ExtendedLocalFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD638B1E2CC2300035128C /* ExtendedLocalFileProvider.swift */; };
|
||||
79BD638D1E2CC2300035128C /* ExtendedLocalFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD638B1E2CC2300035128C /* ExtendedLocalFileProvider.swift */; };
|
||||
79BD638E1E2CC2300035128C /* ExtendedLocalFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD638B1E2CC2300035128C /* ExtendedLocalFileProvider.swift */; };
|
||||
79BD63A81E2CC2940035128C /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63A71E2CC2940035128C /* CoreGraphics.framework */; };
|
||||
79BD63AA1E2CC2BB0035128C /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63A91E2CC2BB0035128C /* AVFoundation.framework */; };
|
||||
79BD63AC1E2CC2C20035128C /* ImageIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63AB1E2CC2C20035128C /* ImageIO.framework */; };
|
||||
79BD63B01E2CC3300035128C /* libxml2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63AF1E2CC3300035128C /* libxml2.tbd */; };
|
||||
79BD63B21E2CC3350035128C /* ImageIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63B11E2CC3350035128C /* ImageIO.framework */; };
|
||||
79BD63B61E2CC3860035128C /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63B51E2CC3860035128C /* CoreFoundation.framework */; };
|
||||
79BD63B81E2CC38D0035128C /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63B71E2CC38D0035128C /* AVFoundation.framework */; };
|
||||
79BD63BA1E2CC39B0035128C /* libxml2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63B91E2CC39B0035128C /* libxml2.tbd */; };
|
||||
79BD63BE1E2CC3C20035128C /* ImageIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63BD1E2CC3C20035128C /* ImageIO.framework */; };
|
||||
79BD63C01E2CC3CD0035128C /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63BF1E2CC3CD0035128C /* CoreGraphics.framework */; };
|
||||
79BD63C21E2CC3D30035128C /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63C11E2CC3D30035128C /* AVFoundation.framework */; };
|
||||
79BD63C51E2D17880035128C /* OneDriveFileProvide.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD63C31E2D17880035128C /* OneDriveFileProvide.swift */; };
|
||||
79BD63C61E2D17880035128C /* OneDriveFileProvide.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD63C31E2D17880035128C /* OneDriveFileProvide.swift */; };
|
||||
79BD63C71E2D17880035128C /* OneDriveFileProvide.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD63C31E2D17880035128C /* OneDriveFileProvide.swift */; };
|
||||
79BD63C81E2D17880035128C /* OneDriveHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD63C41E2D17880035128C /* OneDriveHelper.swift */; };
|
||||
79BD63C91E2D17880035128C /* OneDriveHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD63C41E2D17880035128C /* OneDriveHelper.swift */; };
|
||||
79BD63CA1E2D17880035128C /* OneDriveHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD63C41E2D17880035128C /* OneDriveHelper.swift */; };
|
||||
79F5745B1DFDB10B00179ABF /* FileObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79F5745A1DFDB10A00179ABF /* FileObject.swift */; };
|
||||
79F5745C1DFDB10B00179ABF /* FileObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79F5745A1DFDB10A00179ABF /* FileObject.swift */; };
|
||||
79F5745D1DFDB10B00179ABF /* FileObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79F5745A1DFDB10A00179ABF /* FileObject.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
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>"; };
|
||||
79480FF51E3ABDD0007E7275 /* CloudFileProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudFileProvider.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>"; };
|
||||
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>"; };
|
||||
@@ -91,8 +157,25 @@
|
||||
799396A21D48C02300086753 /* SMB2Tree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2Tree.swift; sourceTree = "<group>"; };
|
||||
799396A31D48C02300086753 /* SMB2Types.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2Types.swift; sourceTree = "<group>"; };
|
||||
799396A41D48C02300086753 /* SMBErrorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMBErrorType.swift; sourceTree = "<group>"; };
|
||||
799396A51D48C02300086753 /* TCPSocketClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TCPSocketClient.swift; sourceTree = "<group>"; };
|
||||
799396A61D48C02300086753 /* WebDAVFileProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebDAVFileProvider.swift; sourceTree = "<group>"; };
|
||||
79BD638B1E2CC2300035128C /* ExtendedLocalFileProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtendedLocalFileProvider.swift; sourceTree = "<group>"; };
|
||||
79BD63A71E2CC2940035128C /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.2.sdk/System/Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; };
|
||||
79BD63A91E2CC2BB0035128C /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.2.sdk/System/Library/Frameworks/AVFoundation.framework; sourceTree = DEVELOPER_DIR; };
|
||||
79BD63AB1E2CC2C20035128C /* ImageIO.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ImageIO.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.2.sdk/System/Library/Frameworks/ImageIO.framework; sourceTree = DEVELOPER_DIR; };
|
||||
79BD63AD1E2CC2EB0035128C /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.2.sdk/System/Library/Frameworks/MediaPlayer.framework; sourceTree = DEVELOPER_DIR; };
|
||||
79BD63AF1E2CC3300035128C /* libxml2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libxml2.tbd; path = usr/lib/libxml2.tbd; sourceTree = SDKROOT; };
|
||||
79BD63B11E2CC3350035128C /* ImageIO.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ImageIO.framework; path = System/Library/Frameworks/ImageIO.framework; sourceTree = SDKROOT; };
|
||||
79BD63B31E2CC33D0035128C /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = System/Library/Frameworks/MediaPlayer.framework; sourceTree = SDKROOT; };
|
||||
79BD63B51E2CC3860035128C /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
|
||||
79BD63B71E2CC38D0035128C /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
|
||||
79BD63B91E2CC39B0035128C /* libxml2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libxml2.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.1.sdk/usr/lib/libxml2.tbd; sourceTree = DEVELOPER_DIR; };
|
||||
79BD63BB1E2CC3B90035128C /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.1.sdk/System/Library/Frameworks/MediaPlayer.framework; sourceTree = DEVELOPER_DIR; };
|
||||
79BD63BD1E2CC3C20035128C /* ImageIO.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ImageIO.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.1.sdk/System/Library/Frameworks/ImageIO.framework; sourceTree = DEVELOPER_DIR; };
|
||||
79BD63BF1E2CC3CD0035128C /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.1.sdk/System/Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; };
|
||||
79BD63C11E2CC3D30035128C /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.1.sdk/System/Library/Frameworks/AVFoundation.framework; sourceTree = DEVELOPER_DIR; };
|
||||
79BD63C31E2D17880035128C /* OneDriveFileProvide.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OneDriveFileProvide.swift; sourceTree = "<group>"; };
|
||||
79BD63C41E2D17880035128C /* OneDriveHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OneDriveHelper.swift; sourceTree = "<group>"; };
|
||||
79F5745A1DFDB10A00179ABF /* FileObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileObject.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -100,6 +183,10 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
79BD63AA1E2CC2BB0035128C /* AVFoundation.framework in Frameworks */,
|
||||
79BD63AC1E2CC2C20035128C /* ImageIO.framework in Frameworks */,
|
||||
79BD63A81E2CC2940035128C /* CoreGraphics.framework in Frameworks */,
|
||||
791950F51DE58A5400B4426E /* libxml2.tbd in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -107,6 +194,10 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
79BD63B81E2CC38D0035128C /* AVFoundation.framework in Frameworks */,
|
||||
79BD63B61E2CC3860035128C /* CoreFoundation.framework in Frameworks */,
|
||||
79BD63B21E2CC3350035128C /* ImageIO.framework in Frameworks */,
|
||||
79BD63B01E2CC3300035128C /* libxml2.tbd in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -114,18 +205,58 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
79BD63C21E2CC3D30035128C /* AVFoundation.framework in Frameworks */,
|
||||
79BD63C01E2CC3CD0035128C /* CoreGraphics.framework in Frameworks */,
|
||||
79BD63BE1E2CC3C20035128C /* ImageIO.framework in Frameworks */,
|
||||
79BD63BA1E2CC39B0035128C /* libxml2.tbd in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
791950F31DE58A5300B4426E /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
79BD63C11E2CC3D30035128C /* AVFoundation.framework */,
|
||||
79BD63BF1E2CC3CD0035128C /* CoreGraphics.framework */,
|
||||
79BD63BD1E2CC3C20035128C /* ImageIO.framework */,
|
||||
79BD63BB1E2CC3B90035128C /* MediaPlayer.framework */,
|
||||
79BD63B91E2CC39B0035128C /* libxml2.tbd */,
|
||||
79BD63B71E2CC38D0035128C /* AVFoundation.framework */,
|
||||
79BD63B51E2CC3860035128C /* CoreFoundation.framework */,
|
||||
79BD63B31E2CC33D0035128C /* MediaPlayer.framework */,
|
||||
79BD63B11E2CC3350035128C /* ImageIO.framework */,
|
||||
79BD63AF1E2CC3300035128C /* libxml2.tbd */,
|
||||
79BD63AD1E2CC2EB0035128C /* MediaPlayer.framework */,
|
||||
79BD63AB1E2CC2C20035128C /* ImageIO.framework */,
|
||||
79BD63A91E2CC2BB0035128C /* AVFoundation.framework */,
|
||||
79BD63A71E2CC2940035128C /* CoreGraphics.framework */,
|
||||
791950F41DE58A5400B4426E /* libxml2.tbd */,
|
||||
);
|
||||
name = Frameworks;
|
||||
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 = (
|
||||
79E34A101E2AC6C600E1293B /* Extra */,
|
||||
799396911D48C02300086753 /* Sources */,
|
||||
7993968A1D48B8C700086753 /* Pod */,
|
||||
799396681D48B7F600086753 /* Products */,
|
||||
791950F31DE58A5300B4426E /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -152,15 +283,23 @@
|
||||
799396911D48C02300086753 /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7924B18B1D89DAE000589DB7 /* AEXML */,
|
||||
799396991D48C02300086753 /* SMBTypes */,
|
||||
799396921D48C02300086753 /* AEXML.swift */,
|
||||
799396931D48C02300086753 /* DropboxFileProvider.swift */,
|
||||
799396941D48C02300086753 /* FileProvider.h */,
|
||||
799396951D48C02300086753 /* FileProvider.swift */,
|
||||
79F5745A1DFDB10A00179ABF /* FileObject.swift */,
|
||||
799396961D48C02300086753 /* LocalFileProvider.swift */,
|
||||
792572401DF23BDA006A1526 /* LocalHelper.swift */,
|
||||
79480FF51E3ABDD0007E7275 /* CloudFileProvider.swift */,
|
||||
79BD638B1E2CC2300035128C /* ExtendedLocalFileProvider.swift */,
|
||||
7902C0851D61B56D00564440 /* RemoteSession.swift */,
|
||||
799396931D48C02300086753 /* DropboxFileProvider.swift */,
|
||||
794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */,
|
||||
79BD63C31E2D17880035128C /* OneDriveFileProvide.swift */,
|
||||
79BD63C41E2D17880035128C /* OneDriveHelper.swift */,
|
||||
7924B1A81D89F79200589DB7 /* FPSStreamTask.swift */,
|
||||
799396971D48C02300086753 /* SMBClient.swift */,
|
||||
799396981D48C02300086753 /* SMBFileProvider.swift */,
|
||||
799396A51D48C02300086753 /* TCPSocketClient.swift */,
|
||||
799396A61D48C02300086753 /* WebDAVFileProvider.swift */,
|
||||
);
|
||||
path = Sources;
|
||||
@@ -179,11 +318,20 @@
|
||||
7993969C1D48C02300086753 /* SMB2FileHandle.swift */,
|
||||
7993969E1D48C02300086753 /* SMB2IOCtl.swift */,
|
||||
7993969F1D48C02300086753 /* SMB2Query.swift */,
|
||||
794C220D1D591A4B00EC49B8 /* SMB2QueryTypes.swift */,
|
||||
794C22091D5893F800EC49B8 /* SMB2Notification.swift */,
|
||||
799396A11D48C02300086753 /* SMB2SetInfo.swift */,
|
||||
);
|
||||
path = SMBTypes;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
79E34A101E2AC6C600E1293B /* Extra */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
name = Extra;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
@@ -271,10 +419,11 @@
|
||||
7993965C1D48B7BF00086753 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 0730;
|
||||
LastUpgradeCheck = 0810;
|
||||
TargetAttributes = {
|
||||
799396661D48B7F600086753 = {
|
||||
CreatedOnToolsVersion = 7.3.1;
|
||||
LastSwiftMigration = 0800;
|
||||
};
|
||||
799396741D48B80D00086753 = {
|
||||
CreatedOnToolsVersion = 7.3.1;
|
||||
@@ -334,23 +483,37 @@
|
||||
files = (
|
||||
799396B31D48C02300086753 /* LocalFileProvider.swift in Sources */,
|
||||
799396D41D48C02300086753 /* SMB2Tree.swift in Sources */,
|
||||
7924B1A21D89DAE000589DB7 /* Options.swift in Sources */,
|
||||
792572411DF23BDA006A1526 /* LocalHelper.swift in Sources */,
|
||||
79480FF61E3ABDD0007E7275 /* CloudFileProvider.swift in Sources */,
|
||||
79F5745B1DFDB10B00179ABF /* FileObject.swift in Sources */,
|
||||
79BD63C51E2D17880035128C /* OneDriveFileProvide.swift in Sources */,
|
||||
7924B1991D89DAE000589DB7 /* Element.swift in Sources */,
|
||||
799396C81D48C02300086753 /* SMB2IOCtl.swift in Sources */,
|
||||
799396DD1D48C02300086753 /* TCPSocketClient.swift in Sources */,
|
||||
799396D71D48C02300086753 /* SMB2Types.swift in Sources */,
|
||||
7924B1B21D89FCDA00589DB7 /* FPSStreamTask.swift in Sources */,
|
||||
799396C51D48C02300086753 /* SMB2FileOperation.swift in Sources */,
|
||||
79BD63C81E2D17880035128C /* OneDriveHelper.swift in Sources */,
|
||||
799396BF1D48C02300086753 /* SMB2DataTypes.swift in Sources */,
|
||||
799396B91D48C02300086753 /* SMBFileProvider.swift in Sources */,
|
||||
794C220E1D591A4B00EC49B8 /* SMB2QueryTypes.swift in Sources */,
|
||||
794C21FE1D58912A00EC49B8 /* DropboxHelper.swift in Sources */,
|
||||
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 */,
|
||||
799396AA1D48C02300086753 /* DropboxFileProvider.swift in Sources */,
|
||||
79BD638C1E2CC2300035128C /* ExtendedLocalFileProvider.swift in Sources */,
|
||||
7924B1B31D89FD6400589DB7 /* SMBClient.swift in Sources */,
|
||||
7924B1A51D89DAE000589DB7 /* Parser.swift in Sources */,
|
||||
799396B01D48C02300086753 /* FileProvider.swift in Sources */,
|
||||
799396B61D48C02300086753 /* SMBClient.swift in Sources */,
|
||||
7924B19C1D89DAE000589DB7 /* Error.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -360,23 +523,37 @@
|
||||
files = (
|
||||
799396B41D48C02300086753 /* LocalFileProvider.swift in Sources */,
|
||||
799396D51D48C02300086753 /* SMB2Tree.swift in Sources */,
|
||||
7924B1A31D89DAE000589DB7 /* Options.swift in Sources */,
|
||||
792572421DF23BDA006A1526 /* LocalHelper.swift in Sources */,
|
||||
79480FF71E3ABDD0007E7275 /* CloudFileProvider.swift in Sources */,
|
||||
79F5745C1DFDB10B00179ABF /* FileObject.swift in Sources */,
|
||||
79BD63C61E2D17880035128C /* OneDriveFileProvide.swift in Sources */,
|
||||
7924B1B01D89F7DE00589DB7 /* FPSStreamTask.swift in Sources */,
|
||||
7924B19A1D89DAE000589DB7 /* Element.swift in Sources */,
|
||||
799396C91D48C02300086753 /* SMB2IOCtl.swift in Sources */,
|
||||
799396DE1D48C02300086753 /* TCPSocketClient.swift in Sources */,
|
||||
799396D81D48C02300086753 /* SMB2Types.swift in Sources */,
|
||||
799396C61D48C02300086753 /* SMB2FileOperation.swift in Sources */,
|
||||
79BD63C91E2D17880035128C /* OneDriveHelper.swift in Sources */,
|
||||
799396C01D48C02300086753 /* SMB2DataTypes.swift in Sources */,
|
||||
799396BA1D48C02300086753 /* SMBFileProvider.swift in Sources */,
|
||||
794C220F1D591A4B00EC49B8 /* SMB2QueryTypes.swift in Sources */,
|
||||
794C21FF1D58912A00EC49B8 /* DropboxHelper.swift in Sources */,
|
||||
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 */,
|
||||
7924B1AD1D89F7D800589DB7 /* SMBClient.swift in Sources */,
|
||||
79BD638D1E2CC2300035128C /* ExtendedLocalFileProvider.swift in Sources */,
|
||||
799396AB1D48C02300086753 /* DropboxFileProvider.swift in Sources */,
|
||||
7924B1A61D89DAE000589DB7 /* Parser.swift in Sources */,
|
||||
799396B11D48C02300086753 /* FileProvider.swift in Sources */,
|
||||
799396B71D48C02300086753 /* SMBClient.swift in Sources */,
|
||||
7924B19D1D89DAE000589DB7 /* Error.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -386,23 +563,37 @@
|
||||
files = (
|
||||
799396B51D48C02300086753 /* LocalFileProvider.swift in Sources */,
|
||||
799396D61D48C02300086753 /* SMB2Tree.swift in Sources */,
|
||||
7924B1A41D89DAE000589DB7 /* Options.swift in Sources */,
|
||||
792572431DF23BDA006A1526 /* LocalHelper.swift in Sources */,
|
||||
79480FF81E3ABDD0007E7275 /* CloudFileProvider.swift in Sources */,
|
||||
79F5745D1DFDB10B00179ABF /* FileObject.swift in Sources */,
|
||||
79BD63C71E2D17880035128C /* OneDriveFileProvide.swift in Sources */,
|
||||
7924B1B11D89F7DF00589DB7 /* FPSStreamTask.swift in Sources */,
|
||||
7924B19B1D89DAE000589DB7 /* Element.swift in Sources */,
|
||||
799396CA1D48C02300086753 /* SMB2IOCtl.swift in Sources */,
|
||||
799396DF1D48C02300086753 /* TCPSocketClient.swift in Sources */,
|
||||
799396D91D48C02300086753 /* SMB2Types.swift in Sources */,
|
||||
799396C71D48C02300086753 /* SMB2FileOperation.swift in Sources */,
|
||||
79BD63CA1E2D17880035128C /* OneDriveHelper.swift in Sources */,
|
||||
799396C11D48C02300086753 /* SMB2DataTypes.swift in Sources */,
|
||||
799396BB1D48C02300086753 /* SMBFileProvider.swift in Sources */,
|
||||
794C22101D591A4B00EC49B8 /* SMB2QueryTypes.swift in Sources */,
|
||||
794C22001D58912A00EC49B8 /* DropboxHelper.swift in Sources */,
|
||||
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 */,
|
||||
7924B1AE1D89F7D900589DB7 /* SMBClient.swift in Sources */,
|
||||
79BD638E1E2CC2300035128C /* ExtendedLocalFileProvider.swift in Sources */,
|
||||
799396AC1D48C02300086753 /* DropboxFileProvider.swift in Sources */,
|
||||
7924B1A71D89DAE000589DB7 /* Parser.swift in Sources */,
|
||||
799396B21D48C02300086753 /* FileProvider.swift in Sources */,
|
||||
799396B81D48C02300086753 /* SMBClient.swift in Sources */,
|
||||
7924B19E1D89DAE000589DB7 /* Error.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -412,12 +603,58 @@
|
||||
799396601D48B7BF00086753 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_VERSION_STRING = 0.11.3;
|
||||
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;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
799396611D48B7BF00086753 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_VERSION_STRING = 0.11.3;
|
||||
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;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -426,9 +663,8 @@
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
BUNDLE_VERSION_STRING = 0.8.2;
|
||||
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;
|
||||
@@ -440,20 +676,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)",
|
||||
@@ -469,7 +700,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;
|
||||
@@ -486,9 +716,8 @@
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
BUNDLE_VERSION_STRING = 0.8.2;
|
||||
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;
|
||||
@@ -500,17 +729,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;
|
||||
@@ -553,22 +778,18 @@
|
||||
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;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_BITCODE = NO;
|
||||
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)",
|
||||
@@ -584,7 +805,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;
|
||||
@@ -614,19 +834,16 @@
|
||||
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;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_BITCODE = NO;
|
||||
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;
|
||||
@@ -667,19 +884,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)",
|
||||
@@ -694,14 +908,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 = "";
|
||||
};
|
||||
@@ -726,16 +939,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;
|
||||
@@ -752,7 +963,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"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "FileProvider"
|
||||
)
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.3.0</string>
|
||||
<string>${BUNDLE_VERSION_STRING}</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.3.0</string>
|
||||
<string>${BUNDLE_VERSION_STRING}</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.3.0</string>
|
||||
<string>${BUNDLE_VERSION_STRING}</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
# 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]
|
||||
[]()
|
||||
[](https://img.shields.io/cocoapods/v/FileProvider.svg)
|
||||
|
||||
[![release badge][release-image]][release-url]
|
||||
[](https://cocoapods.org/pods/FileProvider)
|
||||
[](https://github.com/Carthage/Carthage)
|
||||
|
||||
[![Build Status][travis-image]][travis-url]
|
||||
[![codebeat badge][codebeat-image]][codebeat-url]
|
||||
|
||||
<!---
|
||||
[![Build Status][travis-image]][travis-url]
|
||||
[](https://github.com/Carthage/Carthage)
|
||||
[](https://codecov.io/gh/amosavian/FileProvider)
|
||||
--->
|
||||
|
||||
This library provides implementaion of WebDav and SMB/CIFS (incomplete) and local files.
|
||||
This library provides implementaion of WebDav, Dropbox, OneDrive and SMB2 (incomplete) and local files.
|
||||
|
||||
All functions are async calls and it wont block your main thread.
|
||||
|
||||
@@ -21,44 +25,74 @@ 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 usual file transmission system on Macs.
|
||||
- [ ] **SMBFileProvider** SMB/CIFS and SMB2/3 are file and printer sharing protocol which is originated from IBM & Microsoft and SMB2/3 is now replacing AFP protocol on MacOS. I implemented data types and some basic functions but *main interface is not implemented yet!*
|
||||
- [ ] **DropboxFileProvider** *almost completed. upload, thumbnail and search functions not implemented yet*
|
||||
- [ ] **FTPFileProvider**
|
||||
- [ ] **AmazonS3FileProvider**
|
||||
- [x] **LocalFileProvider** a wrapper around `FileManager` with some additions like searching and reading a portion of file.
|
||||
- [x] **WebDAVFileProvider** WebDAV protocol is defacto file transmission standard, replaced FTP.
|
||||
- [x] **DropboxFileProvider** A wrapper around Dropbox Web API.
|
||||
* For now it has limitation in uploading files up to 150MB.
|
||||
- [x] **OneDriveFileProvider** A wrapper around OneDrive Web API, works with `onedrive.com` and compatible (business) servers.
|
||||
* For now it has limitation in uploading files up to 100MB.
|
||||
- [x] **CloudFileProvider** A wrapper around app's ubiquitous container to iCloud Drive in iOS 8+ API.
|
||||
- [ ] **SMBFileProvider** SMB2/3 introduced in 2006, which is a file and printer sharing protocol originated from Microsoft Windows and now is replacing AFP protocol on MacOS.
|
||||
* Data types and some basic functions are implemented but *main interface is not implemented yet!*
|
||||
* 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**
|
||||
- [ ] **GoogleDriveFileProvider**
|
||||
|
||||
## 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
|
||||
|
||||
### Cocoapods / Carthage / Swift Package Manager
|
||||
|
||||
FileProvider supports both CocoaPods.
|
||||
|
||||
Add this line to your pods file:
|
||||
|
||||
pod "FileProvider"
|
||||
```ruby
|
||||
pod "FileProvider"
|
||||
```
|
||||
|
||||
Or add this to Cartfile:
|
||||
|
||||
```
|
||||
github "amosavian/FileProvider"
|
||||
```
|
||||
|
||||
Or to use in Swift Package Manager add this line in `Dependencies`:
|
||||
|
||||
```swift
|
||||
.Package(url: "https://github.com/amosavian/FileProvider.git", majorVersion: 0, minorVersion: 8)
|
||||
```
|
||||
|
||||
### Manually
|
||||
|
||||
### Git
|
||||
To have latest updates with ease, use this command on terminal to get a clone:
|
||||
|
||||
git clone https://github.com/amosavian/FileProvider FileProvider
|
||||
|
||||
```bash
|
||||
git clone https://github.com/amosavian/FileProvider
|
||||
```
|
||||
|
||||
You can update your library using this command in FileProvider folder:
|
||||
|
||||
git pull
|
||||
```bash
|
||||
git pull
|
||||
```
|
||||
|
||||
if you have a git based project, use this command in your projects directory to add this project as a submodule to your project:
|
||||
|
||||
git submodule add https://github.com/amosavian/FileProvider FileProvider
|
||||
```bash
|
||||
git submodule add https://github.com/amosavian/FileProvider
|
||||
```
|
||||
Then you can do either:
|
||||
|
||||
### Manually
|
||||
Copy Source folder to your project and Voila!
|
||||
* Copy Source folder to your project and Voila!
|
||||
|
||||
* Drop FileProvider.xcodeproj to you Xcode workspace and add the framework to your Embeded Binaries in target.
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -68,73 +102,94 @@ 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)
|
||||
// Equals with:
|
||||
let documentsProvider = LocalFileProvider(directory: .documentDirectory, domainMask: = .userDomainMask)
|
||||
|
||||
// Equals with:
|
||||
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||
let documentsProvider = LocalFileProvider(baseURL: documentsURL)
|
||||
```
|
||||
|
||||
Also for using group shared container:
|
||||
|
||||
```swift
|
||||
let documentsProvider = LocalFileProvider(sharedContainerId: "group.yourcompany.appContainer")
|
||||
// Replace your group identifier with string above
|
||||
```
|
||||
|
||||
You can't change the base url later. and all paths are related to this base url by default.
|
||||
|
||||
To initialize an iCloud Container provider use below code, This will automatically manager creating Documents folder in container:
|
||||
|
||||
```swift
|
||||
let documentsProvider = CloudFileProvider(containerId: nil)
|
||||
```
|
||||
|
||||
|
||||
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)
|
||||
|
||||
* For Dropbox, user is clientID and password is Token which both must be retrieved via [OAuth2 API of Dropbox](https://www.dropbox.com/developers/reference/oauth-guide). There are libraries like [p2/OAuth2](https://github.com/p2/OAuth2) or [OAuthSwift](https://github.com/OAuthSwift/OAuthSwift) which can facilate the procedure to retrieve token.
|
||||
* For Dropbox & OneDrive, user is clientID and password is Token which both must be retrieved via [OAuth2 API of Dropbox](https://www.dropbox.com/developers/reference/oauth-guide). There are libraries like [p2/OAuth2](https://github.com/p2/OAuth2) or [OAuthSwift](https://github.com/OAuthSwift/OAuthSwift) which can facilate the procedure to retrieve token. The latter is easier to use and prefered.
|
||||
|
||||
For interaction with UI, set delegate variable of `FileProvider` object
|
||||
|
||||
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 +197,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,102 +209,183 @@ 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:
|
||||
|
||||
```swift
|
||||
func storageProperties(completionHandler: { total, used in
|
||||
print("Total Storage Space: \(total)")
|
||||
print("Used Space: \(used)")
|
||||
print("Free Space: \(total - used)")
|
||||
})
|
||||
```
|
||||
|
||||
* if this function is unavailable on provider or an error has been occurred, total space will be reported `-1` and used space `0`
|
||||
|
||||
### Change current directory
|
||||
|
||||
documentsProvider.currentPath = "/New Folder"
|
||||
// now path is ~/Documents/New Folder
|
||||
```swift
|
||||
documentsProvider.currentPath = "/New Folder"
|
||||
// now path is ~/Documents/New Folder
|
||||
```
|
||||
|
||||
You can then pass "" (empty string) to 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.
|
||||
|
||||
### Thumbnail and meta-information
|
||||
|
||||
Providers which conform `ExtendedFileProvider` are able to generate thumbnail or provide file meta-information for images, media and pdf files.
|
||||
|
||||
Local, OneDrive and Dropbox providers support this functionality.
|
||||
|
||||
##### Thumbnails
|
||||
To check either file thumbnail is supported or not and fetch thumbnail, use (and modify) these example code:
|
||||
|
||||
```swift
|
||||
let path = "/newImage.jpg"
|
||||
let thumbSize = CGSize(width: 64, height: 64)
|
||||
if documentsProvider.thumbnailOfFileSupported(path: path {
|
||||
documentsProvider..thumbnailOfFile(path: file.path, dimension: thumbSize, completionHandler: { (image, error) in
|
||||
DispatchQueue.main.async {
|
||||
self.previewImage.image = image
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* Please note it won't cache generated images. if you don't do it yourself, it may hit you app's performance.
|
||||
|
||||
##### Meta-informations
|
||||
|
||||
To get meta-information like image/video taken date, dimension, etc., use (and modify) these example code:
|
||||
|
||||
```swift
|
||||
if documentsProvider..propertiesOfFile(path: file.path, completionHandler: { (propertiesDictionary, keys, error) in
|
||||
for key in keys {
|
||||
print("\(key): \(propertiesDictionary[key])")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* **Bonus:** You can modify/extend Local provider generator by setting `LocalFileInformationGenerator` static variables and methods
|
||||
|
||||
## Contribute
|
||||
|
||||
We would love for you to contribute to **FileProvider**, check the `LICENSE` file for more info.
|
||||
@@ -267,17 +403,15 @@ 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
|
||||
[release-url]: https://github.com/amosavian/FileProvider/releases
|
||||
[release-image]: https://img.shields.io/github/release/amosavian/FileProvider.svg
|
||||
@@ -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: "&", options: .LiteralSearch)
|
||||
|
||||
// replace the other four special characters
|
||||
let escapeChars = ["<" : "<", ">" : ">", "'" : "'", "\"" : """]
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+29
@@ -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[];
|
||||
|
||||
Executable
+128
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+285
@@ -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: "&", options: .literal)
|
||||
|
||||
// replace the other four special characters
|
||||
let escapeChars = ["<" : "<", ">" : ">", "'" : "'", "\"" : """]
|
||||
for (char, echar) in escapeChars {
|
||||
escaped = escaped.replacingOccurrences(of: char, with: echar, options: .literal)
|
||||
}
|
||||
|
||||
return escaped
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+37
@@ -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
|
||||
}
|
||||
Executable
+26
@@ -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>
|
||||
Executable
+52
@@ -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() {}
|
||||
|
||||
}
|
||||
Executable
+101
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,480 @@
|
||||
//
|
||||
// CloudFileProvider.swift
|
||||
// FileProvider
|
||||
//
|
||||
// Created by Amir Abbas Mousavian.
|
||||
// Copyright © 2017 Mousavian. Distributed under MIT license.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
open class CloudFileProvider: LocalFileProvider {
|
||||
|
||||
public var type: String {
|
||||
return "iCloudDrive"
|
||||
}
|
||||
|
||||
/// Actually is readonly, value is true
|
||||
override open var isCoorinating: Bool {
|
||||
get {
|
||||
return true
|
||||
}
|
||||
set {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
open var containerId: String?
|
||||
|
||||
public init? (containerId: String?) {
|
||||
assert(!Thread.isMainThread, "LocalFileProvider.init(containerId:) is not recommended to be executed on Main Thread.")
|
||||
guard FileManager.default.ubiquityIdentityToken == nil else {
|
||||
return nil
|
||||
}
|
||||
guard let ubiquityURL = FileManager.default.url(forUbiquityContainerIdentifier: containerId) else {
|
||||
return nil
|
||||
}
|
||||
self.containerId = containerId
|
||||
let baseURL = ubiquityURL.standardized.appendingPathComponent("Documents/")
|
||||
super.init(baseURL: baseURL)
|
||||
self.isCoorinating = true
|
||||
|
||||
dispatch_queue = DispatchQueue(label: "FileProvider.\(self.type)", attributes: DispatchQueue.Attributes.concurrent)
|
||||
operation_queue = OperationQueue()
|
||||
operation_queue.name = "FileProvider.\(self.type).Operation"
|
||||
|
||||
fileManager.url(forUbiquityContainerIdentifier: containerId)
|
||||
opFileManager.url(forUbiquityContainerIdentifier: containerId)
|
||||
|
||||
try? fileManager.createDirectory(at: baseURL, withIntermediateDirectories: true)
|
||||
}
|
||||
|
||||
open override func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {
|
||||
dispatch_queue.async {
|
||||
let pathURL = self.url(of: path)
|
||||
let query = NSMetadataQuery()
|
||||
query.predicate = NSPredicate(format: "%K BEGINSWITH %@", NSMetadataItemPathKey, pathURL.path)
|
||||
query.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]
|
||||
var finishObserver: NSObjectProtocol?
|
||||
finishObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.NSMetadataQueryDidFinishGathering, object: query, queue: nil, using: { (notification) in
|
||||
defer {
|
||||
query.stop()
|
||||
NotificationCenter.default.removeObserver(finishObserver!)
|
||||
}
|
||||
|
||||
guard let results = query.results as? [NSMetadataItem] else {
|
||||
return
|
||||
}
|
||||
|
||||
query.disableUpdates()
|
||||
|
||||
var contents = [FileObject]()
|
||||
for result in results {
|
||||
guard let attribs = result.values(forAttributes: [NSMetadataItemURLKey, NSMetadataItemFSNameKey, NSMetadataItemPathKey, NSMetadataItemFSSizeKey, NSMetadataItemContentTypeTreeKey, NSMetadataItemFSCreationDateKey, NSMetadataItemFSContentChangeDateKey]) else {
|
||||
continue
|
||||
}
|
||||
|
||||
guard let url = (attribs[NSMetadataItemURLKey] as? URL)?.standardized, url.deletingLastPathComponent().path.trimmingCharacters(in: pathTrimSet) == pathURL.path.trimmingCharacters(in: pathTrimSet) else {
|
||||
continue
|
||||
}
|
||||
|
||||
if let file = self.mapFileObject(attributes: attribs) {
|
||||
contents.append(file)
|
||||
}
|
||||
}
|
||||
|
||||
completionHandler(contents, nil)
|
||||
})
|
||||
query.start()
|
||||
}
|
||||
}
|
||||
|
||||
/// iCloud Storage size and free space is unavailable, it returns local space
|
||||
open override func storageProperties(completionHandler: (@escaping (_ total: Int64, _ used: Int64) -> Void)) {
|
||||
super.storageProperties(completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
open override func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
|
||||
dispatch_queue.async {
|
||||
let pathURL = self.url(of: path)
|
||||
let query = NSMetadataQuery()
|
||||
query.predicate = NSPredicate(format: "%K LIKE %@", NSMetadataItemPathKey, pathURL.path)
|
||||
query.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]
|
||||
var finishObserver: NSObjectProtocol?
|
||||
finishObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.NSMetadataQueryDidFinishGathering, object: query, queue: nil, using: { (notification) in
|
||||
defer {
|
||||
query.stop()
|
||||
NotificationCenter.default.removeObserver(finishObserver!)
|
||||
}
|
||||
|
||||
query.disableUpdates()
|
||||
|
||||
guard let result = (query.results as? [NSMetadataItem])?.first, let attribs = result.values(forAttributes: [NSMetadataItemURLKey, NSMetadataItemFSNameKey, NSMetadataItemPathKey, NSMetadataItemFSSizeKey, NSMetadataItemContentTypeTreeKey, NSMetadataItemFSCreationDateKey, NSMetadataItemFSContentChangeDateKey]) else {
|
||||
let error = self.throwError(path, code: CocoaError.fileNoSuchFile)
|
||||
completionHandler(nil, error)
|
||||
return
|
||||
}
|
||||
|
||||
if let file = self.mapFileObject(attributes: attribs) {
|
||||
completionHandler(file, nil)
|
||||
} else {
|
||||
let noFileError = self.throwError(path, code: CocoaError.fileNoSuchFile)
|
||||
completionHandler(nil, noFileError)
|
||||
}
|
||||
})
|
||||
query.start()
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open override func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let r = super.create(folder: folderName, at: atPath, completionHandler: completionHandler)
|
||||
return CloudOperationHandle(operationType: r!.operationType, baseURL: self.baseURL)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open override func create(file fileName: String, at atPath: String, contents data: Data?, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let r = super.create(file: fileName, at: atPath, contents: data, completionHandler: completionHandler)
|
||||
return CloudOperationHandle(operationType: r!.operationType, baseURL: self.baseURL)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open override func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let r = super.moveItem(path: path, to: toPath, overwrite: overwrite, completionHandler: completionHandler)
|
||||
return CloudOperationHandle(operationType: r!.operationType, baseURL: self.baseURL)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open override func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let r = super.copyItem(path: path, to: toPath, overwrite: overwrite, completionHandler: completionHandler)
|
||||
return CloudOperationHandle(operationType: r!.operationType, baseURL: self.baseURL)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open override func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let r = super.removeItem(path: path, completionHandler: completionHandler)
|
||||
return CloudOperationHandle(operationType: r!.operationType, baseURL: self.baseURL)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open override func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
// TODO: Make use of overwrite parameter
|
||||
let opType = FileOperationType.copy(source: localFile.absoluteString, destination: toPath)
|
||||
operation_queue.addOperation {
|
||||
let tempFolder: URL
|
||||
if #available(iOS 10.0, macOS 10.12, tvOS 10.0, *) {
|
||||
tempFolder = FileManager.default.temporaryDirectory
|
||||
} else {
|
||||
tempFolder = URL(fileURLWithPath: NSTemporaryDirectory())
|
||||
}
|
||||
let tmpFile = tempFolder.appendingPathComponent(UUID().uuidString)
|
||||
|
||||
do {
|
||||
try self.opFileManager.copyItem(at: localFile, to: tmpFile)
|
||||
let toUrl = self.url(of: toPath)
|
||||
try self.opFileManager.setUbiquitous(true, itemAt: tmpFile, destinationURL: toUrl)
|
||||
completionHandler?(nil)
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.delegate?.fileproviderSucceed(self, operation: opType)
|
||||
})
|
||||
} catch let e {
|
||||
if self.opFileManager.fileExists(atPath: tmpFile.path) {
|
||||
try? self.opFileManager.removeItem(at: tmpFile)
|
||||
}
|
||||
completionHandler?(e)
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.delegate?.fileproviderFailed(self, operation: opType)
|
||||
})
|
||||
}
|
||||
}
|
||||
return CloudOperationHandle(operationType: opType, baseURL: self.baseURL)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open override func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.copy(source: path, destination: toLocalURL.absoluteString)
|
||||
|
||||
do {
|
||||
try self.opFileManager.startDownloadingUbiquitousItem(at: self.url(of: path))
|
||||
} catch let e {
|
||||
completionHandler?(e)
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.delegate?.fileproviderFailed(self, operation: opType)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
let r = super.copyItem(path: path, toLocalURL: toLocalURL, completionHandler: completionHandler)
|
||||
return CloudOperationHandle(operationType: r!.operationType, baseURL: self.baseURL)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open override func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
|
||||
let r = super.contents(path: path, completionHandler: completionHandler)
|
||||
return CloudOperationHandle(operationType: r!.operationType, baseURL: self.baseURL)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open override func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
|
||||
let r = super.contents(path: path, offset: offset, length: length, completionHandler: completionHandler)
|
||||
return CloudOperationHandle(operationType: r!.operationType, baseURL: self.baseURL)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open override func writeContents(path: String, contents data: Data, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let r = super.writeContents(path: path, contents: data, atomically: atomically, overwrite: overwrite, completionHandler: completionHandler)
|
||||
return CloudOperationHandle(operationType: r!.operationType, baseURL: self.baseURL)
|
||||
}
|
||||
|
||||
open override func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
|
||||
dispatch_queue.async {
|
||||
let pathURL = self.url(of: path)
|
||||
let query = NSMetadataQuery()
|
||||
query.predicate = NSPredicate(format: "(%K BEGINSWITH %@) && (%K LIKE %@)", NSMetadataItemPathKey, pathURL.path, NSMetadataItemFSNameKey, query)
|
||||
query.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]
|
||||
|
||||
var lastReportedCount = 0
|
||||
|
||||
if let foundItemHandler = foundItemHandler {
|
||||
var updateObserver: NSObjectProtocol?
|
||||
|
||||
updateObserver = NotificationCenter.default.addObserver(forName: .NSMetadataQueryGatheringProgress, object: query, queue: nil, using: { (notification) in
|
||||
|
||||
query.disableUpdates()
|
||||
|
||||
guard query.resultCount > lastReportedCount else { return }
|
||||
|
||||
for index in lastReportedCount..<query.resultCount {
|
||||
guard let attribs = (query.result(at: index) as? NSMetadataItem)?.values(forAttributes: [NSMetadataItemURLKey, NSMetadataItemFSNameKey, NSMetadataItemPathKey, NSMetadataItemFSSizeKey, NSMetadataItemContentTypeTreeKey, NSMetadataItemFSCreationDateKey, NSMetadataItemFSContentChangeDateKey]) else {
|
||||
continue
|
||||
}
|
||||
|
||||
guard let url = (attribs[NSMetadataItemURLKey] as? URL)?.standardized, recursive || url.deletingLastPathComponent().path.trimmingCharacters(in: pathTrimSet) == pathURL.path.trimmingCharacters(in: pathTrimSet) else {
|
||||
continue
|
||||
}
|
||||
|
||||
if let file = self.mapFileObject(attributes: attribs) {
|
||||
foundItemHandler(file)
|
||||
}
|
||||
}
|
||||
lastReportedCount = query.resultCount
|
||||
|
||||
query.enableUpdates()
|
||||
})
|
||||
}
|
||||
|
||||
var finishObserver: NSObjectProtocol?
|
||||
finishObserver = NotificationCenter.default.addObserver(forName: .NSMetadataQueryDidFinishGathering, object: query, queue: nil, using: { (notification) in
|
||||
defer {
|
||||
query.stop()
|
||||
NotificationCenter.default.removeObserver(finishObserver!)
|
||||
}
|
||||
|
||||
guard let results = query.results as? [NSMetadataItem] else {
|
||||
return
|
||||
}
|
||||
|
||||
query.disableUpdates()
|
||||
|
||||
var contents = [FileObject]()
|
||||
for result in results {
|
||||
guard let attribs = result.values(forAttributes: [NSMetadataItemURLKey, NSMetadataItemFSNameKey, NSMetadataItemPathKey, NSMetadataItemFSSizeKey, NSMetadataItemContentTypeTreeKey, NSMetadataItemFSCreationDateKey, NSMetadataItemFSContentChangeDateKey]) else {
|
||||
continue
|
||||
}
|
||||
|
||||
guard let url = (attribs[NSMetadataItemURLKey] as? URL)?.standardized, recursive || url.deletingLastPathComponent().path.trimmingCharacters(in: pathTrimSet) == pathURL.path.trimmingCharacters(in: pathTrimSet) else {
|
||||
continue
|
||||
}
|
||||
|
||||
if let file = self.mapFileObject(attributes: attribs) {
|
||||
contents.append(file)
|
||||
}
|
||||
}
|
||||
|
||||
completionHandler(contents, nil)
|
||||
})
|
||||
|
||||
query.start()
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var monitors = [URL: (NSMetadataQuery, NSObjectProtocol)]()
|
||||
//
|
||||
open override func registerNotifcation(path: String, eventHandler: @escaping (() -> Void)) {
|
||||
self.unregisterNotifcation(path: path)
|
||||
let pathURL = self.url(of: path)
|
||||
let query = NSMetadataQuery()
|
||||
query.predicate = NSPredicate(format: "(%K BEGINSWITH %@)", NSMetadataItemPathKey, pathURL.path)
|
||||
query.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]
|
||||
|
||||
let updateObserver = NotificationCenter.default.addObserver(forName: .NSMetadataQueryDidUpdate, object: query, queue: nil, using: { (notification) in
|
||||
|
||||
query.disableUpdates()
|
||||
|
||||
eventHandler()
|
||||
|
||||
query.enableUpdates()
|
||||
})
|
||||
|
||||
query.start()
|
||||
|
||||
monitors[pathURL] = (query, updateObserver)
|
||||
}
|
||||
|
||||
open override func unregisterNotifcation(path: String) {
|
||||
let key = url(of: path)
|
||||
guard let (query, observer) = monitors[key] else {
|
||||
return
|
||||
}
|
||||
query.disableUpdates()
|
||||
query.stop()
|
||||
monitors.removeValue(forKey: key)
|
||||
NotificationCenter.default.removeObserver(observer)
|
||||
}
|
||||
|
||||
open override func isRegisteredForNotification(path: String) -> Bool {
|
||||
return monitors[url(of: path)] != nil
|
||||
}
|
||||
|
||||
/// may return nil
|
||||
open override func copy(with zone: NSZone? = nil) -> Any {
|
||||
let copy = CloudFileProvider(containerId: self.containerId)
|
||||
copy?.currentPath = self.currentPath
|
||||
copy?.delegate = self.delegate
|
||||
copy?.fileOperationDelegate = self.fileOperationDelegate
|
||||
copy?.isPathRelative = self.isPathRelative
|
||||
return copy as Any
|
||||
}
|
||||
|
||||
fileprivate func mapFileObject(attributes attribs: [String: Any]) -> FileObject? {
|
||||
guard let url = (attribs[NSMetadataItemURLKey] as? URL)?.standardized, let name = attribs[NSMetadataItemFSNameKey] as? String else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let path = self.relativePathOf(url: url)
|
||||
let rpath = path.hasPrefix("/") ? path.substring(from: path.index(after: path.startIndex)) : path
|
||||
let relativeUrl = URL(string: rpath, relativeTo: self.baseURL)
|
||||
let file = FileObject(url: relativeUrl ?? url, name: name, path: path)
|
||||
|
||||
file.size = (attribs[NSMetadataItemFSSizeKey] as? NSNumber)?.int64Value ?? -1
|
||||
file.creationDate = attribs[NSMetadataItemFSCreationDateKey] as? Date
|
||||
file.modifiedDate = attribs[NSMetadataItemFSContentChangeDateKey] as? Date
|
||||
let isFolder = (attribs[NSMetadataItemContentTypeTreeKey] as? [String])?.contains("public.folder") ?? false
|
||||
let isSymbolic = (attribs[NSMetadataItemContentTypeTreeKey] as? [String])?.contains("public.symlink") ?? false
|
||||
file.type = isFolder ? .directory : (isSymbolic ? .symbolicLink : .regular)
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
/// Removes local copy of file, but spares cloud copy
|
||||
open func evictItem(path: String, completionHandler: SimpleCompletionHandler) {
|
||||
operation_queue.addOperation {
|
||||
do {
|
||||
try self.opFileManager.evictUbiquitousItem(at: self.url(of: path))
|
||||
completionHandler?(nil)
|
||||
} catch let e {
|
||||
completionHandler?(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open func temporaryLink(to path: String, completionHandler: @escaping ((_ link: URL?, _ attribute: FileObject?, _ expiration: Date?, _ error: Error?) -> Void)) {
|
||||
operation_queue.addOperation {
|
||||
do {
|
||||
var expiration: NSDate?
|
||||
let url = try self.opFileManager.url(forPublishingUbiquitousItemAt: self.url(of: path), expiration: &expiration)
|
||||
completionHandler(url, nil, expiration as Date?, nil)
|
||||
} catch let e {
|
||||
completionHandler(nil, nil, nil, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open class CloudOperationHandle: OperationHandle {
|
||||
public let baseURL: URL?
|
||||
public let operationType: FileOperationType
|
||||
|
||||
init (operationType: FileOperationType, baseURL: URL?) {
|
||||
self.baseURL = baseURL
|
||||
self.operationType = operationType
|
||||
}
|
||||
|
||||
private var sourceURL: URL? {
|
||||
guard let source = operationType.source, let baseURL = baseURL else { return nil }
|
||||
return source.hasPrefix("file://") ? URL(fileURLWithPath: source) : baseURL.appendingPathComponent(source)
|
||||
}
|
||||
|
||||
private var destURL: URL? {
|
||||
guard let dest = operationType.destination, let baseURL = baseURL 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")
|
||||
|
||||
guard let url = destURL ?? sourceURL, let item = CloudOperationHandle.getMetadataItem(url: url) else { return 0 }
|
||||
let downloaded = item.value(forAttribute: NSMetadataUbiquitousItemPercentDownloadedKey) as? Double ?? 0
|
||||
let uploaded = item.value(forAttribute: NSMetadataUbiquitousItemPercentUploadedKey) as? Double ?? 0
|
||||
guard let size = item.value(forAttribute: NSMetadataItemFSSizeKey) as? Int64 else { return -1 }
|
||||
if (downloaded == 0 || downloaded == 100) && (uploaded > 0 && uploaded < 100) {
|
||||
return Int64(uploaded * (Double(size) / 100))
|
||||
} else if (uploaded == 0 || uploaded == 100) && (downloaded > 0 && downloaded < 100) {
|
||||
return Int64(downloaded * (Double(size) / 100))
|
||||
} else if uploaded == 100 || downloaded == 100 {
|
||||
return size
|
||||
}
|
||||
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")
|
||||
guard let url = destURL ?? sourceURL, let item = CloudOperationHandle.getMetadataItem(url: url) else { return -1 }
|
||||
return item.value(forAttribute: NSMetadataItemFSSizeKey) as? Int64 ?? -1
|
||||
}
|
||||
|
||||
open var inProgress: Bool {
|
||||
guard let url = destURL ?? sourceURL, let item = CloudOperationHandle.getMetadataItem(url: url) else { return false }
|
||||
let downloadStatus = item.value(forAttribute: NSMetadataUbiquitousItemDownloadingStatusKey) as? String ?? NSMetadataUbiquitousItemDownloadingStatusNotDownloaded
|
||||
let isUploading = item.value(forAttribute: NSMetadataUbiquitousItemIsUploadingKey) as? Bool ?? false
|
||||
return downloadStatus == NSMetadataUbiquitousItemDownloadingStatusCurrent || isUploading
|
||||
}
|
||||
|
||||
/// Not usable in local provider
|
||||
open func cancel() -> Bool {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
fileprivate static func getMetadataItem(url: URL) -> NSMetadataItem? {
|
||||
let query = NSMetadataQuery()
|
||||
query.predicate = NSPredicate(format: "(%K LIKE %@)", NSMetadataItemPathKey, url.path)
|
||||
query.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]
|
||||
|
||||
var item: NSMetadataItem?
|
||||
|
||||
let group = DispatchGroup()
|
||||
var finishObserver: NSObjectProtocol?
|
||||
finishObserver = NotificationCenter.default.addObserver(forName: .NSMetadataQueryDidFinishGathering, object: query, queue: nil, using: { (notification) in
|
||||
defer {
|
||||
query.stop()
|
||||
group.leave()
|
||||
NotificationCenter.default.removeObserver(finishObserver!)
|
||||
}
|
||||
|
||||
if query.resultCount > 0 {
|
||||
item = query.result(at: 0) as? NSMetadataItem
|
||||
}
|
||||
|
||||
query.disableUpdates()
|
||||
|
||||
})
|
||||
|
||||
group.enter()
|
||||
query.start()
|
||||
_ = group.wait(timeout: DispatchTime.now() + 30)
|
||||
return item
|
||||
}
|
||||
}
|
||||
+364
-300
@@ -1,3 +1,4 @@
|
||||
|
||||
//
|
||||
// DropboxFileProvider.swift
|
||||
// FileProvider
|
||||
@@ -7,280 +8,274 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct FileProviderDropboxError: ErrorType, CustomStringConvertible {
|
||||
public let code: FileProviderHTTPErrorCode
|
||||
public let path: String
|
||||
|
||||
public var description: String {
|
||||
return code.description
|
||||
}
|
||||
}
|
||||
|
||||
public final class DropboxFileObject: FileObject {
|
||||
public let serverTime: NSDate?
|
||||
public let id: String?
|
||||
public let rev: String?
|
||||
|
||||
public init(absoluteURL: NSURL, name: String, path: String, size: Int64, serverTime: NSDate?, createdDate: NSDate?, modifiedDate: NSDate?, fileType: FileType, isHidden: Bool, isReadOnly: Bool, id: String?, rev: String?) {
|
||||
self.serverTime = serverTime
|
||||
self.id = id
|
||||
self.rev = rev
|
||||
super.init(absoluteURL: absoluteURL, name: name, path: path, size: size, createdDate: createdDate, modifiedDate: modifiedDate, fileType: fileType, isHidden: isHidden, isReadOnly: isReadOnly)
|
||||
}
|
||||
}
|
||||
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: FileProviderBasicRemote {
|
||||
open static let type: String = "DropBox"
|
||||
open let isPathRelative: Bool
|
||||
open let baseURL: URL?
|
||||
open var currentPath: String
|
||||
|
||||
open let apiURL: URL
|
||||
open let contentURL: URL
|
||||
|
||||
open var dispatch_queue: DispatchQueue
|
||||
open var operation_queue: OperationQueue {
|
||||
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?
|
||||
private var session: NSURLSession {
|
||||
open weak var delegate: FileProviderDelegate?
|
||||
open let credential: URLCredential?
|
||||
open private(set) var cache: URLCache?
|
||||
public var useCache: Bool
|
||||
public var validatingCache: Bool
|
||||
|
||||
fileprivate var _session: URLSession?
|
||||
fileprivate var sessionDelegate: SessionDelegate?
|
||||
public var session: URLSession {
|
||||
if _session == nil {
|
||||
let queue = NSOperationQueue()
|
||||
//queue.underlyingQueue = dispatch_queue
|
||||
_session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), delegate: self, delegateQueue: queue)
|
||||
self.sessionDelegate = SessionDelegate(fileProvider: self, credential: credential)
|
||||
let config = URLSessionConfiguration.default
|
||||
config.urlCache = cache
|
||||
config.requestCachePolicy = .returnCacheDataElseLoad
|
||||
_session = URLSession(configuration: config, delegate: sessionDelegate as URLSessionDelegate?, delegateQueue: self.operation_queue)
|
||||
}
|
||||
return _session!
|
||||
}
|
||||
|
||||
public init? (baseURL: NSURL, credential: NSURLCredential?) {
|
||||
if !["http", "https"].contains(baseURL.uw_scheme.lowercaseString) {
|
||||
return nil
|
||||
}
|
||||
self.baseURL = baseURL
|
||||
dispatch_queue = dispatch_queue_create("FileProvider.\(type)", DISPATCH_QUEUE_CONCURRENT)
|
||||
//let url = baseURL.uw_absoluteString
|
||||
public init? (credential: URLCredential?, cache: URLCache? = nil) {
|
||||
self.baseURL = nil
|
||||
self.isPathRelative = true
|
||||
self.currentPath = ""
|
||||
self.useCache = false
|
||||
self.validatingCache = true
|
||||
self.cache = cache
|
||||
self.credential = credential
|
||||
|
||||
self.apiURL = URL(string: "https://api.dropboxapi.com/2/")!
|
||||
self.contentURL = URL(string: "https://content.dropboxapi.com/2/")!
|
||||
|
||||
dispatch_queue = DispatchQueue(label: "FileProvider.\(type(of: self).type)", attributes: DispatchQueue.Attributes.concurrent)
|
||||
operation_queue = OperationQueue()
|
||||
operation_queue.name = "FileProvider.\(type(of: self).type).Operation"
|
||||
|
||||
}
|
||||
|
||||
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/list_revisions")!
|
||||
let request = NSMutableURLRequest(URL: url)
|
||||
request.HTTPMethod = "POST"
|
||||
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
|
||||
let url = URL(string: "files/get_metadata", relativeTo: apiURL)!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
let requestDictionary = ["path": correctPath(path)!]
|
||||
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: [String: AnyObject] = ["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) : nil
|
||||
if let data = data, let jsonStr = String(data: data, encoding: NSUTF8StringEncoding) {
|
||||
let json = self.jsonToDictionary(jsonStr)
|
||||
if (json?["is_deleted"] as? NSNumber)?.boolValue ?? false, let entries = json?["entries"] as? [AnyObject] where entries.count > 0 , let entry = entries[0] as? [String: AnyObject], let file = self.mapToFileObject(entry) {
|
||||
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 = DropboxFileObject(json: json) {
|
||||
fileObject = file
|
||||
}
|
||||
completionHandler(attributes: nil, error: dbError)
|
||||
return
|
||||
}
|
||||
completionHandler(attributes: nil, error: error)
|
||||
}
|
||||
completionHandler(fileObject, dbError ?? error)
|
||||
})
|
||||
task.resume()
|
||||
}
|
||||
|
||||
public weak var fileOperationDelegate: FileOperationDelegate?
|
||||
open func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void)) {
|
||||
let url = URL(string: "users/get_space_usage", relativeTo: apiURL)!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
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(totalSize, usedSize)
|
||||
})
|
||||
task.resume()
|
||||
}
|
||||
|
||||
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, 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, 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 = "files/create_folder"
|
||||
case .copy:
|
||||
url = "files/copy"
|
||||
case .move:
|
||||
url = "files/move"
|
||||
case .remove:
|
||||
url = "files/delete"
|
||||
default: // modify, link, fetch
|
||||
return nil
|
||||
}
|
||||
var request = URLRequest(url: URL(string: url, relativeTo: apiURL)!)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
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 ?? "") : 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) {
|
||||
NotImplemented()
|
||||
let request = NSMutableURLRequest(URL: absoluteURL(toPath))
|
||||
request.HTTPMethod = "PUT"
|
||||
let task = session.uploadTaskWithRequest(request, fromFile: localFile) { (data, response, error) in
|
||||
completionHandler?(error: error)
|
||||
self.delegateNotify(.Move(source: localFile.uw_absoluteString, destination: toPath), error: error)
|
||||
public func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.copy(source: localFile.absoluteString, destination: toPath)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
task.taskDescription = self.dictionaryToJSON(["type": "Copy", "source": localFile.uw_absoluteString, "dest": toPath])
|
||||
task.resume()
|
||||
return upload_simple(toPath, localFile: localFile, overwrite: overwrite, operation: opType, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
public func copyPathToLocalFile(path: String, toLocalURL destURL: NSURL, completionHandler: SimpleCompletionHandler) {
|
||||
let url = NSURL(string: "https://api.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: "files/download", relativeTo: contentURL)!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "GET"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
let requestDictionary = ["path": path]
|
||||
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) : nil
|
||||
completionHandler?(error: dbError ?? error)
|
||||
let requestDictionary: [String: AnyObject] = ["path": path as NSString]
|
||||
let requestJson = dictionaryToJSON(requestDictionary) ?? ""
|
||||
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 = 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 contentsAtPath(path: String, offset: Int64, length: Int, completionHandler: ((contents: NSData?, error: ErrorType?) -> Void)) {
|
||||
let url = NSURL(string: "https://api.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: "files/download", relativeTo: contentURL)!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "GET"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
if length > 0 {
|
||||
request.setValue("bytes=\(offset)-\(offset + length)", forHTTPHeaderField: "Range")
|
||||
request.setValue("bytes=\(offset)-\(offset + length - 1)", forHTTPHeaderField: "Range")
|
||||
} else if offset > 0 && length < 0 {
|
||||
request.setValue("bytes=\(offset)-", forHTTPHeaderField: "Range")
|
||||
}
|
||||
let requestDictionary = ["path": path]
|
||||
let requestDictionary: [String: AnyObject] = ["path": correctPath(path)! as NSString]
|
||||
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) : nil
|
||||
completionHandler(contents: nil, error: dbError ?? error)
|
||||
return
|
||||
}
|
||||
let destURL = NSURL(fileURLWithPath: NSTemporaryDirectory()).uw_URLByAppendingPathComponent(cacheURL.lastPathComponent ?? "tmpfile")
|
||||
do {
|
||||
try NSFileManager.defaultManager().moveItemAtURL(cacheURL, toURL: destURL)
|
||||
completionHandler(contents: NSData(contentsOfURL: destURL), error: error)
|
||||
} catch let e {
|
||||
completionHandler(contents: nil, error: e)
|
||||
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))
|
||||
}
|
||||
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) {
|
||||
NotImplemented()
|
||||
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
|
||||
defer {
|
||||
self.delegateNotify(.Modify(path: path), error: error)
|
||||
}
|
||||
if atomically {
|
||||
self.moveItemAtPath((path as NSString).stringByAppendingPathExtension("tmp")!, toPath: path, completionHandler: completionHandler)
|
||||
}
|
||||
if let error = error {
|
||||
// If there is no error, completionHandler has been executed by move command
|
||||
completionHandler?(error: error)
|
||||
}
|
||||
public func writeContents(path: String, contents data: Data, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.modify(path: path)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
task.taskDescription = self.dictionaryToJSON(["type": "Modify", "source": path])
|
||||
task.resume()
|
||||
// FIXME: remove 150MB restriction
|
||||
return upload_simple(path, data: data, overwrite: overwrite, operation: opType, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
public func searchFilesAtPath(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: ((files: [FileObject], error: ErrorType?) -> Void)) {
|
||||
NotImplemented()
|
||||
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(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.
|
||||
@@ -289,137 +284,206 @@ extension DropboxFileProvider: FileProviderReadWrite {
|
||||
*/
|
||||
NotImplemented()
|
||||
}
|
||||
private func unregisterNotifcation(path: String) {
|
||||
fileprivate func unregisterNotifcation(path: String) {
|
||||
NotImplemented()
|
||||
}
|
||||
|
||||
// TODO: Implement /copy_reference, /get_account & /get_current_account
|
||||
}
|
||||
|
||||
private extension DropboxFileProvider {
|
||||
private func list(path: String, cursor: String? = nil, prevContents: [DropboxFileObject] = [], recursive: Bool = false, completionHandler: ((contents: [FileObject], cursor: String?, error: ErrorType?) -> Void)) {
|
||||
var requestDictionary = [String: AnyObject]()
|
||||
let url: NSURL
|
||||
if let cursor = cursor {
|
||||
url = NSURL(string: "https://api.dropboxapi.com/2/files/list_folder/continue")!
|
||||
requestDictionary["cursor"] = cursor
|
||||
} else {
|
||||
url = NSURL(string: "https://api.dropboxapi.com/2/files/list_folder")!
|
||||
requestDictionary["path"] = correctPath(path)
|
||||
requestDictionary["recursive"] = NSNumber(bool: recursive)
|
||||
extension DropboxFileProvider {
|
||||
@available(*, deprecated, message: "Use DropboxFileProvider.temporaryLink(to:, completionHandler: (URL?, DropboxFileObject?, Date?, Error?)) function instead.")
|
||||
open func temporaryLink(to path: String, completionHandler: @escaping ((_ link: URL?, _ attribute: DropboxFileObject?, _ error: Error?) -> Void)) {
|
||||
self.temporaryLink(to: path) { (url, file, _, error) in
|
||||
completionHandler(url, file, error)
|
||||
}
|
||||
let request = NSMutableURLRequest(URL: url)
|
||||
request.HTTPMethod = "POST"
|
||||
}
|
||||
|
||||
open func temporaryLink(to path: String, completionHandler: @escaping ((_ link: URL?, _ attribute: DropboxFileObject?, _ expiration: Date?, _ error: Error?) -> Void)) {
|
||||
let url = URL(string: "files/get_temporary_link", relativeTo: apiURL)!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
request.HTTPBody = dictionaryToJSON(requestDictionary)?.dataUsingEncoding(NSUTF8StringEncoding)
|
||||
let task = session.dataTaskWithRequest(request) { (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)
|
||||
}
|
||||
|
||||
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
|
||||
for entry in entries {
|
||||
if let entry = entry as? [String: AnyObject], let file = self.mapToFileObject(entry) {
|
||||
files.append(file)
|
||||
}
|
||||
let requestDictionary: [String: AnyObject] = ["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)
|
||||
}
|
||||
let ncursor = json?["cursor"] as? String
|
||||
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)
|
||||
if let attribDic = json["metadata"] as? [String: AnyObject] {
|
||||
fileObject = DropboxFileObject(json: attribDic)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
completionHandler(contents: [], cursor: nil, error: responseError ?? error)
|
||||
}
|
||||
|
||||
let expiration: Date? = link != nil ? Date(timeIntervalSinceNow: 4 * 60 * 60) : nil
|
||||
completionHandler(link, fileObject, expiration, dbError ?? error)
|
||||
})
|
||||
task.resume()
|
||||
}
|
||||
|
||||
open func copyItem(remoteURL: URL, to toPath: String, completionHandler: @escaping ((_ jobId: String?, _ attribute: DropboxFileObject?, _ error: Error?) -> Void)) {
|
||||
let url = URL(string: "files/save_url", relativeTo: apiURL)!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
let requestDictionary: [String: AnyObject] = ["path": correctPath(toPath)! as NSString, "url" : remoteURL.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: toPath, 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 = DropboxFileObject(json: attribDic)
|
||||
}
|
||||
}
|
||||
}
|
||||
completionHandler(jobId, fileObject, dbError ?? error)
|
||||
})
|
||||
task.resume()
|
||||
}
|
||||
|
||||
open func copyItem(reference: String, to toPath: String, completionHandler:SimpleCompletionHandler) {
|
||||
let url = URL(string: "files/save_url", relativeTo: apiURL)!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
let requestDictionary: [String: AnyObject] = ["path": correctPath(toPath)! as NSString, "copy_reference" : reference 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 {
|
||||
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
|
||||
dbError = code != nil ? FileProviderDropboxError(code: code!, path: toPath, errorDescription: String(data: data ?? Data(), encoding: .utf8)) : nil
|
||||
}
|
||||
completionHandler?(dbError ?? error)
|
||||
})
|
||||
task.resume()
|
||||
}
|
||||
}
|
||||
|
||||
internal extension DropboxFileProvider {
|
||||
private func mapToFileObject(jsonStr: String) -> DropboxFileObject? {
|
||||
guard let json = self.jsonToDictionary(jsonStr) else { return nil }
|
||||
return self.mapToFileObject(json)
|
||||
}
|
||||
|
||||
private 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 href = NSURL(string: path)!
|
||||
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(absoluteURL: href, name: name, path: path, size: size, serverTime: serverTime, createdDate: nil, modifiedDate: modifiedDate, fileType: isDirectory ? .Directory : .Regular, isHidden: false, isReadOnly: isReadonly, id: id, rev: rev)
|
||||
}
|
||||
|
||||
private func delegateNotify(operation: FileOperation, error: ErrorType?) {
|
||||
dispatch_async(dispatch_get_main_queue(), {
|
||||
if error == nil {
|
||||
self.delegate?.fileproviderSucceed(self, operation: operation)
|
||||
} else {
|
||||
self.delegate?.fileproviderFailed(self, operation: operation)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
extension DropboxFileProvider: FileProvider {}
|
||||
|
||||
// MARK: URLSession delegate
|
||||
extension DropboxFileProvider: 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
|
||||
extension DropboxFileProvider: ExtendedFileProvider {
|
||||
public func thumbnailOfFileSupported(path: String) -> Bool {
|
||||
switch (path as NSString).pathExtension.lowercased() {
|
||||
case "jpg", "jpeg", "gif", "bmp", "png", "tif", "tiff":
|
||||
return true
|
||||
case "doc", "docx", "docm", "xls", "xlsx", "xlsm":
|
||||
return true
|
||||
case "ppt", "pps", "ppsx", "ppsm", "pptx", "pptm":
|
||||
return true
|
||||
case "rtf":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
guard let type = json["type"] as? String, let source = json["source"] as? String else {
|
||||
return
|
||||
}
|
||||
|
||||
public func propertiesOfFileSupported(path: String) -> Bool {
|
||||
let fileExt = (path as NSString).pathExtension.lowercased()
|
||||
switch fileExt {
|
||||
case "jpg", "jpeg", "bmp", "gif", "png", "tif", "tiff":
|
||||
return true
|
||||
/*case "mp3", "aac", "m4a":
|
||||
return true*/
|
||||
case "mp4", "mpg", "3gp", "mov", "avi":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
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 value for dimension is 64x64, according to Dropbox documentation
|
||||
public func thumbnailOfFile(path: String, dimension: CGSize?, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void)) {
|
||||
let url: URL
|
||||
switch (path as NSString).pathExtension.lowercased() {
|
||||
case "jpg", "jpeg", "gif", "bmp", "png", "tif", "tiff":
|
||||
url = URL(string: "files/get_thumbnail", relativeTo: contentURL)!
|
||||
case "doc", "docx", "docm", "xls", "xlsx", "xlsm":
|
||||
fallthrough
|
||||
case "ppt", "pps", "ppsx", "ppsm", "pptx", "pptm":
|
||||
fallthrough
|
||||
case "rtf":
|
||||
url = URL(string: "files/get_preview", relativeTo: contentURL)!
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
let progress = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
|
||||
|
||||
self.delegate?.fileproviderProgress(self, operation: op, progress: progress)
|
||||
var request = URLRequest(url: url)
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
var requestDictionary: [String: AnyObject] = ["path": path as NSString]
|
||||
requestDictionary["format"] = "jpeg" as NSString
|
||||
if let dimension = dimension {
|
||||
requestDictionary["size"] = "w\(Int(dimension.width))h\(Int(dimension.height))" as NSString
|
||||
}
|
||||
request.setValue(dictionaryToJSON(requestDictionary), forHTTPHeaderField: "Dropbox-API-Arg")
|
||||
let task = self.session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var image: ImageClass? = nil
|
||||
if let r = response as? HTTPURLResponse, let result = r.allHeaderFields["Dropbox-API-Result"] as? String, let jsonResult = jsonToDictionary(result) {
|
||||
if jsonResult["error"] != nil {
|
||||
completionHandler(nil, self.throwError(path, code: URLError.cannotDecodeRawData as FoundationErrorEnum))
|
||||
}
|
||||
}
|
||||
if let data = data {
|
||||
if DropboxFileProvider.dataIsPDF(data) {
|
||||
image = DropboxFileProvider.convertToImage(pdfData: data)
|
||||
} else if let contentType = (response as? HTTPURLResponse)?.allHeaderFields["Content-Type"] as? String, contentType.contains("text/html") {
|
||||
// TODO: Implement converting html returned type of get_preview to image
|
||||
} else {
|
||||
image = ImageClass(data: data)
|
||||
}
|
||||
}
|
||||
completionHandler(image, error)
|
||||
})
|
||||
task.resume()
|
||||
}
|
||||
|
||||
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 propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String : Any], _ keys: [String], _ error: Error?) -> Void)) {
|
||||
let url = URL(string: "files/get_metadata", relativeTo: apiURL)!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
let requestDictionary: [String: AnyObject] = ["path": correctPath(path)! as NSString, "include_media_info": NSNumber(value: true)]
|
||||
request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: .utf8)
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var dbError: FileProviderDropboxError?
|
||||
var dic = [String: Any]()
|
||||
var keys = [String]()
|
||||
if let response = response as? HTTPURLResponse {
|
||||
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
|
||||
dbError = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8)) : nil
|
||||
if let data = data, let jsonStr = String(data: data, encoding: .utf8), let json = jsonToDictionary(jsonStr), let properties = json["media_info"] as? [String: Any] {
|
||||
(dic, keys) = self.mapMediaInfo(properties)
|
||||
}
|
||||
}
|
||||
completionHandler(dic, keys, dbError ?? error)
|
||||
})
|
||||
task.resume()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
//
|
||||
// DropboxHelper.swift
|
||||
// FileProvider
|
||||
//
|
||||
// Created by Amir Abbas Mousavian.
|
||||
// Copyright © 2016 Mousavian. Distributed under MIT license.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct FileProviderDropboxError: Error, CustomStringConvertible {
|
||||
public let code: FileProviderHTTPErrorCode
|
||||
public let path: String
|
||||
public let errorDescription: String?
|
||||
|
||||
public var description: String {
|
||||
return code.description
|
||||
}
|
||||
}
|
||||
|
||||
public final class DropboxFileObject: FileObject {
|
||||
internal init(name: String, path: String) {
|
||||
super.init(url: URL(string: path) ?? URL(string: "/")!, name: name, path: path)
|
||||
}
|
||||
|
||||
internal convenience init? (jsonStr: String) {
|
||||
guard let json = jsonToDictionary(jsonStr) else { return nil }
|
||||
self.init(json: json)
|
||||
}
|
||||
|
||||
internal convenience init? (json: [String: AnyObject]) {
|
||||
guard let name = json["name"] as? String else { return nil }
|
||||
guard let path = json["path_display"] as? String else { return nil }
|
||||
self.init(name: name, path: path)
|
||||
self.size = (json["size"] as? NSNumber)?.int64Value ?? -1
|
||||
self.serverTime = resolve(dateString: json["server_modified"] as? String ?? "")
|
||||
self.modifiedDate = resolve(dateString: json["client_modified"] as? String ?? "")
|
||||
self.type = (json[".tag"] as? String) == "folder" ? .directory : .regular
|
||||
self.isReadOnly = (json["sharing_info"]?["read_only"] as? NSNumber)?.boolValue ?? false
|
||||
self.id = json["id"] as? String
|
||||
self.rev = json["rev"] as? String
|
||||
}
|
||||
|
||||
open internal(set) var serverTime: Date? {
|
||||
get {
|
||||
return allValues["NSURLServerDateKey"] as? Date
|
||||
}
|
||||
set {
|
||||
allValues["NSURLServerDateKey"] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open internal(set) var id: String? {
|
||||
get {
|
||||
return allValues["NSURLDocumentIdentifyKey"] as? String
|
||||
}
|
||||
set {
|
||||
allValues["NSURLDocumentIdentifyKey"] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open internal(set) var rev: String? {
|
||||
get {
|
||||
return allValues[URLResourceKey.generationIdentifierKey.rawValue] as? String
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.generationIdentifierKey.rawValue] = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// codebeat:disable[ARITY]
|
||||
internal extension DropboxFileProvider {
|
||||
func list(_ path: String, cursor: String? = nil, prevContents: [DropboxFileObject] = [], recursive: Bool = false, completionHandler: @escaping ((_ contents: [FileObject], _ cursor: String?, _ error: Error?) -> Void)) {
|
||||
var requestDictionary = [String: AnyObject]()
|
||||
let url: URL
|
||||
if let cursor = cursor {
|
||||
url = URL(string: "files/list_folder/continue", relativeTo: apiURL)!
|
||||
requestDictionary["cursor"] = cursor as NSString?
|
||||
} else {
|
||||
url = URL(string: "files/list_folder", relativeTo: apiURL)!
|
||||
requestDictionary["path"] = correctPath(path) as NSString?
|
||||
requestDictionary["recursive"] = recursive as NSNumber?
|
||||
}
|
||||
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)?.data(using: .utf8)
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var responseError: FileProviderDropboxError?
|
||||
var files = prevContents
|
||||
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = FileProviderDropboxError(code: rCode, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8))
|
||||
}
|
||||
if let data = data, let jsonStr = String(data: data, encoding: .utf8) {
|
||||
let json = jsonToDictionary(jsonStr)
|
||||
if let entries = json?["entries"] as? [AnyObject] , entries.count > 0 {
|
||||
for entry in entries {
|
||||
if let entry = entry as? [String: AnyObject], let file = DropboxFileObject(json: entry) {
|
||||
files.append(file)
|
||||
}
|
||||
}
|
||||
let ncursor = json?["cursor"] as? String
|
||||
let hasmore = (json?["has_more"] as? NSNumber)?.boolValue ?? false
|
||||
if hasmore {
|
||||
self.list(path, cursor: ncursor, prevContents: files, completionHandler: completionHandler)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
completionHandler(files, nil, responseError ?? error)
|
||||
})
|
||||
task.taskDescription = FileOperationType.fetch(path: path).json
|
||||
task.resume()
|
||||
}
|
||||
|
||||
func upload_simple(_ targetPath: String, data: Data, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
if data.count > 150 * 1024 * 1024 {
|
||||
let error = FileProviderDropboxError(code: .payloadTooLarge, path: targetPath, errorDescription: nil)
|
||||
completionHandler?(error)
|
||||
self.delegateNotify(.create(path: targetPath), error: error)
|
||||
return nil
|
||||
}
|
||||
var requestDictionary = [String: AnyObject]()
|
||||
let url: URL
|
||||
url = URL(string: "files/upload", relativeTo: contentURL)!
|
||||
requestDictionary["path"] = correctPath(targetPath) as NSString?
|
||||
requestDictionary["mode"] = (overwrite ? "overwrite" : "add") as NSString
|
||||
requestDictionary["client_modified"] = rfc3339utc(of: 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.uploadTask(with: request, from: data, completionHandler: { (data, response, error) in
|
||||
var responseError: FileProviderDropboxError?
|
||||
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = FileProviderDropboxError(code: rCode, path: targetPath, errorDescription: String(data: data ?? Data(), encoding: .utf8))
|
||||
}
|
||||
completionHandler?(responseError ?? error)
|
||||
self.delegateNotify(.create(path: targetPath), error: responseError ?? error)
|
||||
})
|
||||
task.taskDescription = operation.json
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: operation, tasks: [task])
|
||||
}
|
||||
|
||||
func upload_simple(_ targetPath: String, localFile: URL, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let size = (try? localFile.resourceValues(forKeys: [.fileSizeKey]))?.fileSize ?? -1
|
||||
if size > 150 * 1024 * 1024 {
|
||||
let error = FileProviderDropboxError(code: .payloadTooLarge, path: targetPath, errorDescription: nil)
|
||||
completionHandler?(error)
|
||||
self.delegateNotify(.create(path: targetPath), error: error)
|
||||
return nil
|
||||
}
|
||||
var requestDictionary = [String: AnyObject]()
|
||||
let url: URL
|
||||
url = URL(string: "files/upload", relativeTo: contentURL)!
|
||||
requestDictionary["path"] = correctPath(targetPath) as NSString?
|
||||
requestDictionary["mode"] = (overwrite ? "overwrite" : "add") as NSString
|
||||
requestDictionary["client_modified"] = rfc3339utc(of: 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")
|
||||
let task = session.uploadTask(with: request, fromFile: localFile, completionHandler: { (data, response, error) in
|
||||
var responseError: FileProviderDropboxError?
|
||||
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = FileProviderDropboxError(code: rCode, path: targetPath, errorDescription: String(data: data ?? Data(), encoding: .utf8))
|
||||
}
|
||||
completionHandler?(responseError ?? error)
|
||||
self.delegateNotify(.create(path: targetPath), error: responseError ?? error)
|
||||
})
|
||||
task.taskDescription = operation.json
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: operation, tasks: [task])
|
||||
}
|
||||
|
||||
func search(_ startPath: String = "", query: String, start: Int = 0, maxResultPerPage: Int = 25, maxResults: Int = -1, foundItem:@escaping ((_ file: DropboxFileObject) -> Void), completionHandler: @escaping ((_ error: Error?) -> Void)) {
|
||||
let url = URL(string: "files/search", relativeTo: apiURL)!
|
||||
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 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? 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: .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 = DropboxFileObject(json: entry) {
|
||||
foundItem(file)
|
||||
}
|
||||
}
|
||||
let rstart = json?["start"] as? Int
|
||||
let hasmore = (json?["more"] as? NSNumber)?.boolValue ?? false
|
||||
if hasmore, let rstart = rstart {
|
||||
self.search(startPath, query: query, start: rstart + entries.count, maxResultPerPage: maxResultPerPage, foundItem: foundItem, completionHandler: completionHandler)
|
||||
} else {
|
||||
completionHandler(responseError ?? error)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
completionHandler(responseError ?? error)
|
||||
})
|
||||
task.resume()
|
||||
}
|
||||
}
|
||||
// codebeat:enable[ARITY]
|
||||
|
||||
internal extension DropboxFileProvider {
|
||||
static let dateFormatter = DateFormatter()
|
||||
static let decimalFormatter = NumberFormatter()
|
||||
|
||||
func mapMediaInfo(_ json: [String: Any]) -> (dictionary: [String: Any], keys: [String]) {
|
||||
var dic = [String: Any]()
|
||||
var keys = [String]()
|
||||
if let dimensions = json["dimensions"] as? [String: Any], let height = dimensions["height"] as? UInt64, let width = dimensions["width"] as? UInt64 {
|
||||
keys.append("Dimensions")
|
||||
dic["Dimensions"] = "\(width)x\(height)"
|
||||
}
|
||||
if let location = json["location"] as? [String: Any], let latitude = location["latitude"] as? Double, let longitude = location["longitude"] as? Double {
|
||||
|
||||
DropboxFileProvider.decimalFormatter.numberStyle = .decimal
|
||||
DropboxFileProvider.decimalFormatter.maximumFractionDigits = 5
|
||||
keys.append("Location")
|
||||
let latStr = DropboxFileProvider.decimalFormatter.string(from: NSNumber(value: latitude))
|
||||
let longStr = DropboxFileProvider.decimalFormatter.string(from: NSNumber(value: longitude))
|
||||
dic["Location"] = "\(latStr), \(longStr)"
|
||||
}
|
||||
if let timeTakenStr = json["time_taken"] as? String, let timeTaken = resolve(dateString: timeTakenStr) {
|
||||
keys.append("Date taken")
|
||||
DropboxFileProvider.dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
||||
dic["Date taken"] = DropboxFileProvider.dateFormatter.string(from: timeTaken)
|
||||
}
|
||||
if let duration = json["duration"] as? UInt64 {
|
||||
keys.append("Duration")
|
||||
dic["Duration"] = DropboxFileProvider.formatshort(interval: TimeInterval(duration))
|
||||
}
|
||||
return (dic, keys)
|
||||
}
|
||||
|
||||
func delegateNotify(_ operation: FileOperationType, error: Error?) {
|
||||
DispatchQueue.main.async(execute: {
|
||||
if error == nil {
|
||||
self.delegate?.fileproviderSucceed(self, operation: operation)
|
||||
} else {
|
||||
self.delegate?.fileproviderFailed(self, operation: operation)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,443 @@
|
||||
//
|
||||
// ExtendedLocalFileProvider.swift
|
||||
// FileProvider
|
||||
//
|
||||
// Created by Amir Abbas Mousavian.
|
||||
// Copyright © 2017 Mousavian. Distributed under MIT license.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import ImageIO
|
||||
import CoreGraphics
|
||||
import AVFoundation
|
||||
#if os(iOS) || os(tvOS)
|
||||
import UIKit
|
||||
#elseif os(macOS)
|
||||
import Cocoa
|
||||
#endif
|
||||
|
||||
extension LocalFileProvider: ExtendedFileProvider {
|
||||
|
||||
public func thumbnailOfFileSupported(path: String) -> Bool {
|
||||
switch (path as NSString).pathExtension.lowercased() {
|
||||
case LocalFileInformationGenerator.imageThumbnailExtensions:
|
||||
return true
|
||||
case LocalFileInformationGenerator.audioThumbnailExtensions:
|
||||
return true
|
||||
case LocalFileInformationGenerator.videoThumbnailExtensions:
|
||||
return true
|
||||
case LocalFileInformationGenerator.pdfThumbnailExtensions:
|
||||
return true
|
||||
case LocalFileInformationGenerator.officeThumbnailExtensions:
|
||||
return true
|
||||
case LocalFileInformationGenerator.customThumbnailExtensions:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public func propertiesOfFileSupported(path: String) -> Bool {
|
||||
let fileExt = (path as NSString).pathExtension.lowercased()
|
||||
switch fileExt {
|
||||
case LocalFileInformationGenerator.imagePropertiesExtensions:
|
||||
return LocalFileInformationGenerator.imageProperties != nil
|
||||
case LocalFileInformationGenerator.audioPropertiesExtensions:
|
||||
return LocalFileInformationGenerator.audioProperties != nil
|
||||
case LocalFileInformationGenerator.videoPropertiesExtensions:
|
||||
return LocalFileInformationGenerator.videoProperties != nil
|
||||
case LocalFileInformationGenerator.pdfPropertiesExtensions:
|
||||
return LocalFileInformationGenerator.pdfProperties != nil
|
||||
case LocalFileInformationGenerator.archivePropertiesExtensions:
|
||||
return LocalFileInformationGenerator.archiveProperties != nil
|
||||
case LocalFileInformationGenerator.officePropertiesExtensions:
|
||||
return LocalFileInformationGenerator.officeProperties != nil
|
||||
case LocalFileInformationGenerator.customPropertiesExtensions:
|
||||
return LocalFileInformationGenerator.customProperties != nil
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public func thumbnailOfFile(path: String, dimension: CGSize? = nil, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void)) {
|
||||
(dispatch_queue).async {
|
||||
var thumbnailImage: ImageClass? = nil
|
||||
// Check cache
|
||||
let fileURL = self.url(of: path)
|
||||
// Create Thumbnail and cache
|
||||
switch fileURL.pathExtension.lowercased() {
|
||||
case LocalFileInformationGenerator.videoThumbnailExtensions:
|
||||
thumbnailImage = LocalFileInformationGenerator.videoThumbnail(fileURL)
|
||||
case LocalFileInformationGenerator.audioThumbnailExtensions:
|
||||
thumbnailImage = LocalFileInformationGenerator.audioThumbnail(fileURL)
|
||||
case LocalFileInformationGenerator.imageThumbnailExtensions:
|
||||
thumbnailImage = LocalFileInformationGenerator.imageThumbnail(fileURL)
|
||||
case LocalFileInformationGenerator.pdfThumbnailExtensions:
|
||||
thumbnailImage = LocalFileInformationGenerator.pdfThumbnail(fileURL)
|
||||
case LocalFileInformationGenerator.officeThumbnailExtensions:
|
||||
thumbnailImage = LocalFileInformationGenerator.officeThumbnail(fileURL)
|
||||
case LocalFileInformationGenerator.customThumbnailExtensions:
|
||||
thumbnailImage = LocalFileInformationGenerator.customThumbnail(fileURL)
|
||||
default:
|
||||
completionHandler(nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if let image = thumbnailImage {
|
||||
let scaledImage = dimension != nil ? LocalFileProvider.scaleDown(image: image, toSize: dimension!) : image
|
||||
completionHandler(scaledImage, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String: Any], _ keys: [String], _ error: Error?) -> Void)) {
|
||||
(dispatch_queue).async {
|
||||
let fileExt = (path as NSString).pathExtension.lowercased()
|
||||
var getter: ((_ fileURL: URL) -> (prop: [String: Any], keys: [String]))?
|
||||
switch fileExt {
|
||||
case LocalFileInformationGenerator.imagePropertiesExtensions:
|
||||
getter = LocalFileInformationGenerator.imageProperties
|
||||
case LocalFileInformationGenerator.audioPropertiesExtensions:
|
||||
getter = LocalFileInformationGenerator.audioProperties
|
||||
case LocalFileInformationGenerator.videoPropertiesExtensions:
|
||||
getter = LocalFileInformationGenerator.videoProperties
|
||||
case LocalFileInformationGenerator.pdfPropertiesExtensions:
|
||||
getter = LocalFileInformationGenerator.pdfProperties
|
||||
case LocalFileInformationGenerator.archivePropertiesExtensions:
|
||||
getter = LocalFileInformationGenerator.archiveProperties
|
||||
case LocalFileInformationGenerator.officePropertiesExtensions:
|
||||
getter = LocalFileInformationGenerator.officeProperties
|
||||
case LocalFileInformationGenerator.customPropertiesExtensions:
|
||||
getter = LocalFileInformationGenerator.customProperties
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
var dic = [String: Any]()
|
||||
var keys = [String]()
|
||||
if let getterMethod = getter {
|
||||
(dic, keys) = getterMethod(self.url(of: path))
|
||||
}
|
||||
|
||||
completionHandler(dic, keys, nil)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public struct LocalFileInformationGenerator {
|
||||
static public var imageThumbnailExtensions: [String] = ["jpg", "jpeg", "gif", "bmp", "png", "tif", "tiff", "ico"]
|
||||
static public var audioThumbnailExtensions: [String] = ["mp3", "aac", "m4a"]
|
||||
static public var videoThumbnailExtensions: [String] = ["mov", "mp4", "m4v", "mpg", "mpeg"]
|
||||
static public var pdfThumbnailExtensions: [String] = ["pdf"]
|
||||
static public var officeThumbnailExtensions: [String] = []
|
||||
static public var customThumbnailExtensions: [String] = []
|
||||
|
||||
static public var imagePropertiesExtensions: [String] = ["jpg", "jpeg", "bmp", "gif", "png", "tif", "tiff"]
|
||||
static public var audioPropertiesExtensions: [String] = ["mp3", "aac", "m4a", "caf"]
|
||||
static public var videoPropertiesExtensions: [String] = ["mp4", "mpg", "3gp", "mov", "avi"]
|
||||
static public var pdfPropertiesExtensions: [String] = ["pdf"]
|
||||
static public var archivePropertiesExtensions: [String] = []
|
||||
static public var officePropertiesExtensions: [String] = []
|
||||
static public var customPropertiesExtensions: [String] = []
|
||||
|
||||
static public var imageThumbnail: (_ fileURL: URL) -> ImageClass? = { fileURL in
|
||||
return ImageClass(contentsOfFile: fileURL.path)
|
||||
}
|
||||
|
||||
static public var audioThumbnail: (_ fileURL: URL) -> ImageClass? = { fileURL in
|
||||
let playerItem = AVPlayerItem(url: fileURL)
|
||||
let metadataList = playerItem.asset.commonMetadata
|
||||
for item in metadataList {
|
||||
if item.commonKey == AVMetadataCommonKeyArtwork {
|
||||
if let data = item.dataValue {
|
||||
return ImageClass(data: data)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
static public var videoThumbnail: (_ fileURL: URL) -> ImageClass? = { fileURL in
|
||||
let asset = AVAsset(url: fileURL)
|
||||
let assetImgGenerate = AVAssetImageGenerator(asset: asset)
|
||||
assetImgGenerate.appliesPreferredTrackTransform = true
|
||||
let time = CMTimeMake(asset.duration.value / 3, asset.duration.timescale)
|
||||
if let cgImage = try? assetImgGenerate.copyCGImage(at: time, actualTime: nil) {
|
||||
#if os(macOS)
|
||||
return ImageClass(cgImage: cgImage, size: NSSize.zero)
|
||||
#else
|
||||
return ImageClass(cgImage: cgImage)
|
||||
#endif
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
static public var pdfThumbnail: (_ fileURL: URL) -> ImageClass? = { fileURL in
|
||||
guard let data = try? Data(contentsOf: fileURL) else { return nil }
|
||||
return LocalFileProvider.convertToImage(pdfData: data)
|
||||
}
|
||||
|
||||
static public var officeThumbnail: (_ fileURL: URL) -> ImageClass? = { fileURL in
|
||||
return nil
|
||||
}
|
||||
|
||||
static public var customThumbnail: (_ fileURL: URL) -> ImageClass? = { fileURL in
|
||||
return nil
|
||||
}
|
||||
|
||||
static public var imageProperties: ((_ fileURL: URL) -> (prop: [String: Any], keys: [String]))? = { fileURL in
|
||||
func simplify(_ top:Int64, _ bottom:Int64) -> (newTop:Int, newBottom:Int) {
|
||||
var x = top
|
||||
var y = bottom
|
||||
while (y != 0) {
|
||||
let buffer = y
|
||||
y = x % y
|
||||
x = buffer
|
||||
}
|
||||
let hcfVal = x
|
||||
let newTopVal = top/hcfVal
|
||||
let newBottomVal = bottom/hcfVal
|
||||
return(Int(newTopVal), Int(newBottomVal))
|
||||
}
|
||||
|
||||
var dic = [String: Any]()
|
||||
var keys = [String]()
|
||||
guard let cgDataRef = CGImageSourceCreateWithURL(fileURL as CFURL, nil), let cfImageDict = CGImageSourceCopyPropertiesAtIndex(cgDataRef, 0, nil) else {
|
||||
return (dic, keys)
|
||||
}
|
||||
let imageDict = cfImageDict as NSDictionary
|
||||
let tiffDict = imageDict[kCGImagePropertyTIFFDictionary as String] as? [String : AnyObject] ?? [:]
|
||||
let exifDict = imageDict[kCGImagePropertyExifDictionary as String] as? [String : AnyObject] ?? [:]
|
||||
if let pixelWidth: AnyObject = imageDict.object(forKey: kCGImagePropertyPixelWidth) as? NSNumber, let pixelHeight: AnyObject = imageDict.object(forKey: kCGImagePropertyPixelHeight) as? NSNumber {
|
||||
keys.append("Dimensions")
|
||||
dic["Dimensions"] = "\(pixelWidth)x\(pixelHeight)"
|
||||
}
|
||||
if let dpi = imageDict[kCGImagePropertyDPIWidth as String] {
|
||||
keys.append("DPI")
|
||||
dic["DPI"] = dpi
|
||||
}
|
||||
if let devicemake = tiffDict[kCGImagePropertyTIFFMake as String] {
|
||||
keys.append("Device make")
|
||||
dic["Device make"] = devicemake
|
||||
}
|
||||
if let devicemodel = tiffDict[kCGImagePropertyTIFFModel as String] {
|
||||
keys.append("Device model")
|
||||
dic["Device model"] = devicemodel
|
||||
}
|
||||
if let lensmodel = exifDict[kCGImagePropertyExifLensModel as String] {
|
||||
keys.append("Lens model")
|
||||
dic["Lens model"] = lensmodel
|
||||
}
|
||||
if let artist = tiffDict[kCGImagePropertyTIFFArtist as String] as? String , !artist.isEmpty {
|
||||
keys.append("Artist")
|
||||
dic["Artist"] = artist
|
||||
}
|
||||
if let cr = tiffDict[kCGImagePropertyTIFFCopyright as String] as? String , !cr.isEmpty {
|
||||
keys.append("Copyright")
|
||||
dic["Copyright"] = cr
|
||||
}
|
||||
if let date = tiffDict[kCGImagePropertyTIFFDateTime as String] as? String , !date.isEmpty {
|
||||
keys.append("Date taken")
|
||||
dic["Date taken"] = date
|
||||
}
|
||||
if let latitude = tiffDict[kCGImagePropertyGPSLatitude as String]?.doubleValue, let longitude = tiffDict[kCGImagePropertyGPSLongitude as String]?.doubleValue {
|
||||
keys.append("Location")
|
||||
dic["Location"] = "\(latitude), \(longitude)"
|
||||
}
|
||||
if let colorspace = imageDict[kCGImagePropertyColorModel as String] {
|
||||
keys.append("Color space")
|
||||
dic["Color space"] = colorspace
|
||||
}
|
||||
if let focallen = exifDict[kCGImagePropertyExifFocalLength as String] {
|
||||
keys.append("Focal length")
|
||||
dic["Focal length"] = focallen
|
||||
}
|
||||
if let fnum = exifDict[kCGImagePropertyExifFNumber as String] {
|
||||
keys.append("F number")
|
||||
dic["F number"] = fnum
|
||||
}
|
||||
if let expprog = exifDict[kCGImagePropertyExifExposureProgram as String] {
|
||||
keys.append("Exposure program")
|
||||
dic["Exposure program"] = expprog
|
||||
}
|
||||
if let exp = exifDict[kCGImagePropertyExifExposureTime as String]?.doubleValue {
|
||||
let expfrac = simplify(Int64(exp * 10_000_000_000_000), 10_000_000_000_000)
|
||||
keys.append("Exposure time")
|
||||
dic["Exposure time"] = "\(expfrac.newTop)/\(expfrac.newBottom)"
|
||||
}
|
||||
if let iso = exifDict[kCGImagePropertyExifISOSpeedRatings as String] as? NSArray , iso.count > 0 {
|
||||
keys.append("ISO speed")
|
||||
dic["ISO speed"] = iso[0]
|
||||
}
|
||||
return (dic, keys)
|
||||
}
|
||||
|
||||
static var audioProperties: ((_ fileURL: URL) -> (prop: [String: Any], keys: [String]))? = { fileURL in
|
||||
func makeDescription(_ key: String?) -> String? {
|
||||
guard let key = key else {
|
||||
return nil
|
||||
}
|
||||
guard let regex = try? NSRegularExpression(pattern: "([a-z])([A-Z])" , options: NSRegularExpression.Options()) else {
|
||||
return nil
|
||||
}
|
||||
let newKey = regex.stringByReplacingMatches(in: key, options: NSRegularExpression.MatchingOptions(), range: NSMakeRange(0, (key as NSString).length) , withTemplate: "$1 $2")
|
||||
return newKey.capitalized
|
||||
}
|
||||
|
||||
var dic = [String: Any]()
|
||||
var keys = [String]()
|
||||
if FileManager.default.fileExists(atPath: fileURL.path) {
|
||||
let playerItem = AVPlayerItem(url: fileURL)
|
||||
let metadataList = playerItem.asset.commonMetadata
|
||||
for item in metadataList {
|
||||
if let description = makeDescription(item.commonKey) {
|
||||
if let value = item.stringValue {
|
||||
keys.append(description)
|
||||
dic[description] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
if let ap = try? AVAudioPlayer(contentsOf: fileURL) {
|
||||
keys.append("Duration")
|
||||
dic["Duration"] = LocalFileProvider.formatshort(interval: ap.duration)
|
||||
if let bitRate = ap.settings[AVSampleRateKey] as? Int {
|
||||
keys.append("Bitrate")
|
||||
dic["Bitrate"] = bitRate
|
||||
}
|
||||
}
|
||||
}
|
||||
return (dic, keys)
|
||||
}
|
||||
|
||||
static public var videoProperties: ((_ fileURL: URL) -> (prop: [String: Any], keys: [String]))? = { fileURL in
|
||||
var dic = [String: Any]()
|
||||
var keys = [String]()
|
||||
if let audioprops = LocalFileInformationGenerator.audioProperties?(fileURL) {
|
||||
dic = audioprops.prop
|
||||
keys = audioprops.keys
|
||||
dic.removeValue(forKey: "Duration")
|
||||
if let index = keys.index(of: "Duration") {
|
||||
keys.remove(at: index)
|
||||
}
|
||||
}
|
||||
let asset = AVURLAsset(url: fileURL, options: nil)
|
||||
let videoTracks = asset.tracks(withMediaType: AVMediaTypeVideo)
|
||||
if videoTracks.count > 0 {
|
||||
var bitrate: Float = 0
|
||||
let width = Int(videoTracks[0].naturalSize.width)
|
||||
let height = Int(videoTracks[0].naturalSize.height)
|
||||
keys.append("Dimensions")
|
||||
dic["Dimensions"] = "\(width)x\(height)"
|
||||
var duration: Int64 = 0
|
||||
for track in videoTracks {
|
||||
duration += track.timeRange.duration.timescale > 0 ? track.timeRange.duration.value / Int64(track.timeRange.duration.timescale) : 0
|
||||
bitrate += track.estimatedDataRate
|
||||
}
|
||||
keys.append("Duration")
|
||||
dic["Duration"] = LocalFileProvider.formatshort(interval: TimeInterval(duration))
|
||||
keys.append("Video Bitrate")
|
||||
dic["Video Bitrate"] = "\(Int(ceil(bitrate / 1000))) kbps"
|
||||
}
|
||||
let audioTracks = asset.tracks(withMediaType: AVMediaTypeAudio)
|
||||
// dic["Audio channels"] = audioTracks.count
|
||||
var bitrate: Float = 0
|
||||
for track in audioTracks {
|
||||
bitrate += track.estimatedDataRate
|
||||
}
|
||||
keys.append("Audio Bitrate")
|
||||
dic["Audio Bitrate"] = "\(Int(ceil(bitrate / 1000))) kbps"
|
||||
return (dic, keys)
|
||||
}
|
||||
|
||||
static public var pdfProperties: ((_ fileURL: URL) -> (prop: [String: Any], keys: [String]))? = { fileURL in
|
||||
func getKey(_ key: String, from dict: CGPDFDictionaryRef) -> String? {
|
||||
var cfValue: CGPDFStringRef? = nil
|
||||
if (CGPDFDictionaryGetString(dict, key, &cfValue)), let value = CGPDFStringCopyTextString(cfValue!) {
|
||||
return value as String
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertDate(_ date: String) -> Date? {
|
||||
var dateStr = date
|
||||
if dateStr.hasPrefix("D:") {
|
||||
dateStr = date.substring(from: date.characters.index(date.startIndex, offsetBy: 2))
|
||||
}
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "yyyyMMddHHmmssTZD"
|
||||
if let result = dateFormatter.date(from: dateStr) {
|
||||
return result
|
||||
}
|
||||
dateFormatter.dateFormat = "yyyyMMddHHmmss"
|
||||
if let result = dateFormatter.date(from: dateStr) {
|
||||
return result
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var dic = [String: Any]()
|
||||
var keys = [String]()
|
||||
if let data = try? Data(contentsOf: fileURL), let provider = CGDataProvider(data: data as CFData), let reference = CGPDFDocument(provider), let dict = reference.info {
|
||||
if let title = getKey("Title", from: dict), !title.isEmpty {
|
||||
keys.append("Title")
|
||||
dic["Title"] = title
|
||||
}
|
||||
if let author = getKey("Author", from: dict), !author.isEmpty {
|
||||
keys.append("Author")
|
||||
dic["Author"] = author
|
||||
}
|
||||
if let subject = getKey("Subject", from: dict), !subject.isEmpty {
|
||||
keys.append("Subject")
|
||||
dic["Subject"] = subject
|
||||
}
|
||||
var majorVersion: Int32 = 0
|
||||
var minorVersion: Int32 = 0
|
||||
reference.getVersion(majorVersion: &majorVersion, minorVersion: &minorVersion)
|
||||
if majorVersion > 0 {
|
||||
keys.append("Version")
|
||||
dic["Version"] = String(majorVersion) + "." + String(minorVersion)
|
||||
}
|
||||
if reference.numberOfPages > 0 {
|
||||
keys.append("Pages")
|
||||
dic["Pages"] = reference.numberOfPages
|
||||
}
|
||||
|
||||
if reference.numberOfPages > 0, let pageRef = reference.page(at: 1) {
|
||||
let size = pageRef.getBoxRect(CGPDFBox.mediaBox).size
|
||||
keys.append("Resolution")
|
||||
dic["Resolution"] = "\(Int(size.width))x\(Int(size.height))"
|
||||
}
|
||||
if let creator = getKey("Creator", from: dict), !creator.isEmpty {
|
||||
keys.append("Content creator")
|
||||
dic["Content creator"] = creator
|
||||
}
|
||||
if let creationDateString = getKey("CreationDate", from: dict), let creationDate = convertDate(creationDateString) {
|
||||
keys.append("Creation date")
|
||||
dic["Creation date"] = creationDate
|
||||
}
|
||||
if let modifiedDateString = getKey("ModDate", from: dict), let modDate = convertDate(modifiedDateString) {
|
||||
keys.append("Modified date")
|
||||
dic["Modified date"] = modDate
|
||||
}
|
||||
keys.append("Security")
|
||||
dic["Security"] = reference.isEncrypted ? "Present" : "None"
|
||||
keys.append("Allows printing")
|
||||
dic["Allows printing"] = reference.allowsPrinting ? "Yes" : "No"
|
||||
keys.append("Allows copying")
|
||||
dic["Allows copying"] = reference.allowsCopying ? "Yes" : "No"
|
||||
}
|
||||
return (dic, keys)
|
||||
}
|
||||
|
||||
static public var archiveProperties: ((_ fileURL: URL) -> (prop: [String: Any], keys: [String]))? = nil
|
||||
|
||||
static public var officeProperties: ((_ fileURL: URL) -> (prop: [String: Any], keys: [String]))? = nil
|
||||
|
||||
static public var customProperties: ((_ fileURL: URL) -> (prop: [String: Any], keys: [String]))? = nil
|
||||
}
|
||||
|
||||
func ~=<T : Equatable>(array: [T], value: T) -> Bool {
|
||||
return array.contains(value)
|
||||
}
|
||||
@@ -0,0 +1,509 @@
|
||||
//
|
||||
// FPSStreamTask.swift
|
||||
// FileProvider
|
||||
//
|
||||
// Created by Amir Abbas Mousavian.
|
||||
// Copyright © 2016 Mousavian. Distributed under MIT license.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
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
|
||||
open class FPSStreamTask: URLSessionTask, StreamDelegate {
|
||||
fileprivate var inputStream: InputStream?
|
||||
fileprivate var outputStream: OutputStream?
|
||||
|
||||
fileprivate var dispatch_queue: DispatchQueue!
|
||||
internal var _underlyingSession: URLSession
|
||||
fileprivate var streamDelegate: FPSStreamDelegate? {
|
||||
return (_underlyingSession.delegate as? FPSStreamDelegate)
|
||||
}
|
||||
fileprivate var _taskIdentifier: Int
|
||||
|
||||
@available(iOS 9.0, OSX 10.11, *)
|
||||
static var streamTasks = [Int: URLSessionStreamTask]()
|
||||
|
||||
@available(iOS 9.0, OSX 10.11, *)
|
||||
internal var _underlyingTask: URLSessionStreamTask? {
|
||||
return FPSStreamTask.streamTasks[_taskIdentifier]
|
||||
}
|
||||
|
||||
open override var taskIdentifier: Int {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
return _underlyingTask!.taskIdentifier
|
||||
} else {
|
||||
return _taskIdentifier
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var _state: URLSessionTask.State = .suspended
|
||||
override open var state: URLSessionTask.State {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
return _underlyingTask!.state
|
||||
} else {
|
||||
return _state
|
||||
}
|
||||
}
|
||||
|
||||
override open var originalRequest: URLRequest? {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
return _underlyingTask!.originalRequest
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
override open var currentRequest: URLRequest? {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
return _underlyingTask!.currentRequest
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var _countOfBytesSent: Int64 = 0
|
||||
fileprivate var _countOfBytesRecieved: Int64 = 0
|
||||
|
||||
override open var countOfBytesSent: Int64 {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
return _underlyingTask!.countOfBytesSent
|
||||
} else {
|
||||
return _countOfBytesSent
|
||||
}
|
||||
}
|
||||
|
||||
override open var countOfBytesReceived: Int64 {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
return _underlyingTask!.countOfBytesReceived
|
||||
} else {
|
||||
return _countOfBytesRecieved
|
||||
}
|
||||
}
|
||||
|
||||
override open var countOfBytesExpectedToSend: Int64 {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
return _underlyingTask!.countOfBytesExpectedToSend
|
||||
} else {
|
||||
return Int64(dataToBeSent.length)
|
||||
}
|
||||
}
|
||||
|
||||
override open var countOfBytesExpectedToReceive: Int64 {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
return _underlyingTask!.countOfBytesExpectedToReceive
|
||||
} else {
|
||||
return Int64(dataReceived.length)
|
||||
}
|
||||
}
|
||||
|
||||
override public init() {
|
||||
fatalError("Use NSURLSession.fpstreamTask() method")
|
||||
}
|
||||
|
||||
var host: (hostname: String, port: Int)?
|
||||
var service: NetService?
|
||||
|
||||
internal init(session: URLSession, host: String, port: Int) {
|
||||
self._underlyingSession = session
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
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 = DispatchQueue(label: "FSPStreamTask", attributes: DispatchQueue.Attributes.concurrent)
|
||||
}
|
||||
}
|
||||
|
||||
internal init(session: URLSession, netService: NetService) {
|
||||
self._underlyingSession = session
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
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 = DispatchQueue(label: "FSPStreamTask", attributes: DispatchQueue.Attributes.concurrent)
|
||||
}
|
||||
}
|
||||
|
||||
override open func cancel() {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
_underlyingTask!.cancel()
|
||||
} else {
|
||||
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?.remove(from: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)
|
||||
self.outputStream?.remove(from: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)
|
||||
|
||||
self.inputStream?.delegate = nil
|
||||
self.outputStream?.delegate = nil
|
||||
|
||||
self.inputStream = nil
|
||||
self.outputStream = nil
|
||||
|
||||
self._state = .completed
|
||||
self._countOfBytesSent = 0
|
||||
self._countOfBytesRecieved = 0
|
||||
}
|
||||
}
|
||||
|
||||
var _error: Error? = nil
|
||||
override open var error: Error? {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
return _underlyingTask!.error
|
||||
} else {
|
||||
return _error
|
||||
}
|
||||
}
|
||||
|
||||
override open func suspend() {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
_underlyingTask!.suspend()
|
||||
} else {
|
||||
self._state = .suspended
|
||||
}
|
||||
}
|
||||
|
||||
override open func resume() {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
_underlyingTask!.resume()
|
||||
} else {
|
||||
var readStream : Unmanaged<CFReadStream>?
|
||||
var writeStream : Unmanaged<CFWriteStream>?
|
||||
|
||||
if inputStream == nil || outputStream == nil {
|
||||
if let host = host {
|
||||
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 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, let outputStream = outputStream else {
|
||||
return
|
||||
}
|
||||
streamDelegate?.urlSession?(self._underlyingSession, streamTask: self, didBecome: inputStream, outputStream: outputStream)
|
||||
}
|
||||
|
||||
guard let inputStream = inputStream, let outputStream = outputStream else {
|
||||
return
|
||||
}
|
||||
|
||||
inputStream.delegate = self
|
||||
outputStream.delegate = self
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
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!.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_queue.async {
|
||||
if timeout > 0 {
|
||||
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 as NSError?)
|
||||
})
|
||||
}
|
||||
while (self.dataReceived.length == 0 || self.dataReceived.length < minBytes) && !timedOut {
|
||||
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.append(self.dataReceived.subdata(with: range))
|
||||
self.dataReceived.replaceBytes(in: range, withBytes: nil, length: 0)
|
||||
} else {
|
||||
dR.append(self.dataReceived as Data)
|
||||
self.dataReceived.length = 0
|
||||
}
|
||||
completionHandler(dR as Data, inputStream.streamStatus == .atEnd, inputStream.streamError as NSError?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Write the data completely to the underlying socket. If all the
|
||||
* bytes have not been written by the timeout, a timeout error will
|
||||
* 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. */
|
||||
open func writeData(_ data: Data, timeout: TimeInterval, completionHandler: @escaping (Error?) -> Void) {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
_underlyingTask!.write(data, timeout: timeout, completionHandler: completionHandler)
|
||||
} else {
|
||||
guard let outputStream = outputStream else {
|
||||
return
|
||||
}
|
||||
var timedOut: Bool = false
|
||||
dispatch_queue.async {
|
||||
if timeout > 0 {
|
||||
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.append(data)
|
||||
while !outputStream.hasSpaceAvailable && !timedOut {
|
||||
RunLoop.current.run(until: Date(timeIntervalSinceNow: 0.1));
|
||||
Thread.sleep(forTimeInterval: 0.1)
|
||||
}
|
||||
if self.dataToBeSent.length > 0 {
|
||||
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.replaceBytes(in: range, withBytes: nil, length: 0)
|
||||
self._countOfBytesSent += bytesWritten
|
||||
completionHandler(nil)
|
||||
} else {
|
||||
self._error = outputStream.streamError
|
||||
completionHandler(outputStream.streamError)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -captureStreams completes any already enqueued reads
|
||||
* and writes, and then invokes the
|
||||
* URLSession:streamTask:didBecomeInputStream:outputStream: delegate
|
||||
* message. When that message is received, the task object is
|
||||
* considered completed and will not receive any more delegate
|
||||
* messages. */
|
||||
open func captureStreams() {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
_underlyingTask!.captureStreams()
|
||||
} else {
|
||||
guard let outputStream = outputStream, let inputStream = inputStream else {
|
||||
return
|
||||
}
|
||||
dispatch_queue.async {
|
||||
self.write(false)
|
||||
while inputStream.streamStatus != .atEnd {
|
||||
RunLoop.current.run(until: Date(timeIntervalSinceNow: 0.1));
|
||||
Thread.sleep(forTimeInterval: 0.1)
|
||||
}
|
||||
self.streamDelegate?.urlSession?(self._underlyingSession, streamTask: self, didBecome: inputStream, outputStream: outputStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Enqueue a request to close the write end of the underlying socket.
|
||||
* All outstanding IO will complete before the write side of the
|
||||
* socket is closed. The server, however, may continue to write bytes
|
||||
* back to the client, so best practice is to continue reading from
|
||||
* the server until you receive EOF.
|
||||
*/
|
||||
open func closeWrite() {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
_underlyingTask!.closeWrite()
|
||||
} else {
|
||||
dispatch_queue.async(execute: {
|
||||
self.write(true)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func write(_ close: Bool) {
|
||||
guard let outputStream = outputStream else {
|
||||
return
|
||||
}
|
||||
while self.dataToBeSent.length > 0 {
|
||||
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.replaceBytes(in: range, withBytes: nil, length: 0)
|
||||
self._countOfBytesSent += bytesWritten
|
||||
} else {
|
||||
self._error = outputStream.streamError as NSError?
|
||||
}
|
||||
if self.dataToBeSent.length == 0 {
|
||||
break
|
||||
}
|
||||
RunLoop.current.run(until: Date(timeIntervalSinceNow: 0.1));
|
||||
Thread.sleep(forTimeInterval: 0.1)
|
||||
}
|
||||
if close {
|
||||
outputStream.close()
|
||||
self.streamDelegate?.urlSession?(self._underlyingSession, writeClosedFor: self)
|
||||
}
|
||||
}
|
||||
|
||||
/* Enqueue a request to close the read side of the underlying socket.
|
||||
* All outstanding IO will complete before the read side is closed.
|
||||
* You may continue writing to the server.
|
||||
*/
|
||||
open func closeRead() {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
_underlyingTask!.closeRead()
|
||||
} else {
|
||||
guard let inputStream = inputStream else {
|
||||
return
|
||||
}
|
||||
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, readClosedFor: self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Begin encrypted handshake. The hanshake begins after all pending
|
||||
* IO has completed. TLS authentication callbacks are sent to the
|
||||
* session's -URLSession:task:didReceiveChallenge:completionHandler:
|
||||
*/
|
||||
open func startSecureConnection() {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
_underlyingTask!.startSecureConnection()
|
||||
} else {
|
||||
inputStream!.setProperty(StreamSocketSecurityLevel.negotiatedSSL.rawValue, forKey: .socketSecurityLevelKey)
|
||||
outputStream!.setProperty(StreamSocketSecurityLevel.negotiatedSSL.rawValue, forKey: .socketSecurityLevelKey)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Cleanly close a secure connection after all pending secure IO has
|
||||
* completed.
|
||||
*/
|
||||
open func stopSecureConnection() {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
_underlyingTask!.stopSecureConnection()
|
||||
} else {
|
||||
inputStream!.setProperty(StreamSocketSecurityLevel.none.rawValue, forKey: .socketSecurityLevelKey)
|
||||
outputStream!.setProperty(StreamSocketSecurityLevel.none.rawValue, forKey: .socketSecurityLevelKey)
|
||||
}
|
||||
}
|
||||
|
||||
open func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
|
||||
switch (eventCode) {
|
||||
case Stream.Event.errorOccurred:
|
||||
self._error = aStream.streamError as NSError?
|
||||
streamDelegate?.urlSession?(_underlyingSession, task: self, didCompleteWithError: error)
|
||||
case Stream.Event.endEncountered:
|
||||
break
|
||||
case Stream.Event():
|
||||
break
|
||||
case Stream.Event.openCompleted:
|
||||
break
|
||||
case Stream.Event.hasBytesAvailable:
|
||||
var buffer = [UInt8](repeating: 0, count: 2048)
|
||||
if (aStream == inputStream) {
|
||||
while (inputStream!.hasBytesAvailable) {
|
||||
let len = inputStream!.read(&buffer, maxLength: buffer.count)
|
||||
if len > 0 {
|
||||
dataReceived.append(&buffer, length: len)
|
||||
self._countOfBytesRecieved += len
|
||||
}
|
||||
}
|
||||
}
|
||||
case Stream.Event.hasSpaceAvailable:
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension URLSession {
|
||||
/* Creates a bidirectional stream task to a given host and port.
|
||||
*/
|
||||
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: NetService) -> FPSStreamTask {
|
||||
return fpstreamTaskWithNetService(service)
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
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.
|
||||
* This may be sent even when no reads are in progress. However, when
|
||||
* 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. */
|
||||
@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.
|
||||
*/
|
||||
@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
|
||||
* available.) This is a hint to the delegate that it may be
|
||||
* desirable to create a new task for subsequent work. Note that
|
||||
* 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. */
|
||||
@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
|
||||
* connection. This will only be invoked after all enqueued IO has
|
||||
* completed (including any necessary handshakes.) The streamTask
|
||||
* will not receive any further delegate messages.
|
||||
*/
|
||||
@objc optional func urlSession(_ session: URLSession, streamTask: FPSStreamTask, didBecome inputStream: InputStream, outputStream: OutputStream)
|
||||
}
|
||||
|
||||
private let ports: [String: Int] = ["http": 80, "https": 443, "smb": 445,"ftp": 21,"ftps": 22, "sftp": 2121,
|
||||
"telnet": 23, "pop": 110, "smtp": 25, "imap": 143]
|
||||
private let securePorts: [String: Int] = ["https": 443, "smb": 445, "ftps": 22, "sftp": 2121,
|
||||
"telnet": 992, "pop": 995, "smtp": 465, "imap": 993]
|
||||
@@ -0,0 +1,308 @@
|
||||
//
|
||||
// FileProvider.swift
|
||||
// FileProvider
|
||||
//
|
||||
// Created by Amir Abbas Mousavian.
|
||||
// Copyright © 2016 Mousavian. Distributed under MIT license.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Containts path and attributes of a file or resource.
|
||||
open class FileObject {
|
||||
open internal(set) var allValues: [String: Any]
|
||||
|
||||
internal init(allValues: [String: Any]) {
|
||||
self.allValues = allValues
|
||||
}
|
||||
|
||||
internal init(url: URL, name: String, path: String) {
|
||||
self.allValues = [String: Any]()
|
||||
self.url = url
|
||||
self.name = name
|
||||
self.path = path
|
||||
}
|
||||
|
||||
/// url to access the resource, not supported by Dropbox provider
|
||||
@available(*, deprecated, message: "Use FileObject.url.absoluteURL instead.")
|
||||
open var absoluteURL: URL? {
|
||||
return url?.absoluteURL
|
||||
}
|
||||
|
||||
open internal(set) var url: URL? {
|
||||
get {
|
||||
return allValues["NSURLFileURLKey"] as? URL
|
||||
}
|
||||
set {
|
||||
allValues["NSURLFileURLKey"] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Name of the file, usually equals with the last path component
|
||||
open internal(set) var name: String {
|
||||
get {
|
||||
return allValues[URLResourceKey.nameKey.rawValue] as! String
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.nameKey.rawValue] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Relative path of file object
|
||||
open internal(set) var path: String {
|
||||
get {
|
||||
return allValues[URLResourceKey.pathKey.rawValue] as! String
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.pathKey.rawValue] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Size of file on disk, return -1 for directories.
|
||||
open internal(set) var size: Int64 {
|
||||
get {
|
||||
return allValues[URLResourceKey.fileSizeKey.rawValue] as? Int64 ?? -1
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.fileSizeKey.rawValue] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// The time contents of file has been created, returns nil if not set
|
||||
open internal(set) var creationDate: Date? {
|
||||
get {
|
||||
return allValues[URLResourceKey.creationDateKey.rawValue] as? Date
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.creationDateKey.rawValue] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// The time contents of file has been modified, returns nil if not set
|
||||
open internal(set) var modifiedDate: Date? {
|
||||
get {
|
||||
return allValues[URLResourceKey.contentModificationDateKey.rawValue] as? Date
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.contentModificationDateKey.rawValue] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// return resource type of file, usually directory, regular or symLink
|
||||
open internal(set) var type: URLFileResourceType? {
|
||||
get {
|
||||
return allValues[URLResourceKey.fileResourceTypeKey.rawValue] as? URLFileResourceType
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.fileResourceTypeKey.rawValue] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, deprecated, message: "Use FileObject.type property instead.")
|
||||
open var fileType: URLFileResourceType? {
|
||||
return self.type
|
||||
}
|
||||
|
||||
/// File is hidden either because begining with dot or filesystem flags
|
||||
/// Setting this value on a file begining with dot has no effect
|
||||
open internal(set) var isHidden: Bool {
|
||||
get {
|
||||
return allValues[URLResourceKey.isHiddenKey.rawValue] as? Bool ?? false
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.isHiddenKey.rawValue] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// File can not be written
|
||||
open internal(set) var isReadOnly: Bool {
|
||||
get {
|
||||
return !(allValues[URLResourceKey.isWritableKey.rawValue] as? Bool ?? true)
|
||||
}
|
||||
set {
|
||||
allValues[URLResourceKey.isWritableKey.rawValue] = !newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// File is a Directory
|
||||
open var isDirectory: Bool {
|
||||
return self.type == .directory
|
||||
}
|
||||
|
||||
/// File is a normal file
|
||||
open var isRegularFile: Bool {
|
||||
return self.type == .regular
|
||||
}
|
||||
|
||||
/// File is a Symbolic link
|
||||
open var isSymLink: Bool {
|
||||
return self.type == .symbolicLink
|
||||
}
|
||||
}
|
||||
|
||||
internal func resolve(dateString: String) -> Date? {
|
||||
let dateFor: DateFormatter = DateFormatter()
|
||||
dateFor.locale = Locale(identifier: "en_US")
|
||||
dateFor.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssZ"
|
||||
if let rfc3339 = dateFor.date(from: dateString) {
|
||||
return rfc3339
|
||||
}
|
||||
dateFor.dateFormat = "EEE',' dd' 'MMM' 'yyyy HH':'mm':'ss z"
|
||||
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
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
internal func rfc3339utc(of date:Date) -> String {
|
||||
let fm = DateFormatter()
|
||||
fm.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"
|
||||
fm.timeZone = TimeZone(identifier:"UTC")
|
||||
fm.locale = Locale(identifier:"en_US_POSIX")
|
||||
return fm.string(from:date)
|
||||
}
|
||||
|
||||
/// Sorting FileObject array by given criteria, **not thread-safe**
|
||||
public struct FileObjectSorting {
|
||||
|
||||
/// Determines sort kind by which item of File object
|
||||
public enum SortType {
|
||||
/// Sorting by default Finder (case-insensitive) behavior
|
||||
case name
|
||||
/// Sorting by case-sensitive form of file name
|
||||
case nameCaseSensitive
|
||||
/// Sorting by case-in sensitive form of file name
|
||||
case nameCaseInsensitive
|
||||
/// Sorting by file type
|
||||
case `extension`
|
||||
/// Sorting by file modified date
|
||||
case modifiedDate
|
||||
/// Sorting by file creation date
|
||||
case creationDate
|
||||
/// Sorting by file modified date
|
||||
case size
|
||||
|
||||
/// all sort types
|
||||
static var allItems: [SortType] {
|
||||
return [.name, .nameCaseSensitive, .nameCaseInsensitive, .extension,
|
||||
.modifiedDate,.creationDate, .size]
|
||||
}
|
||||
}
|
||||
|
||||
public let sortType: SortType
|
||||
/// puts A before Z, default is true
|
||||
public let ascending: Bool
|
||||
/// puts directories on top, regardless of other attributes, default is false
|
||||
public let isDirectoriesFirst: Bool
|
||||
|
||||
public static let nameAscending = FileObjectSorting(type: .name, ascending: true)
|
||||
public static let nameDesceding = FileObjectSorting(type: .name, ascending: false)
|
||||
public static let sizeAscending = FileObjectSorting(type: .size, ascending: true)
|
||||
public static let sizeDesceding = FileObjectSorting(type: .size, ascending: false)
|
||||
public static let extensionAscending = FileObjectSorting(type: .extension, ascending: true)
|
||||
public static let extensionDesceding = FileObjectSorting(type: .extension, ascending: false)
|
||||
public static let modifiedAscending = FileObjectSorting(type: .modifiedDate, ascending: true)
|
||||
public static let modifiedDesceding = FileObjectSorting(type: .modifiedDate, ascending: false)
|
||||
public static let createdAscending = FileObjectSorting(type: .creationDate, ascending: true)
|
||||
public static let createdDesceding = FileObjectSorting(type: .creationDate, ascending: false)
|
||||
|
||||
public init (type: SortType, ascending: Bool = true, isDirectoriesFirst: Bool = false) {
|
||||
self.sortType = type
|
||||
self.ascending = ascending
|
||||
self.isDirectoriesFirst = isDirectoriesFirst
|
||||
}
|
||||
|
||||
/// Sorts array of FileObjects by criterias set in properties
|
||||
public func sort(_ files: [FileObject]) -> [FileObject] {
|
||||
return files.sorted {
|
||||
if isDirectoriesFirst {
|
||||
if ($0.isDirectory) && !($1.isDirectory) {
|
||||
return true
|
||||
}
|
||||
if !($0.isDirectory) && ($1.isDirectory) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
switch sortType {
|
||||
case .name:
|
||||
return ($0.name).localizedStandardCompare($1.name) == (ascending ? .orderedAscending : .orderedDescending)
|
||||
case .nameCaseSensitive:
|
||||
return ($0.name).localizedCompare($1.name) == (ascending ? .orderedAscending : .orderedDescending)
|
||||
case .nameCaseInsensitive:
|
||||
return ($0.name).localizedCaseInsensitiveCompare($1.name) == (ascending ? .orderedAscending : .orderedDescending)
|
||||
case .extension:
|
||||
let kind1 = $0.isDirectory ? "folder" : ($0.path as NSString).pathExtension
|
||||
let kind2 = $1.isDirectory ? "folder" : ($1.path as NSString).pathExtension
|
||||
return kind1.localizedCaseInsensitiveCompare(kind2) == (ascending ? .orderedAscending : .orderedDescending)
|
||||
case .modifiedDate:
|
||||
let fileMod1 = $0.modifiedDate ?? Date.distantPast
|
||||
let fileMod2 = $1.modifiedDate ?? Date.distantPast
|
||||
return ascending ? fileMod1 < fileMod2 : fileMod1 > fileMod2
|
||||
case .creationDate:
|
||||
let fileCreation1 = $0.creationDate ?? Date.distantPast
|
||||
let fileCreation2 = $1.creationDate ?? Date.distantPast
|
||||
return ascending ? fileCreation1 < fileCreation2 : fileCreation1 > fileCreation2
|
||||
case .size:
|
||||
return ascending ? $0.size < $1.size : $0.size > $1.size
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Array where Element: FileObject {
|
||||
public func sorted(by type: FileObjectSorting.SortType, ascending: Bool = true, isDirectoriesFirst: Bool = false) -> [Element] {
|
||||
let sorting = FileObjectSorting(type: type, ascending: ascending, isDirectoriesFirst: isDirectoriesFirst)
|
||||
return sorting.sort(self) as! [Element]
|
||||
}
|
||||
|
||||
public mutating func sorted(by type: FileObjectSorting.SortType, ascending: Bool = true, isDirectoriesFirst: Bool = false) {
|
||||
self = self.sorted(by: type, ascending: ascending, isDirectoriesFirst: isDirectoriesFirst)
|
||||
}
|
||||
}
|
||||
|
||||
extension URLFileResourceType {
|
||||
public init(fileTypeValue: FileAttributeType) {
|
||||
switch fileTypeValue {
|
||||
case FileAttributeType.typeCharacterSpecial: self = .characterSpecial
|
||||
case FileAttributeType.typeDirectory: self = .directory
|
||||
case FileAttributeType.typeBlockSpecial: self = .blockSpecial
|
||||
case FileAttributeType.typeRegular: self = .regular
|
||||
case FileAttributeType.typeSymbolicLink: self = .symbolicLink
|
||||
case FileAttributeType.typeSocket: self = .socket
|
||||
case FileAttributeType.typeUnknown: self = .unknown
|
||||
default: self = .unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal extension URL {
|
||||
var uw_scheme: String {
|
||||
return self.scheme ?? ""
|
||||
}
|
||||
}
|
||||
|
||||
internal func jsonToDictionary(_ jsonString: String) -> [String: AnyObject]? {
|
||||
guard let data = jsonString.data(using: .utf8) else {
|
||||
return nil
|
||||
}
|
||||
if let dic = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions()) as? [String: AnyObject] {
|
||||
return dic
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
internal func dictionaryToJSON(_ dictionary: [String: AnyObject]) -> String? {
|
||||
if let data = try? JSONSerialization.data(withJSONObject: dictionary, options: JSONSerialization.WritingOptions()) {
|
||||
return String(data: data, encoding: .utf8)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -5,6 +5,9 @@
|
||||
// Created by Amir Abbas Mousavian on 5/6/95.
|
||||
//
|
||||
//
|
||||
|
||||
#import <TargetConditionals.h>
|
||||
|
||||
#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
|
||||
#import <UIKit/UIKit.h>
|
||||
//! Project version number for FileProvider iOS.
|
||||
|
||||
+497
-348
@@ -10,46 +10,510 @@ import Foundation
|
||||
#if os(iOS) || os(tvOS)
|
||||
import UIKit
|
||||
public typealias ImageClass = UIImage
|
||||
#elseif os(OSX)
|
||||
#elseif os(macOS)
|
||||
import Cocoa
|
||||
public typealias ImageClass = NSImage
|
||||
#endif
|
||||
|
||||
public enum FileType: String {
|
||||
case Directory
|
||||
case Regular
|
||||
case SymbolicLink
|
||||
case Socket
|
||||
case CharacterSpecial
|
||||
case BlockSpecial
|
||||
case NamedPipe
|
||||
case Unknown
|
||||
public typealias SimpleCompletionHandler = ((_ error: Error?) -> Void)?
|
||||
|
||||
public protocol FileProviderBasic: class {
|
||||
static var type: String { get }
|
||||
var type: String { get }
|
||||
var isPathRelative: Bool { get }
|
||||
var baseURL: URL? { get }
|
||||
var currentPath: String { get set }
|
||||
var dispatch_queue: DispatchQueue { get set }
|
||||
var operation_queue: OperationQueue { 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))
|
||||
|
||||
func url(of path: String?) -> URL
|
||||
}
|
||||
|
||||
public extension FileProviderBasic {
|
||||
public var maximumOperationTasks: Int {
|
||||
get {
|
||||
return operation_queue.maxConcurrentOperationCount
|
||||
}
|
||||
set {
|
||||
operation_queue.maxConcurrentOperationCount = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
@discardableResult
|
||||
func moveItem(path: String, to: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
@discardableResult
|
||||
func copyItem(path: String, to: String, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
@discardableResult
|
||||
func copyItem(path: String, to: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
@discardableResult
|
||||
func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
@discardableResult
|
||||
func copyItem(localFile: URL, to: String, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
@discardableResult
|
||||
func copyItem(localFile: URL, to: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
@discardableResult
|
||||
func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
}
|
||||
|
||||
extension FileProviderOperations {
|
||||
@discardableResult
|
||||
public func moveItem(path: String, to: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
return self.moveItem(path: path, to: to, overwrite: false, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func copyItem(localFile: URL, to: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
return self.copyItem(localFile: localFile, to: to, overwrite: false, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func copyItem(path: String, to: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
return self.copyItem(path: path, to: to, overwrite: false, completionHandler: completionHandler)
|
||||
}
|
||||
}
|
||||
|
||||
public protocol FileProviderReadWrite: FileProviderBasic {
|
||||
@discardableResult
|
||||
func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle?
|
||||
@discardableResult
|
||||
func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle?
|
||||
|
||||
@discardableResult
|
||||
func writeContents(path: String, contents: Data, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
@discardableResult
|
||||
func writeContents(path: String, contents: Data, atomically: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
@discardableResult
|
||||
func writeContents(path: String, contents: Data, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
@discardableResult
|
||||
func writeContents(path: String, contents: Data, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
|
||||
func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void))
|
||||
}
|
||||
|
||||
extension FileProviderReadWrite {
|
||||
@discardableResult
|
||||
public func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle?{
|
||||
return self.contents(path: path, offset: 0, length: -1, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func writeContents(path: String, contents: Data, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
return self.writeContents(path: path, contents: contents, atomically: false, overwrite: false, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func writeContents(path: String, contents: Data, atomically: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
return self.writeContents(path: path, contents: contents, atomically: atomically, overwrite: false, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func writeContents(path: String, contents: Data, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
return self.writeContents(path: path, contents: contents, atomically: false, overwrite: overwrite, completionHandler: completionHandler)
|
||||
}
|
||||
}
|
||||
|
||||
public protocol FileProviderMonitor: FileProviderBasic {
|
||||
func registerNotifcation(path: String, eventHandler: @escaping (() -> Void))
|
||||
func unregisterNotifcation(path: String)
|
||||
func isRegisteredForNotification(path: String) -> Bool
|
||||
}
|
||||
|
||||
public protocol FileProvider: FileProviderBasic, FileProviderOperations, FileProviderReadWrite, NSCopying {
|
||||
}
|
||||
|
||||
internal let pathTrimSet = CharacterSet(charactersIn: " /")
|
||||
extension FileProviderBasic {
|
||||
public var type: String {
|
||||
return Self.type
|
||||
}
|
||||
|
||||
public var bareCurrentPath: String {
|
||||
return currentPath.trimmingCharacters(in: pathTrimSet)
|
||||
}
|
||||
|
||||
func escaped(path: String) -> String {
|
||||
return path.trimmingCharacters(in: pathTrimSet).addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)!
|
||||
}
|
||||
|
||||
@available(*, deprecated, message: "Use FileProvider.url(of:).absoluteURL instead.")
|
||||
public func absoluteURL(_ path: String? = nil) -> URL {
|
||||
return url(of: path).absoluteURL
|
||||
}
|
||||
|
||||
public func url(of path: String? = nil) -> URL {
|
||||
var rpath: String
|
||||
if let path = path {
|
||||
rpath = path
|
||||
} else {
|
||||
rpath = self.currentPath
|
||||
}
|
||||
rpath = rpath.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? rpath
|
||||
if let baseURL = baseURL {
|
||||
if isPathRelative && rpath.hasPrefix("/") {
|
||||
rpath.remove(at: rpath.startIndex)
|
||||
}
|
||||
return URL(string: rpath, relativeTo: baseURL) ?? baseURL
|
||||
} else {
|
||||
return URL(string: rpath)!
|
||||
}
|
||||
}
|
||||
|
||||
public init(fileTypeValue: String) {
|
||||
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
|
||||
public func relativePathOf(url: URL) -> String {
|
||||
if url.baseURL == self.baseURL {
|
||||
return url.relativePath.removingPercentEncoding!
|
||||
}
|
||||
|
||||
guard let baseURL = self.baseURL else { return url.absoluteString }
|
||||
return url.standardizedFileURL.absoluteString.replacingOccurrences(of: baseURL.absoluteString, with: "/").removingPercentEncoding!
|
||||
}
|
||||
|
||||
internal func correctPath(_ path: String?) -> String? {
|
||||
guard let path = path else { return nil }
|
||||
var p = path.hasPrefix("/") ? path : "/" + path
|
||||
if p.hasSuffix("/") {
|
||||
p.remove(at: p.index(before: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.url?.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.url(of: 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(_ fn: String = #function, file: StaticString = #file) {
|
||||
assert(false, "\(fn) method is not yet implemented. \(file)")
|
||||
}
|
||||
}
|
||||
|
||||
public protocol ExtendedFileProvider: FileProviderBasic {
|
||||
func thumbnailOfFileSupported(path: String) -> Bool
|
||||
func propertiesOfFileSupported(path: String) -> Bool
|
||||
func thumbnailOfFile(path: String, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void))
|
||||
func thumbnailOfFile(path: String, dimension: CGSize?, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void))
|
||||
func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String: Any], _ keys: [String], _ error: Error?) -> Void))
|
||||
}
|
||||
|
||||
extension ExtendedFileProvider {
|
||||
public func thumbnailOfFile(path: String, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void)) {
|
||||
self.thumbnailOfFile(path: path, dimension: nil, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
internal static func formatshort(interval: TimeInterval) -> String {
|
||||
var result = "0:00"
|
||||
if interval < TimeInterval(Int32.max) {
|
||||
result = ""
|
||||
var time = DateComponents()
|
||||
time.hour = Int(interval / 3600)
|
||||
time.minute = Int((interval.truncatingRemainder(dividingBy: 3600)) / 60)
|
||||
time.second = Int(interval.truncatingRemainder(dividingBy: 60))
|
||||
let formatter = NumberFormatter()
|
||||
formatter.paddingCharacter = "0"
|
||||
formatter.minimumIntegerDigits = 2
|
||||
formatter.maximumFractionDigits = 0
|
||||
let formatterFirst = NumberFormatter()
|
||||
formatterFirst.maximumFractionDigits = 0
|
||||
if time.hour! > 0 {
|
||||
result = "\(formatterFirst.string(from: NSNumber(value: time.hour!))!):\(formatter.string(from: NSNumber(value: time.minute!))!):\(formatter.string(from: NSNumber(value: time.second!))!)"
|
||||
} else {
|
||||
result = "\(formatterFirst.string(from: NSNumber(value: time.minute!))!):\(formatter.string(from: NSNumber(value: time.second!))!)"
|
||||
}
|
||||
}
|
||||
result = result.trimmingCharacters(in: CharacterSet(charactersIn: ": "))
|
||||
return result
|
||||
}
|
||||
|
||||
internal static func dataIsPDF(_ data: Data) -> Bool {
|
||||
return data.count > 4 && data.scanString(length: 4, encoding: .ascii) == "%PDF"
|
||||
}
|
||||
|
||||
internal static func convertToImage(pdfData: Data?, page: Int = 1) -> ImageClass? {
|
||||
guard let pdfData = pdfData else { return nil }
|
||||
|
||||
let cfPDFData: CFData = pdfData as CFData
|
||||
if let provider = CGDataProvider(data: cfPDFData), let reference = CGPDFDocument(provider), let pageRef = reference.page(at: page) {
|
||||
let frame = pageRef.getBoxRect(CGPDFBox.mediaBox)
|
||||
var size = frame.size
|
||||
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
|
||||
|
||||
#if os(macOS)
|
||||
let ppp = Int(NSScreen.main()?.backingScaleFactor ?? 1) // fetch device is retina or not
|
||||
|
||||
size.width *= CGFloat(ppp)
|
||||
size.height *= CGFloat(ppp)
|
||||
|
||||
let rep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(size.width), pixelsHigh: Int(size.height),
|
||||
bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: NSCalibratedRGBColorSpace,
|
||||
bytesPerRow: 0, bitsPerPixel: 0)
|
||||
|
||||
guard let context = NSGraphicsContext(bitmapImageRep: rep!) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
NSGraphicsContext.saveGraphicsState()
|
||||
NSGraphicsContext.setCurrent(context)
|
||||
|
||||
let transform = pageRef.getDrawingTransform(CGPDFBox.mediaBox, rect: rect, rotate: 0, preserveAspectRatio: true)
|
||||
context.cgContext.concatenate(transform)
|
||||
|
||||
context.cgContext.translateBy(x: 0, y: size.height)
|
||||
context.cgContext.scaleBy(x: CGFloat(ppp), y: CGFloat(-ppp))
|
||||
context.cgContext.drawPDFPage(pageRef)
|
||||
|
||||
let resultingImage = NSImage(size: size)
|
||||
resultingImage.addRepresentation(rep!)
|
||||
return resultingImage
|
||||
#else
|
||||
let ppp = Int(UIScreen.main.scale) // fetch device is retina or not
|
||||
guard let context = UIGraphicsGetCurrentContext() else {
|
||||
return nil
|
||||
}
|
||||
size.width *= CGFloat(ppp)
|
||||
size.height *= CGFloat(ppp)
|
||||
UIGraphicsBeginImageContext(size)
|
||||
|
||||
context.saveGState()
|
||||
let transform = pageRef.getDrawingTransform(CGPDFBox.mediaBox, rect: rect, rotate: 0, preserveAspectRatio: true)
|
||||
context.concatenate(transform)
|
||||
|
||||
context.translateBy(x: 0, y: size.height)
|
||||
context.scaleBy(x: CGFloat(ppp), y: CGFloat(-ppp))
|
||||
context.drawPDFPage(pageRef)
|
||||
|
||||
context.restoreGState()
|
||||
let resultingImage = UIGraphicsGetImageFromCurrentImageContext()
|
||||
UIGraphicsEndImageContext()
|
||||
return resultingImage
|
||||
#endif
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
internal static func scaleDown(image: ImageClass, toSize maxSize: CGSize) -> ImageClass {
|
||||
let height, width: CGFloat
|
||||
if image.size.width > image.size.height {
|
||||
width = maxSize.width
|
||||
height = (image.size.height / image.size.width) * width
|
||||
} else {
|
||||
height = maxSize.height
|
||||
width = (image.size.width / image.size.height) * height
|
||||
}
|
||||
|
||||
let newSize = CGSize(width: width, height: height)
|
||||
|
||||
#if os(macOS)
|
||||
var imageRect = NSRect(origin: CGPoint.zero, size: image.size)
|
||||
let imageRef = image.cgImage(forProposedRect: &imageRect, context: nil, hints: nil)
|
||||
|
||||
// Create NSImage from the CGImage using the new size
|
||||
return NSImage(cgImage: imageRef!, size: newSize)
|
||||
#else
|
||||
UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0)
|
||||
image.draw(in: CGRect(origin: CGPoint.zero, size: newSize))
|
||||
let newImage = UIGraphicsGetImageFromCurrentImageContext() ?? image
|
||||
UIGraphicsEndImageContext()
|
||||
return newImage
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public enum FileOperationType: CustomStringConvertible {
|
||||
case create (path: String)
|
||||
case copy (source: String, destination: String)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
internal class Weak<T: AnyObject> {
|
||||
weak var value : T?
|
||||
init (_ value: T) {
|
||||
self.value = value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,320 +522,5 @@ public protocol FoundationErrorEnum {
|
||||
var rawValue: Int { get }
|
||||
}
|
||||
|
||||
extension NSURLError: FoundationErrorEnum {}
|
||||
extension NSCocoaError: 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?, name: String, path: String, size: Int64, createdDate: NSDate?, modifiedDate: NSDate?, fileType: FileType, isHidden: Bool, isReadOnly: Bool) {
|
||||
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 init(name: String, path: String, createdDate: NSDate?, modifiedDate: NSDate?, isHidden: Bool, isReadOnly: Bool) {
|
||||
self.absoluteURL = nil
|
||||
self.name = name
|
||||
self.path = path
|
||||
self.size = -1
|
||||
self.createdDate = createdDate
|
||||
self.modifiedDate = modifiedDate
|
||||
self.fileType = .Regular
|
||||
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))
|
||||
}
|
||||
|
||||
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 }
|
||||
return path.hasPrefix("/") ? path : "/" + path
|
||||
}
|
||||
|
||||
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()
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
extension URLError.Code: FoundationErrorEnum {}
|
||||
extension CocoaError.Code: FoundationErrorEnum {}
|
||||
|
||||
+506
-409
@@ -8,486 +8,583 @@
|
||||
|
||||
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: String = "Local"
|
||||
open var isPathRelative: Bool
|
||||
open fileprivate(set) var baseURL: URL?
|
||||
open var currentPath: String
|
||||
open var dispatch_queue: DispatchQueue
|
||||
open var operation_queue: OperationQueue
|
||||
open weak var delegate: FileProviderDelegate?
|
||||
open internal(set) var credential: URLCredential?
|
||||
|
||||
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)
|
||||
fileProviderManagerDelegate = LocalFileProviderManagerDelegate(provider: self)
|
||||
opFileManager.delegate = fileProviderManagerDelegate
|
||||
/// Forces file operations to use NSFileCoordinating, should be set true if:
|
||||
/// - Files are on ubiquity(iCloud) container
|
||||
/// - Multiple processes are accessing same file, recommended always in macOS and when using app extensions in iOS/tvOS (shared container)
|
||||
open var isCoorinating: Bool
|
||||
|
||||
/// default values are `directory: .documentDirectory, domainMask: .userDomainMask`
|
||||
public convenience init (directory: FileManager.SearchPathDirectory = .documentDirectory, domainMask: FileManager.SearchPathDomainMask = .userDomainMask) {
|
||||
self.init(baseURL: FileManager.default.urls(for: directory, in: domainMask).first!)
|
||||
}
|
||||
|
||||
public init (baseURL: NSURL) {
|
||||
public convenience init ? (sharedContainerId: String, directory: FileManager.SearchPathDirectory = .userDirectory) {
|
||||
guard let baseURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: sharedContainerId) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var finalBaseURL = baseURL
|
||||
|
||||
switch directory {
|
||||
case .documentDirectory:
|
||||
finalBaseURL = baseURL.appendingPathComponent("Documents")
|
||||
case .libraryDirectory:
|
||||
finalBaseURL = baseURL.appendingPathComponent("Library")
|
||||
case .cachesDirectory:
|
||||
finalBaseURL = baseURL.appendingPathComponent("Library/Caches")
|
||||
case .applicationSupportDirectory:
|
||||
finalBaseURL = baseURL.appendingPathComponent("Library/Application%20support")
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
self.init(baseURL: finalBaseURL)
|
||||
|
||||
try? fileManager.createDirectory(at: finalBaseURL, withIntermediateDirectories: true)
|
||||
}
|
||||
|
||||
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)
|
||||
self.isPathRelative = true
|
||||
self.currentPath = ""
|
||||
self.credential = nil
|
||||
self.isCoorinating = false
|
||||
|
||||
dispatch_queue = DispatchQueue(label: "FileProvider.\(type(of: self).type)", attributes: DispatchQueue.Attributes.concurrent)
|
||||
operation_queue = OperationQueue()
|
||||
operation_queue.name = "FileProvider.\(type(of: self).type).Operation"
|
||||
|
||||
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])
|
||||
@available(*, deprecated, message: "Overriding this method has no effect anymore.")
|
||||
open class func defaultBaseURL() -> URL {
|
||||
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||
}
|
||||
|
||||
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.url(of: 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)
|
||||
} catch let e as NSError {
|
||||
completionHandler(contents: [], error: e)
|
||||
completionHandler(filesAttributes, nil)
|
||||
} catch let 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 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) + "/")
|
||||
let url = self.url(of: atPath).appendingPathComponent(folderName)
|
||||
|
||||
let operationHandler: (URL) -> Void = { url in
|
||||
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) + "/"))
|
||||
})
|
||||
} 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) + "/"))
|
||||
})
|
||||
try self.opFileManager.createDirectory(at: url, withIntermediateDirectories: true, attributes: [:])
|
||||
completionHandler?(nil)
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.fileproviderSucceed(self, operation: opType)
|
||||
}
|
||||
} catch let e {
|
||||
completionHandler?(e)
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.fileproviderFailed(self, operation: opType)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
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)))
|
||||
})
|
||||
} 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)))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
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))
|
||||
})
|
||||
} catch let e as NSError {
|
||||
completionHandler?(error: e)
|
||||
dispatch_async(dispatch_get_main_queue(), {
|
||||
self.delegate?.fileproviderFailed(self, operation: .Move(source: path, destination: toPath))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
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))
|
||||
})
|
||||
} catch let e as NSError {
|
||||
completionHandler?(error: e)
|
||||
dispatch_async(dispatch_get_main_queue(), {
|
||||
self.delegate?.fileproviderFailed(self, operation: .Copy(source: path, destination: toPath))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func removeItemAtPath(path: String, completionHandler: SimpleCompletionHandler) {
|
||||
dispatch_async(operation_queue) {
|
||||
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))
|
||||
})
|
||||
} catch let e as NSError {
|
||||
completionHandler?(error: e)
|
||||
dispatch_async(dispatch_get_main_queue(), {
|
||||
self.delegate?.fileproviderFailed(self, operation: .Remove(path: path))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func copyLocalFileToPath(localFile: NSURL, toPath: String, completionHandler: SimpleCompletionHandler) {
|
||||
dispatch_async(operation_queue) {
|
||||
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))
|
||||
})
|
||||
} 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))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func copyPathToLocalFile(path: String, toLocalURL: NSURL, completionHandler: SimpleCompletionHandler) {
|
||||
dispatch_async(operation_queue) {
|
||||
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))
|
||||
})
|
||||
} 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))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
if !self.fileManager.fileExistsAtPath(aPath) {
|
||||
self.throwError(path, code: NSURLError.FileDoesNotExist)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
if isCoorinating {
|
||||
let intent = NSFileAccessIntent.writingIntent(with: url, options: .forReplacing)
|
||||
self.coordinated(intents: [intent], completionHandler: operationHandler, errorHandler: { error in
|
||||
completionHandler?(error)
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.fileproviderFailed(self, operation: opType)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
operation_queue.addOperation {
|
||||
operationHandler(url)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
@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))
|
||||
let url = self.url(of: atPath).appendingPathComponent(fileName, isDirectory: false)
|
||||
|
||||
let operationHandler: (URL) -> Void = { url in
|
||||
let success = self.opFileManager.createFile(atPath: url.path, contents: data, attributes: nil)
|
||||
if success {
|
||||
completionHandler?(nil)
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.fileproviderSucceed(self, operation: opType)
|
||||
}
|
||||
} else {
|
||||
completionHandler?(self.throwError(atPath, code: URLError.cannotCreateFile as FoundationErrorEnum))
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.fileproviderFailed(self, operation: opType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isCoorinating {
|
||||
let intent = NSFileAccessIntent.writingIntent(with:url, options: .forReplacing)
|
||||
self.coordinated(intents: [intent], completionHandler: operationHandler, errorHandler: { error in
|
||||
completionHandler?(error)
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.fileproviderFailed(self, operation: opType)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
operation_queue.addOperation {
|
||||
operationHandler(url)
|
||||
}
|
||||
}
|
||||
|
||||
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.move(source: path, destination: toPath)
|
||||
let sourceUrl = self.url(of: path)
|
||||
let destUrl = self.url(of: toPath)
|
||||
|
||||
let sourceIntent = NSFileAccessIntent.writingIntent(with: sourceUrl, options: .forDeleting)
|
||||
let destIntent = NSFileAccessIntent.writingIntent(with: destUrl, options: .forReplacing)
|
||||
|
||||
let operationHandler: (URL, URL) -> Void = { sourceUrl, destUrl in
|
||||
if !overwrite && self.fileManager.fileExists(atPath: destUrl.path) {
|
||||
completionHandler?(self.throwError(toPath, code: URLError.cannotMoveFile as FoundationErrorEnum))
|
||||
return
|
||||
}
|
||||
do {
|
||||
try self.opFileManager.moveItem(at: sourceUrl, to: destUrl)
|
||||
completionHandler?(nil)
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.fileproviderSucceed(self, operation: opType)
|
||||
}
|
||||
} catch let e {
|
||||
completionHandler?(e)
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.fileproviderFailed(self, operation: opType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isCoorinating {
|
||||
coordinated(intents: [sourceIntent, destIntent], completionHandler: operationHandler) { (error) in
|
||||
completionHandler?(error)
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.fileproviderFailed(self, operation: opType)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
operation_queue.addOperation {
|
||||
operationHandler(sourceUrl, destUrl)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.copy(source: path, destination: toPath)
|
||||
let sourceUrl = self.url(of: path)
|
||||
let destUrl = self.url(of: toPath)
|
||||
|
||||
let sourceIntent = NSFileAccessIntent.readingIntent(with: sourceUrl, options: .withoutChanges)
|
||||
let destIntent = NSFileAccessIntent.writingIntent(with: destUrl, options: .forDeleting)
|
||||
|
||||
let operationHandler: (URL, URL) -> Void = { sourceUrl, destUrl in
|
||||
if !overwrite && self.fileManager.fileExists(atPath: destUrl.path) {
|
||||
completionHandler?(self.throwError(toPath, code: URLError.cannotMoveFile as FoundationErrorEnum))
|
||||
return
|
||||
}
|
||||
do {
|
||||
try self.opFileManager.copyItem(at: sourceUrl, to: destUrl)
|
||||
completionHandler?(nil)
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.fileproviderSucceed(self, operation: opType)
|
||||
}
|
||||
} catch let e {
|
||||
completionHandler?(e)
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.fileproviderFailed(self, operation: opType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isCoorinating {
|
||||
coordinated(intents: [sourceIntent, destIntent], moving: true, completionHandler: operationHandler) { (error) in
|
||||
completionHandler?(error)
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.fileproviderFailed(self, operation: opType)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
operation_queue.addOperation {
|
||||
operationHandler(sourceUrl, destUrl)
|
||||
}
|
||||
}
|
||||
|
||||
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.remove(path: path)
|
||||
let url = self.url(of: path)
|
||||
|
||||
let operationHandler: (URL) -> Void = { url in
|
||||
do {
|
||||
let successfulSecurityScopedResourceAccess = url.startAccessingSecurityScopedResource()
|
||||
try self.opFileManager.removeItem(at: url)
|
||||
if successfulSecurityScopedResourceAccess {
|
||||
url.stopAccessingSecurityScopedResource()
|
||||
}
|
||||
completionHandler?(nil)
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.fileproviderSucceed(self, operation: opType)
|
||||
}
|
||||
} catch let e {
|
||||
completionHandler?(e)
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.fileproviderFailed(self, operation: opType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isCoorinating {
|
||||
let intent = NSFileAccessIntent.writingIntent(with:url, options: .forReplacing)
|
||||
self.coordinated(intents: [intent], completionHandler: operationHandler, errorHandler: { error in
|
||||
completionHandler?(error)
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.fileproviderFailed(self, operation: opType)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
operation_queue.addOperation {
|
||||
operationHandler(url)
|
||||
}
|
||||
}
|
||||
|
||||
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
// TODO: Make use of overwrite parameter
|
||||
let opType = FileOperationType.copy(source: localFile.absoluteString, destination: toPath)
|
||||
operation_queue.addOperation {
|
||||
do {
|
||||
try self.opFileManager.copyItem(at: localFile, to: self.url(of: toPath))
|
||||
completionHandler?(nil)
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.fileproviderSucceed(self, operation: opType)
|
||||
}
|
||||
} catch let e {
|
||||
completionHandler?(e)
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.fileproviderFailed(self, operation: opType)
|
||||
}
|
||||
}
|
||||
}
|
||||
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.copy(source: path, destination: toLocalURL.absoluteString)
|
||||
operation_queue.addOperation {
|
||||
do {
|
||||
try self.opFileManager.copyItem(at: self.url(of: path), to: toLocalURL)
|
||||
completionHandler?(nil)
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.fileproviderSucceed(self, operation: opType)
|
||||
}
|
||||
} catch let e {
|
||||
completionHandler?(e)
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.fileproviderFailed(self, operation: opType)
|
||||
}
|
||||
}
|
||||
}
|
||||
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
|
||||
let opType = FileOperationType.fetch(path: path)
|
||||
let url = self.url(of: path)
|
||||
|
||||
let operationHandler: (URL) -> Void = { url in
|
||||
let data = self.fileManager.contents(atPath: url.path)
|
||||
completionHandler(data, nil)
|
||||
}
|
||||
|
||||
if isCoorinating {
|
||||
let intent = NSFileAccessIntent.readingIntent(with: url, options: .withoutChanges)
|
||||
coordinated(intents: [intent], completionHandler: operationHandler, errorHandler: { error in
|
||||
completionHandler(nil, error)
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.fileproviderFailed(self, operation: opType)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
dispatch_queue.async {
|
||||
operationHandler(url)
|
||||
}
|
||||
}
|
||||
|
||||
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
|
||||
if length < 0 {
|
||||
return self.contents(path: path, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
let opType = FileOperationType.fetch(path: path)
|
||||
let url = self.url(of: path)
|
||||
|
||||
let operationHandler: (URL) -> Void = { url in
|
||||
guard self.fileManager.fileExists(atPath: url.path) && !url.fileIsDirectory else {
|
||||
completionHandler(nil, self.throwError(path, code: URLError.cannotOpenFile as FoundationErrorEnum))
|
||||
return
|
||||
}
|
||||
guard let handle = FileHandle(forReadingAtPath: url.path) else {
|
||||
completionHandler(nil, self.throwError(path, code: URLError.cannotOpenFile as FoundationErrorEnum))
|
||||
return
|
||||
}
|
||||
defer {
|
||||
handle.closeFile()
|
||||
}
|
||||
handle.seek(toFileOffset: UInt64(offset))
|
||||
let data = handle.readData(ofLength: length)
|
||||
completionHandler(data, nil)
|
||||
}
|
||||
|
||||
if isCoorinating {
|
||||
let intent = NSFileAccessIntent.readingIntent(with: url, options: .withoutChanges)
|
||||
coordinated(intents: [intent], completionHandler: operationHandler, errorHandler: { error in
|
||||
completionHandler(nil, error)
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.fileproviderFailed(self, operation: opType)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
dispatch_queue.async {
|
||||
operationHandler(url)
|
||||
}
|
||||
}
|
||||
|
||||
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open func writeContents(path: String, contents data: Data, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.modify(path: path)
|
||||
let url = self.url(of: path)
|
||||
var options: Data.WritingOptions = []
|
||||
if atomically {
|
||||
options.insert(.atomic)
|
||||
}
|
||||
if overwrite {
|
||||
options.insert(.withoutOverwriting)
|
||||
}
|
||||
|
||||
let operationHandler: (URL) -> Void = { url in
|
||||
do {
|
||||
try data.write(to: url, options: atomically ? [.atomic] : [])
|
||||
completionHandler?(nil)
|
||||
DispatchQueue.main.async{
|
||||
self.delegate?.fileproviderSucceed(self, operation: opType)
|
||||
}
|
||||
} catch let e {
|
||||
completionHandler?(e)
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.fileproviderFailed(self, operation: opType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isCoorinating {
|
||||
let intent = NSFileAccessIntent.writingIntent(with: url, options: .forReplacing)
|
||||
coordinated(intents: [intent], completionHandler: operationHandler, errorHandler: { error in
|
||||
completionHandler?(error)
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.fileproviderFailed(self, operation: opType)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
operation_queue.addOperation {
|
||||
operationHandler(url)
|
||||
}
|
||||
}
|
||||
|
||||
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
|
||||
}
|
||||
|
||||
open func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
|
||||
dispatch_queue.async {
|
||||
let iterator = self.fileManager.enumerator(at: self.url(of: 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)
|
||||
let absurl = self.absoluteURL(path)
|
||||
var isdirv: AnyObject?
|
||||
do {
|
||||
try absurl.getResourceValue(&isdirv, forKey: NSURLIsDirectoryKey)
|
||||
} catch _ {
|
||||
}
|
||||
if !(isdirv?.boolValue ?? false) {
|
||||
open func registerNotifcation(path: String, eventHandler: @escaping (() -> Void)) {
|
||||
self.unregisterNotifcation(path: path)
|
||||
let dirurl = self.url(of: path)
|
||||
let isdir = (try? dirurl.resourceValues(forKeys: [.isDirectoryKey]).isDirectory ?? false) ?? false
|
||||
if !isdir {
|
||||
return
|
||||
}
|
||||
let monitor = LocalFolderMonitor(url: absurl) {
|
||||
let monitor = LocalFolderMonitor(url: dirurl) {
|
||||
eventHandler()
|
||||
}
|
||||
monitor.start()
|
||||
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.addOperation {
|
||||
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))
|
||||
})
|
||||
} catch let e as NSError {
|
||||
completionHandler?(error: e)
|
||||
dispatch_async(dispatch_get_main_queue(), {
|
||||
self.delegate?.fileproviderFailed(self, operation: .Link(link: path, target: destPath))
|
||||
})
|
||||
try self.opFileManager.createSymbolicLink(at: self.url(of: path), withDestinationURL: self.url(of: destPath))
|
||||
completionHandler?(nil)
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.fileproviderSucceed(self, operation: .link(link: path, target: destPath))
|
||||
}
|
||||
} catch let e {
|
||||
completionHandler?(e)
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.fileproviderFailed(self, operation: .link(link: path, target: destPath))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func destination(ofSymbolicLink path: String, completionHandler: @escaping (_ url: URL?, _ error: Error?) -> Void) {
|
||||
dispatch_queue.async {
|
||||
do {
|
||||
let destPath = try self.opFileManager.destinationOfSymbolicLink(atPath: self.url(of: path).path)
|
||||
let destUrl = URL(fileURLWithPath: destPath)
|
||||
completionHandler(destUrl, nil)
|
||||
} catch let e{
|
||||
completionHandler(nil, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
internal extension LocalFileProvider {
|
||||
func coordinated(intents: [NSFileAccessIntent], completionHandler: @escaping (_ url: URL) -> Void, errorHandler: ((_ error: Error) -> Void)? = nil) {
|
||||
let coordinator = NSFileCoordinator(filePresenter: nil)
|
||||
coordinator.coordinate(with: intents, queue: operation_queue) { (error) in
|
||||
if let error = error {
|
||||
errorHandler?(error)
|
||||
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)
|
||||
completionHandler(intents[0].url)
|
||||
}
|
||||
}
|
||||
|
||||
/// Stops sending notifications if currently enabled
|
||||
func stop() {
|
||||
if state {
|
||||
state = false
|
||||
dispatch_suspend(source)
|
||||
func coordinated(intents: [NSFileAccessIntent], moving: Bool = false, completionHandler: @escaping (_ sourceUrl: URL, _ destURL: URL) -> Void, errorHandler: ((_ error: Error) -> Void)? = nil) {
|
||||
let coordinator = NSFileCoordinator(filePresenter: nil)
|
||||
coordinator.coordinate(with: intents, queue: operation_queue) { (error) in
|
||||
if let error = error {
|
||||
errorHandler?(error)
|
||||
return
|
||||
}
|
||||
if moving {
|
||||
coordinator.item(at: intents[0].url, willMoveTo: intents[1].url)
|
||||
}
|
||||
completionHandler(intents[0].url, intents[1].url)
|
||||
if moving {
|
||||
coordinator.item(at: intents[0].url, didMoveTo: intents[1].url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
dispatch_source_cancel(source)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,306 @@
|
||||
//
|
||||
// LocalFileProvider.swift
|
||||
// FileProvider
|
||||
//
|
||||
// Created by Amir Abbas Mousavian.
|
||||
// Copyright © 2016 Mousavian. Distributed under MIT license.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public final class LocalFileObject: FileObject {
|
||||
internal override init(url: URL, name: String, path: String) {
|
||||
super.init(url: url, name: name, path: path)
|
||||
}
|
||||
|
||||
public convenience init? (fileWithPath path: String, relativeTo relativeURL: URL?) {
|
||||
let fileURL: URL
|
||||
var rpath = path.replacingOccurrences(of: relativeURL?.absoluteString ?? "", with: "")
|
||||
if path.hasPrefix("/") {
|
||||
rpath.remove(at: rpath.startIndex)
|
||||
}
|
||||
if rpath.isEmpty {
|
||||
fileURL = relativeURL ?? URL(fileURLWithPath: path)
|
||||
} else {
|
||||
if #available(iOS 9.0, macOS 10.11, tvOS 9.0, *) {
|
||||
fileURL = URL(fileURLWithPath: rpath, relativeTo: relativeURL)
|
||||
} else {
|
||||
fileURL = relativeURL?.appendingPathComponent(path) ?? URL(fileURLWithPath: path)
|
||||
}
|
||||
}
|
||||
self.init(fileWithURL: fileURL)
|
||||
}
|
||||
|
||||
public convenience init?(fileWithURL fileURL: URL) {
|
||||
do {
|
||||
let values = try fileURL.resourceValues(forKeys: [.nameKey, .fileSizeKey, .fileAllocatedSizeKey, .creationDateKey, .contentModificationDateKey, .fileResourceTypeKey, .isHiddenKey, .isWritableKey, .typeIdentifierKey, .generationIdentifierKey])
|
||||
self.init(url: fileURL, name: values.name ?? fileURL.lastPathComponent, path: fileURL.relativePath)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
open var rev: String? {
|
||||
get {
|
||||
let data = allValues[URLResourceKey.generationIdentifierKey.rawValue] as? Data
|
||||
return data?.map { String(format: "%02hhx", $0) }.joined()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal class LocalFolderMonitor {
|
||||
fileprivate let source: DispatchSourceFileSystemObject
|
||||
fileprivate let descriptor: CInt
|
||||
fileprivate let qq: DispatchQueue = DispatchQueue.global(qos: .default)
|
||||
fileprivate var state: Bool = false
|
||||
fileprivate var monitoredTime: TimeInterval = Date().timeIntervalSinceReferenceDate
|
||||
var url: URL
|
||||
|
||||
/// Creates a folder monitor object with monitoring enabled.
|
||||
init(url: URL, handler: @escaping ()->Void) {
|
||||
self.url = url
|
||||
descriptor = open((url as NSURL).fileSystemRepresentation, O_EVTONLY)
|
||||
source = DispatchSource.makeFileSystemObjectSource(fileDescriptor: descriptor, eventMask: DispatchSource.FileSystemEvent.write, queue: qq)
|
||||
// Folder monitoring is recursive and deep. Monitoring a root folder may be very costly
|
||||
// We have a 0.2 second delay to ensure we wont call handler 1000s times when there is
|
||||
// a huge file operation. This ensures app will work smoothly while this 250 milisec won't
|
||||
// affect user experince much
|
||||
let main_handler: ()->Void = {
|
||||
if Date().timeIntervalSinceReferenceDate < self.monitoredTime + 0.2 {
|
||||
return
|
||||
}
|
||||
self.monitoredTime = Date().timeIntervalSinceReferenceDate
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.25, execute: {
|
||||
handler()
|
||||
})
|
||||
}
|
||||
source.setEventHandler(handler: main_handler)
|
||||
source.setCancelHandler {
|
||||
close(self.descriptor)
|
||||
}
|
||||
start()
|
||||
}
|
||||
|
||||
/// Starts sending notifications if currently stopped
|
||||
func start() {
|
||||
if !state {
|
||||
state = true
|
||||
source.resume()
|
||||
}
|
||||
}
|
||||
|
||||
/// Stops sending notifications if currently enabled
|
||||
func stop() {
|
||||
if state {
|
||||
state = false
|
||||
source.suspend()
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
source.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
internal class LocalFileProviderManagerDelegate: NSObject, FileManagerDelegate {
|
||||
weak var provider: LocalFileProvider?
|
||||
|
||||
init(provider: LocalFileProvider) {
|
||||
self.provider = provider
|
||||
}
|
||||
|
||||
func fileManager(_ fileManager: FileManager, shouldCopyItemAt srcURL: URL, to dstURL: URL) -> Bool {
|
||||
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
|
||||
return true
|
||||
}
|
||||
let srcPath = provider.relativePathOf(url: srcURL)
|
||||
let dstPath = provider.relativePathOf(url: dstURL)
|
||||
return delegate.fileProvider(provider, shouldDoOperation: .copy(source: srcPath, destination: dstPath))
|
||||
}
|
||||
|
||||
func fileManager(_ fileManager: FileManager, shouldMoveItemAt srcURL: URL, to dstURL: URL) -> Bool {
|
||||
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
|
||||
return true
|
||||
}
|
||||
let srcPath = provider.relativePathOf(url: srcURL)
|
||||
let dstPath = provider.relativePathOf(url: dstURL)
|
||||
return delegate.fileProvider(provider, shouldDoOperation: .move(source: srcPath, destination: dstPath))
|
||||
}
|
||||
|
||||
func fileManager(_ fileManager: FileManager, shouldRemoveItemAt URL: URL) -> Bool {
|
||||
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
|
||||
return true
|
||||
}
|
||||
let path = provider.relativePathOf(url: URL)
|
||||
return delegate.fileProvider(provider, shouldDoOperation: .remove(path: path))
|
||||
}
|
||||
|
||||
func fileManager(_ fileManager: FileManager, shouldLinkItemAt srcURL: URL, to dstURL: URL) -> Bool {
|
||||
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
|
||||
return true
|
||||
}
|
||||
let srcPath = provider.relativePathOf(url: srcURL)
|
||||
let dstPath = provider.relativePathOf(url: dstURL)
|
||||
return delegate.fileProvider(provider, shouldDoOperation: .link(link: srcPath, target: dstPath))
|
||||
}
|
||||
|
||||
func fileManager(_ fileManager: FileManager, shouldProceedAfterError error: Error, copyingItemAt srcURL: URL, to dstURL: URL) -> Bool {
|
||||
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
|
||||
return false
|
||||
}
|
||||
let srcPath = provider.relativePathOf(url: srcURL)
|
||||
let dstPath = provider.relativePathOf(url: dstURL)
|
||||
return delegate.fileProvider(provider, shouldProceedAfterError: error, operation: .copy(source: srcPath, destination: dstPath))
|
||||
}
|
||||
|
||||
func fileManager(_ fileManager: FileManager, shouldProceedAfterError error: Error, movingItemAt srcURL: URL, to dstURL: URL) -> Bool {
|
||||
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
|
||||
return false
|
||||
}
|
||||
let srcPath = provider.relativePathOf(url: srcURL)
|
||||
let dstPath = provider.relativePathOf(url: dstURL)
|
||||
return delegate.fileProvider(provider, shouldProceedAfterError: error, operation: .move(source: srcPath, destination: dstPath))
|
||||
}
|
||||
|
||||
func fileManager(_ fileManager: FileManager, shouldProceedAfterError error: Error, removingItemAt URL: URL) -> Bool {
|
||||
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
|
||||
return false
|
||||
}
|
||||
let path = provider.relativePathOf(url: URL)
|
||||
return delegate.fileProvider(provider, shouldProceedAfterError: error, operation: .remove(path: path))
|
||||
}
|
||||
|
||||
func fileManager(_ fileManager: FileManager, shouldProceedAfterError error: Error, linkingItemAt srcURL: URL, to dstURL: URL) -> Bool {
|
||||
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
|
||||
return false
|
||||
}
|
||||
let srcPath = provider.relativePathOf(url: srcURL)
|
||||
let dstPath = provider.relativePathOf(url: dstURL)
|
||||
return delegate.fileProvider(provider, shouldProceedAfterError: error, operation: .link(link: srcPath, target: dstPath))
|
||||
}
|
||||
}
|
||||
|
||||
open class LocalOperationHandle: OperationHandle {
|
||||
public let baseURL: URL
|
||||
public let operationType: FileOperationType
|
||||
|
||||
init (operationType: FileOperationType, baseURL: URL?) {
|
||||
self.baseURL = baseURL ?? URL(fileURLWithPath: "/")
|
||||
self.operationType = operationType
|
||||
}
|
||||
|
||||
private var sourceURL: URL? {
|
||||
guard let source = operationType.source else { return nil }
|
||||
return source.hasPrefix("file://") ? URL(fileURLWithPath: source) : baseURL.appendingPathComponent(source)
|
||||
}
|
||||
|
||||
private var destURL: URL? {
|
||||
guard let dest = operationType.destination else { return nil }
|
||||
return dest.hasPrefix("file://") ? URL(fileURLWithPath: dest) : baseURL.appendingPathComponent(dest)
|
||||
}
|
||||
|
||||
/// Caution: may put pressure on CPU, may have latency
|
||||
open var bytesSoFar: Int64 {
|
||||
assert(!Thread.isMainThread, "Don't run \(#function) method on main thread")
|
||||
switch operationType {
|
||||
case .modify:
|
||||
guard let url = sourceURL, url.isFileURL else { return 0 }
|
||||
if url.fileIsDirectory {
|
||||
return iterateDirectory(url, deep: true).totalsize
|
||||
} else {
|
||||
return url.fileSize
|
||||
}
|
||||
case .copy, .move:
|
||||
guard let url = destURL, url.isFileURL else { return 0 }
|
||||
if url.fileIsDirectory {
|
||||
return iterateDirectory(url, deep: true).totalsize
|
||||
} else {
|
||||
return url.fileSize
|
||||
}
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Caution: may put pressure on CPU, may have latency
|
||||
open var totalBytes: Int64 {
|
||||
assert(!Thread.isMainThread, "Don't run \(#function) method on main thread")
|
||||
switch operationType {
|
||||
case .copy, .move:
|
||||
guard let url = sourceURL, url.isFileURL else { return 0 }
|
||||
if url.fileIsDirectory {
|
||||
return iterateDirectory(url, deep: true).totalsize
|
||||
} else {
|
||||
return url.fileSize
|
||||
}
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Not usable in local provider
|
||||
open var inProgress: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
/// Not usable in local provider
|
||||
open func cancel() -> Bool{
|
||||
return false
|
||||
}
|
||||
|
||||
func iterateDirectory(_ pathURL: URL, deep: Bool) -> (folders: Int, files: Int, totalsize: Int64) {
|
||||
var folders = 0
|
||||
var files = 0
|
||||
var totalsize: Int64 = 0
|
||||
let keys: [URLResourceKey] = [.isDirectoryKey, .fileSizeKey]
|
||||
let enumOpt: FileManager.DirectoryEnumerationOptions = !deep ? [.skipsSubdirectoryDescendants, .skipsPackageDescendants] : []
|
||||
|
||||
let fp = FileManager()
|
||||
let filesList = fp.enumerator(at: pathURL, includingPropertiesForKeys: keys, options: enumOpt, errorHandler: nil)
|
||||
while let fileURL = filesList?.nextObject() as? URL {
|
||||
do {
|
||||
let values = try fileURL.resourceValues(forKeys: [.isDirectoryKey, .fileSizeKey])
|
||||
let isdir = values.isDirectory ?? false
|
||||
let size = Int64(values.fileSize ?? 0)
|
||||
if isdir {
|
||||
folders += 1
|
||||
} else {
|
||||
files += 1
|
||||
}
|
||||
totalsize += size
|
||||
} catch _ {
|
||||
}
|
||||
}
|
||||
|
||||
return (folders, files, totalsize)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
internal extension URL {
|
||||
var fileIsDirectory: Bool {
|
||||
return (try? self.resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory ?? false
|
||||
}
|
||||
|
||||
var fileSize: Int64 {
|
||||
return Int64((try? self.resourceValues(forKeys: [.fileSizeKey]))?.fileSize ?? -1)
|
||||
}
|
||||
|
||||
var fileExists: Bool {
|
||||
return self.isFileURL && FileManager.default.fileExists(atPath: self.path)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
|
||||
//
|
||||
// OneDriveFileProvider.swift
|
||||
// FileProvider
|
||||
//
|
||||
// Created by Amir Abbas Mousavian.
|
||||
// Copyright © 2017 Mousavian. Distributed under MIT license.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreGraphics
|
||||
|
||||
// Because this class uses NSURLSession, it's necessary to disable App Transport Security
|
||||
// in case of using this class with unencrypted HTTP connection.
|
||||
|
||||
open class OneDriveFileProvider: FileProviderBasicRemote {
|
||||
open static let type: String = "OneDrive"
|
||||
open let isPathRelative: Bool
|
||||
open let baseURL: URL?
|
||||
open var serverURL: URL { return baseURL! }
|
||||
open var drive: String
|
||||
open var driveURL: URL {
|
||||
return URL(string: "/drive/\(drive):/", relativeTo: baseURL)!
|
||||
}
|
||||
open var currentPath: String
|
||||
|
||||
open var dispatch_queue: DispatchQueue
|
||||
open var operation_queue: OperationQueue {
|
||||
willSet {
|
||||
assert(_session == nil, "It's not effective to change dispatch_queue property after session is initialized.")
|
||||
}
|
||||
}
|
||||
|
||||
open weak var delegate: FileProviderDelegate?
|
||||
open let credential: URLCredential?
|
||||
open private(set) var cache: URLCache?
|
||||
public var useCache: Bool
|
||||
public var validatingCache: Bool
|
||||
|
||||
fileprivate var _session: URLSession?
|
||||
fileprivate var sessionDelegate: SessionDelegate?
|
||||
public var session: URLSession {
|
||||
if _session == nil {
|
||||
self.sessionDelegate = SessionDelegate(fileProvider: self, credential: credential)
|
||||
let queue = OperationQueue()
|
||||
//queue.underlyingQueue = dispatch_queue
|
||||
let config = URLSessionConfiguration.default
|
||||
config.urlCache = cache
|
||||
config.requestCachePolicy = .returnCacheDataElseLoad
|
||||
_session = URLSession(configuration: config, delegate: sessionDelegate as URLSessionDelegate?, delegateQueue: queue)
|
||||
}
|
||||
return _session!
|
||||
}
|
||||
|
||||
public init? (credential: URLCredential?, serverURL: URL? = nil, drive: String = "root", cache: URLCache? = nil) {
|
||||
self.baseURL = (serverURL ?? URL(string: "https://api.onedrive.com/")!).appendingPathComponent("")
|
||||
self.drive = drive
|
||||
self.isPathRelative = true
|
||||
self.currentPath = ""
|
||||
self.useCache = false
|
||||
self.validatingCache = true
|
||||
self.cache = cache
|
||||
self.credential = credential
|
||||
dispatch_queue = DispatchQueue(label: "FileProvider.\(type(of: self).type)", attributes: DispatchQueue.Attributes.concurrent)
|
||||
operation_queue = OperationQueue()
|
||||
operation_queue.name = "FileProvider.\(type(of: self).type).Operation"
|
||||
}
|
||||
|
||||
deinit {
|
||||
_session?.invalidateAndCancel()
|
||||
}
|
||||
|
||||
open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {
|
||||
list(path) { (contents, cursor, error) in
|
||||
completionHandler(contents, error)
|
||||
}
|
||||
}
|
||||
|
||||
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
|
||||
let url = URL(string: escaped(path: path), relativeTo: driveURL)!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "GET"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var dbError: FileProviderOneDriveError?
|
||||
var fileObject: OneDriveFileObject?
|
||||
if let response = response as? HTTPURLResponse {
|
||||
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
|
||||
dbError = code != nil ? FileProviderOneDriveError(code: code!, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8)) : nil
|
||||
if let data = data, let jsonStr = String(data: data, encoding: .utf8), let json = jsonToDictionary(jsonStr), let file = OneDriveFileObject(baseURL: self.baseURL, drive: self.drive, json: json) {
|
||||
fileObject = file
|
||||
}
|
||||
}
|
||||
completionHandler(fileObject, dbError ?? error)
|
||||
})
|
||||
task.resume()
|
||||
}
|
||||
|
||||
open func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void)) {
|
||||
let url = URL(string: "/drive/root", relativeTo: baseURL)!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "GET"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var totalSize: Int64 = -1
|
||||
var usedSize: Int64 = 0
|
||||
if let data = data, let jsonStr = String(data: data, encoding: .utf8), let json = jsonToDictionary(jsonStr) {
|
||||
totalSize = (json["total"] as? NSNumber)?.int64Value ?? -1
|
||||
usedSize = (json["used"] as? NSNumber)?.int64Value ?? 0
|
||||
}
|
||||
completionHandler(totalSize, usedSize)
|
||||
})
|
||||
task.resume()
|
||||
}
|
||||
|
||||
open weak var fileOperationDelegate: FileOperationDelegate?
|
||||
}
|
||||
|
||||
extension OneDriveFileProvider: FileProviderOperations {
|
||||
|
||||
|
||||
public func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let path = (atPath as NSString).appendingPathComponent(folderName) + "/"
|
||||
return doOperation(.create(path: path), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
public func create(file fileName: String, at path: String, contents data: Data?, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let filePath = (path as NSString).appendingPathComponent(fileName)
|
||||
return self.writeContents(path: filePath, contents: data ?? Data(), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
public func moveItem(path: String, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
return doOperation(.move(source: path, destination: toPath), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
public func copyItem(path: String, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
return doOperation(.copy(source: path, destination: toPath), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
public func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
return doOperation(.remove(path: path), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
fileprivate func doOperation(_ operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: operation) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
guard let sourcePath = operation.source else { return nil }
|
||||
let destPath = operation.destination
|
||||
var request = URLRequest(url: URL(string: sourcePath, relativeTo: driveURL)!)
|
||||
switch operation {
|
||||
case .create:
|
||||
request.httpMethod = "CREATE"
|
||||
case .copy:
|
||||
request.httpMethod = "POST"
|
||||
case .move:
|
||||
request.httpMethod = "PATCH"
|
||||
case .remove:
|
||||
request.httpMethod = "DELETE"
|
||||
default: // modify, link, fetch
|
||||
return nil
|
||||
}
|
||||
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
var requestDictionary = [String: AnyObject]()
|
||||
if let dest = correctPath(destPath) as NSString? {
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
requestDictionary["parentReference"] = ("/drive/\(drive):" + dest.deletingLastPathComponent) as NSString
|
||||
requestDictionary["name"] = dest.lastPathComponent as NSString
|
||||
request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: .utf8)
|
||||
}
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var dbError: FileProviderOneDriveError?
|
||||
if let response = response as? HTTPURLResponse, response.statusCode >= 300, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) {
|
||||
dbError = FileProviderOneDriveError(code: code, path: sourcePath, errorDescription: String(data: data ?? Data(), encoding: .utf8))
|
||||
}
|
||||
completionHandler?(dbError ?? error)
|
||||
self.delegateNotify(operation, error: dbError ?? error)
|
||||
})
|
||||
task.taskDescription = operation.json
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: operation, tasks: [task])
|
||||
}
|
||||
|
||||
public func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.copy(source: localFile.absoluteString, destination: toPath)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
return upload_simple(toPath, localFile: localFile, overwrite: overwrite, operation: opType, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
public func copyItem(path: String, toLocalURL destURL: URL, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.copy(source: path, destination: destURL.absoluteString)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
let url = URL(string: escaped(path: path) + ":/content", relativeTo: driveURL)!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "GET"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
let task = session.downloadTask(with: request, completionHandler: { (cacheURL, response, error) in
|
||||
guard let cacheURL = cacheURL, let httpResponse = response as? HTTPURLResponse , httpResponse.statusCode < 300 else {
|
||||
let code = FileProviderHTTPErrorCode(rawValue: (response as? HTTPURLResponse)?.statusCode ?? -1)
|
||||
let errorData : Data? = nil //Data(contentsOf:cacheURL) // TODO: Figure out how to get error response data for the error description
|
||||
let dbError : FileProviderOneDriveError? = code != nil ? FileProviderOneDriveError(code: code!, path: path, errorDescription: String(data: errorData ?? Data(), encoding: .utf8)) : nil
|
||||
completionHandler?(dbError ?? error)
|
||||
return
|
||||
}
|
||||
do {
|
||||
try FileManager.default.moveItem(at: cacheURL, to: destURL)
|
||||
completionHandler?(nil)
|
||||
} catch let e {
|
||||
completionHandler?(e)
|
||||
}
|
||||
})
|
||||
task.taskDescription = opType.json
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: opType, tasks: [task])
|
||||
}
|
||||
}
|
||||
|
||||
extension OneDriveFileProvider: FileProviderReadWrite {
|
||||
public func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
|
||||
let opType = FileOperationType.fetch(path: path)
|
||||
let url = URL(string: escaped(path: path) + ":/content", relativeTo: driveURL)!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "GET"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
if length > 0 {
|
||||
request.setValue("bytes=\(offset)-\(offset + length - 1)", forHTTPHeaderField: "Range")
|
||||
} else if offset > 0 && length < 0 {
|
||||
request.setValue("bytes=\(offset)-", forHTTPHeaderField: "Range")
|
||||
}
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var dbError: FileProviderOneDriveError?
|
||||
if let httpResponse = response as? HTTPURLResponse , httpResponse.statusCode >= 300, let code = FileProviderHTTPErrorCode(rawValue: httpResponse.statusCode) {
|
||||
dbError = FileProviderOneDriveError(code: code, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8))
|
||||
}
|
||||
let filedata = dbError ?? error == nil ? data : nil
|
||||
completionHandler(filedata, dbError ?? error)
|
||||
})
|
||||
task.taskDescription = opType.json
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: opType, tasks: [task])
|
||||
}
|
||||
|
||||
public func writeContents(path: String, contents data: Data, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let opType = FileOperationType.modify(path: path)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
// FIXME: remove 150MB restriction
|
||||
return upload_simple(path, data: data, overwrite: overwrite, operation: opType, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
public func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
|
||||
var foundFiles = [OneDriveFileObject]()
|
||||
search(path, query: query, foundItem: { (file) in
|
||||
foundFiles.append(file)
|
||||
foundItemHandler?(file)
|
||||
}, completionHandler: { (error) in
|
||||
completionHandler(foundFiles, error)
|
||||
})
|
||||
}
|
||||
|
||||
fileprivate func registerNotifcation(path: String, eventHandler: (() -> Void)) {
|
||||
/* There is two ways to monitor folders changing in OneDrive. Either using webooks
|
||||
* which means you have to implement a server to translate it to push notifications
|
||||
* or using apiv2 list_folder/longpoll method. The second one is implemeted here.
|
||||
* Tough webhooks are much more efficient, longpoll is much simpler to implement!
|
||||
* You can implemnt your own webhook service and replace this method accordingly.
|
||||
*/
|
||||
NotImplemented()
|
||||
}
|
||||
fileprivate func unregisterNotifcation(path: String) {
|
||||
NotImplemented()
|
||||
}
|
||||
|
||||
// TODO: Implement /copy_reference, /get_account & /get_current_account
|
||||
}
|
||||
|
||||
|
||||
extension OneDriveFileProvider: ExtendedFileProvider {
|
||||
public func thumbnailOfFileSupported(path: String) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
public func propertiesOfFileSupported(path: String) -> Bool {
|
||||
let fileExt = (path as NSString).pathExtension.lowercased()
|
||||
switch fileExt {
|
||||
case "jpg", "jpeg", "bmp", "gif", "png", "tif", "tiff":
|
||||
return true
|
||||
case "mp3", "aac", "m4a", "wma":
|
||||
return true
|
||||
case "mp4", "mpg", "3gp", "mov", "avi", "wmv":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public func thumbnailOfFile(path: String, dimension: CGSize?, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void)) {
|
||||
let url: URL
|
||||
if let dimension = dimension {
|
||||
url = URL(string: escaped(path: path) + ":/thumbnails/0/=c\(dimension.width)x\(dimension.height)/content", relativeTo: driveURL)!
|
||||
} else {
|
||||
url = URL(string: escaped(path: path) + ":/thumbnails/0/small/content", relativeTo: driveURL)!
|
||||
}
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
let task = self.session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var image: ImageClass? = nil
|
||||
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
let responseError = FileProviderOneDriveError(code: rCode, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8))
|
||||
completionHandler(nil, responseError)
|
||||
return
|
||||
}
|
||||
if let data = data {
|
||||
image = ImageClass(data: data)
|
||||
}
|
||||
completionHandler(image, error)
|
||||
})
|
||||
task.resume()
|
||||
}
|
||||
|
||||
public func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String : Any], _ keys: [String], _ error: Error?) -> Void)) {
|
||||
let url = URL(string: escaped(path: path), relativeTo: driveURL)!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "GET"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var dbError: FileProviderOneDriveError?
|
||||
var dic = [String: Any]()
|
||||
var keys = [String]()
|
||||
if let response = response as? HTTPURLResponse {
|
||||
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
|
||||
dbError = code != nil ? FileProviderOneDriveError(code: code!, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8)) : nil
|
||||
if let data = data, let jsonStr = String(data: data, encoding: .utf8), let json = jsonToDictionary(jsonStr) {
|
||||
(dic, keys) = self.mapMediaInfo(json)
|
||||
}
|
||||
}
|
||||
completionHandler(dic, keys, dbError ?? error)
|
||||
})
|
||||
task.resume()
|
||||
}
|
||||
}
|
||||
|
||||
extension OneDriveFileProvider: FileProvider {
|
||||
open func copy(with zone: NSZone? = nil) -> Any {
|
||||
let copy = OneDriveFileProvider(credential: self.credential, serverURL: self.baseURL, drive: self.drive, cache: self.cache)!
|
||||
copy.currentPath = self.currentPath
|
||||
copy.delegate = self.delegate
|
||||
copy.fileOperationDelegate = self.fileOperationDelegate
|
||||
copy.useCache = self.useCache
|
||||
copy.validatingCache = self.validatingCache
|
||||
return copy
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
//
|
||||
// OneDriveHelper.swift
|
||||
// FileProvider
|
||||
//
|
||||
// Created by Amir Abbas Mousavian.
|
||||
// Copyright © 2017 Mousavian. Distributed under MIT license.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct FileProviderOneDriveError: Error, CustomStringConvertible {
|
||||
public let code: FileProviderHTTPErrorCode
|
||||
public let path: String
|
||||
public let errorDescription: String?
|
||||
|
||||
public var description: String {
|
||||
return code.description
|
||||
}
|
||||
}
|
||||
|
||||
public final class OneDriveFileObject: FileObject {
|
||||
internal init(baseURL: URL?, name: String, path: String) {
|
||||
var rpath = path
|
||||
if path.hasPrefix("/") {
|
||||
rpath.remove(at: rpath.startIndex)
|
||||
}
|
||||
let url = URL(string: rpath, relativeTo: baseURL) ?? URL(string: path)!
|
||||
super.init(url: url, name: name, path: path)
|
||||
}
|
||||
|
||||
internal convenience init? (baseURL: URL?, drive: String, jsonStr: String) {
|
||||
guard let json = jsonToDictionary(jsonStr) else { return nil }
|
||||
self.init(baseURL: baseURL, drive: drive, json: json)
|
||||
}
|
||||
|
||||
internal convenience init? (baseURL: URL?, drive: String, json: [String: AnyObject]) {
|
||||
guard let name = json["name"] as? String else { return nil }
|
||||
guard let path = (json["parentReference"] as? NSDictionary)?["path"] as? String else { return nil }
|
||||
let lPath = path.replacingOccurrences(of: "/drive/\(drive):", with: "/", options: .anchored, range: nil)
|
||||
self.init(baseURL: baseURL, name: name, path: lPath)
|
||||
self.size = (json["size"] as? NSNumber)?.int64Value ?? -1
|
||||
self.modifiedDate = resolve(dateString: json["lastModifiedDateTime"] as? String ?? "")
|
||||
self.creationDate = resolve(dateString: json["createdDateTime"] as? String ?? "")
|
||||
self.type = (json["folder"] as? String) != nil ? .directory : .regular
|
||||
self.id = json["id"] as? String
|
||||
self.entryTag = json["eTag"] as? String
|
||||
}
|
||||
|
||||
open internal(set) var id: String? {
|
||||
get {
|
||||
return allValues["NSURLDocumentIdentifyKey"] as? String
|
||||
}
|
||||
set {
|
||||
allValues["NSURLDocumentIdentifyKey"] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open internal(set) var contentType: String {
|
||||
get {
|
||||
return allValues["NSURLContentTypeKey"] as? String ?? ""
|
||||
}
|
||||
set {
|
||||
allValues["NSURLContentTypeKey"] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open internal(set) var entryTag: String? {
|
||||
get {
|
||||
return allValues["NSURLEntryTagKey"] as? String
|
||||
}
|
||||
set {
|
||||
allValues["NSURLEntryTagKey"] = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// codebeat:disable[ARITY]
|
||||
internal extension OneDriveFileProvider {
|
||||
func list(_ path: String, cursor: String? = nil, prevContents: [OneDriveFileObject] = [], completionHandler: @escaping ((_ contents: [FileObject], _ cursor: String?, _ error: Error?) -> Void)) {
|
||||
let url: URL
|
||||
if let cursor = cursor {
|
||||
url = URL(string: cursor)!
|
||||
} else {
|
||||
url = URL(string: escaped(path: path), relativeTo: driveURL)!
|
||||
}
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "GET"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var responseError: FileProviderOneDriveError?
|
||||
var files = prevContents
|
||||
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = FileProviderOneDriveError(code: rCode, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8))
|
||||
}
|
||||
if let data = data, let jsonStr = String(data: data, encoding: .utf8) {
|
||||
let json = jsonToDictionary(jsonStr)
|
||||
if let entries = json?["value"] as? [AnyObject] , entries.count > 0 {
|
||||
for entry in entries {
|
||||
if let entry = entry as? [String: AnyObject], let file = OneDriveFileObject(baseURL: self.baseURL, drive: self.drive, json: entry) {
|
||||
files.append(file)
|
||||
}
|
||||
}
|
||||
let ncursor = json?["@odata.nextLink"] as? String
|
||||
let hasmore = ncursor != nil
|
||||
if hasmore {
|
||||
self.list(path, cursor: ncursor, prevContents: files, completionHandler: completionHandler)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
completionHandler(files, nil, responseError ?? error)
|
||||
})
|
||||
task.taskDescription = FileOperationType.fetch(path: path).json
|
||||
task.resume()
|
||||
}
|
||||
|
||||
func upload_simple(_ targetPath: String, data: Data, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
if data.count > 100 * 1024 * 1024 {
|
||||
let error = FileProviderOneDriveError(code: .payloadTooLarge, path: targetPath, errorDescription: nil)
|
||||
completionHandler?(error)
|
||||
self.delegateNotify(.create(path: targetPath), error: error)
|
||||
return nil
|
||||
}
|
||||
let queryStr = overwrite ? "" : "?@name.conflictBehavior=fail"
|
||||
let url = URL(string: escaped(path: targetPath) + ":/content" + queryStr, relativeTo: driveURL)!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "PUT"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
|
||||
request.httpBody = data
|
||||
let task = session.uploadTask(with: request, from: data, completionHandler: { (data, response, error) in
|
||||
var responseError: FileProviderOneDriveError?
|
||||
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = FileProviderOneDriveError(code: rCode, path: targetPath, errorDescription: String(data: data ?? Data(), encoding: .utf8))
|
||||
}
|
||||
completionHandler?(responseError ?? error)
|
||||
self.delegateNotify(.create(path: targetPath), error: responseError ?? error)
|
||||
})
|
||||
task.taskDescription = operation.json
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: operation, tasks: [task])
|
||||
}
|
||||
|
||||
func upload_simple(_ targetPath: String, localFile: URL, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let size = (try? localFile.resourceValues(forKeys: [.fileSizeKey]))?.fileSize ?? -1
|
||||
if size > 100 * 1024 * 1024 {
|
||||
let error = FileProviderOneDriveError(code: .payloadTooLarge, path: targetPath, errorDescription: nil)
|
||||
completionHandler?(error)
|
||||
self.delegateNotify(.create(path: targetPath), error: error)
|
||||
return nil
|
||||
}
|
||||
let queryStr = overwrite ? "" : "?@name.conflictBehavior=fail"
|
||||
let url = URL(string: escaped(path: targetPath) + ":/content" + queryStr, relativeTo: driveURL)!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "PUT"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
|
||||
let task = session.uploadTask(with: request, fromFile: localFile, completionHandler: { (data, response, error) in
|
||||
var responseError: FileProviderOneDriveError?
|
||||
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = FileProviderOneDriveError(code: rCode, path: targetPath, errorDescription: String(data: data ?? Data(), encoding: .utf8))
|
||||
}
|
||||
completionHandler?(responseError ?? error)
|
||||
self.delegateNotify(.create(path: targetPath), error: responseError ?? error)
|
||||
})
|
||||
task.taskDescription = operation.json
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: operation, tasks: [task])
|
||||
}
|
||||
|
||||
func search(_ startPath: String = "", query: String, next: String? = nil, foundItem:@escaping ((_ file: OneDriveFileObject) -> Void), completionHandler: @escaping ((_ error: Error?) -> Void)) {
|
||||
let url: URL
|
||||
if let next = next {
|
||||
url = URL(string: next)!
|
||||
} else if self.escaped(path: startPath) == "" {
|
||||
url = URL(string: "/drive/\(drive)/view.search?q=\(query)", relativeTo: baseURL)!
|
||||
} else {
|
||||
url = URL(string: "\(escaped(path: startPath))/view.search?q=\(query)", relativeTo: driveURL)!
|
||||
}
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "GET"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var responseError: FileProviderOneDriveError?
|
||||
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = FileProviderOneDriveError(code: rCode, path: startPath, errorDescription: String(data: data ?? Data(), encoding: .utf8))
|
||||
}
|
||||
if let data = data, let jsonStr = String(data: data, encoding: .utf8) {
|
||||
let json = jsonToDictionary(jsonStr)
|
||||
if let entries = json?["value"] as? [AnyObject] , entries.count > 0 {
|
||||
for entry in entries {
|
||||
if let entry = entry as? [String: AnyObject], let file = OneDriveFileObject(baseURL: self.baseURL, drive: self.drive, json: entry) {
|
||||
foundItem(file)
|
||||
}
|
||||
}
|
||||
let next = json?["@odata.nextLink"] as? String
|
||||
let hasmore = next != nil
|
||||
if hasmore, let next = next {
|
||||
self.search(startPath, query: query, next: next, foundItem: foundItem, completionHandler: completionHandler)
|
||||
} else {
|
||||
completionHandler(responseError ?? error)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
completionHandler(responseError ?? error)
|
||||
})
|
||||
task.resume()
|
||||
}
|
||||
}
|
||||
// codebeat:enable[ARITY]
|
||||
|
||||
internal extension OneDriveFileProvider {
|
||||
static let dateFormatter = DateFormatter()
|
||||
static let decimalFormatter = NumberFormatter()
|
||||
|
||||
func mapMediaInfo(_ json: [String: Any]) -> (dictionary: [String: Any], keys: [String]) {
|
||||
|
||||
func spaceCamelCase(_ text: String) -> String {
|
||||
var newString: String = ""
|
||||
|
||||
let upperCase = CharacterSet.uppercaseLetters
|
||||
for scalar in text.unicodeScalars {
|
||||
if upperCase.contains(scalar) {
|
||||
newString.append(" ")
|
||||
}
|
||||
let character = Character(scalar)
|
||||
newString.append(character)
|
||||
}
|
||||
|
||||
return newString.capitalized
|
||||
}
|
||||
|
||||
var dic = [String: Any]()
|
||||
var keys = [String]()
|
||||
|
||||
if let parent = json["image"] as? [String: Any] ?? json["video"] as? [String: Any], let height = parent["height"] as? UInt64, let width = parent["width"] as? UInt64 {
|
||||
keys.append("Dimensions")
|
||||
dic["Dimensions"] = "\(width)x\(height)"
|
||||
}
|
||||
if let location = json["location"] as? [String: Any], let latitude = location["latitude"] as? Double, let longitude = location["longitude"] as? Double {
|
||||
|
||||
OneDriveFileProvider.decimalFormatter.numberStyle = .decimal
|
||||
OneDriveFileProvider.decimalFormatter.maximumFractionDigits = 5
|
||||
keys.append("Location")
|
||||
let latStr = OneDriveFileProvider.decimalFormatter.string(from: NSNumber(value: latitude))
|
||||
let longStr = OneDriveFileProvider.decimalFormatter.string(from: NSNumber(value: longitude))
|
||||
dic["Location"] = "\(latStr), \(longStr)"
|
||||
}
|
||||
if let parent = json["image"] as? [String: Any] ?? json["video"] as? [String: Any], let duration = parent["duration"] as? UInt64 {
|
||||
keys.append("Duration")
|
||||
dic["Duration"] = OneDriveFileProvider.formatshort(interval: TimeInterval(duration) / 1000)
|
||||
}
|
||||
if let timeTakenStr = json["takenDateTime"] as? String, let timeTaken = resolve(dateString: timeTakenStr) {
|
||||
keys.append("Date taken")
|
||||
OneDriveFileProvider.dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
||||
dic["Date taken"] = OneDriveFileProvider.dateFormatter.string(from: timeTaken)
|
||||
}
|
||||
|
||||
if let photo = json["photo"] as? [String: Any] {
|
||||
if let devicemake = photo["cameraMake"] as? String {
|
||||
keys.append("Device make")
|
||||
dic["Device make"] = devicemake
|
||||
}
|
||||
if let devicemodel = photo["cameraModel"] as? String {
|
||||
keys.append("Device model")
|
||||
dic["Device model"] = devicemodel
|
||||
}
|
||||
if let focallen = photo["focalLength"] as? Double {
|
||||
keys.append("Focal length")
|
||||
dic["Focal length"] = focallen
|
||||
}
|
||||
if let fnum = photo["fNumber"] as? Double {
|
||||
keys.append("F number")
|
||||
dic["F number"] = fnum
|
||||
}
|
||||
if let expNom = photo["exposureNumerator"] as? Double, let expDen = photo["exposureDenominator"] as? Double {
|
||||
keys.append("Exposure time")
|
||||
dic["Exposure time"] = "\(Int(expNom))/\(Int(expDen))"
|
||||
}
|
||||
if let iso = photo["iso"] as? Int64 {
|
||||
keys.append("ISO speed")
|
||||
dic["ISO speed"] = iso
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if let audio = json["audio"] as? [String: Any] {
|
||||
for (key, value) in audio {
|
||||
if key == "bitrate" || key == "isVariableBitrate" { continue }
|
||||
let casedKey = spaceCamelCase(key)
|
||||
keys.append(casedKey)
|
||||
dic[casedKey] = value
|
||||
}
|
||||
}
|
||||
|
||||
if let video = json["video"] as? [String: Any] {
|
||||
if let bitRate = video["bitrate"] as? Int {
|
||||
keys.append("Bitrate")
|
||||
dic["Bitrate"] = bitRate
|
||||
}
|
||||
}
|
||||
|
||||
return (dic, keys)
|
||||
}
|
||||
|
||||
func delegateNotify(_ operation: FileOperationType, error: Error?) {
|
||||
DispatchQueue.main.async(execute: {
|
||||
if error == nil {
|
||||
self.delegate?.fileproviderSucceed(self, operation: operation)
|
||||
} else {
|
||||
self.delegate?.fileproviderFailed(self, operation: operation)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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: [Int: String] = [100: "Continue", 101: "Switching Protocols", 102: "Processing"]
|
||||
fileprivate static let status2xx: [Int: String] = [200: "OK", 201: "Created", 202: "Accepted", 203: "Non-Authoritative Information", 204: "No Content", 205: "Reset Content", 206: "Partial Content", 207: "Multi-Status", 208: "Already Reported", 226: "IM Used"]
|
||||
fileprivate static let status3xx: [Int: String] = [300: "Multiple Choices", 301: "Moved Permanently", 302: "Found", 303: "See Other", 304: "Not Modified", 305: "Use Proxy", 306: "Switch Proxy", 307: "Temporary Redirect", 308: "Permanent Redirect"]
|
||||
fileprivate static let status4xx: [Int: String] = [400: "Bad Request", 401: "Unauthorized/Expired Session", 402: "Payment Required", 403: "Forbidden", 404: "Not Found", 405: "Method Not Allowed", 406: "Not Acceptable", 407: "Proxy Authentication Required", 408: "Request Timeout", 409: "Conflict", 410: "Gone", 411: "Length Required", 412: "Precondition Failed", 413: "Payload Too Large", 414: "URI Too Long", 415: "Unsupported Media Type", 416: "Range Not Satisfiable", 417: "Expectation Failed", 421: "Misdirected Request", 422: "Unprocessable Entity", 423: "Locked", 424: "Failed Dependency", 425: "Unordered Collection", 426: "Upgrade Required", 428: "Precondition Required", 429: "Too Many Requests", 431: "Request Header Fields Too Large", 451: "Unavailable For Legal Reasons"]
|
||||
fileprivate static let status5xx: [Int: String] = [500: "Internal Server Error", 501: "Not Implemented", 502: "Bad Gateway", 503: "Service Unavailable", 504: "Gateway Timeout", 505: "HTTP Version Not Supported", 506: "Variant Also Negotiates", 507: "Insufficient Storage", 508: "Loop Detected", 509: "Bandwidth Limit Exceeded", 510: "Not Extended", 511: "Network Authentication Required"]
|
||||
|
||||
public var description: String {
|
||||
switch self.rawValue {
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
+147
-105
@@ -8,116 +8,164 @@
|
||||
|
||||
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
|
||||
|
||||
class SMBProtocolClient: TCPSocketClient {
|
||||
var currentMessageID: UInt64 = 0
|
||||
protocol SMBProtocolClientDelegate: class {
|
||||
func receivedResponse(client: SMB2ProtocolClient, response: SMBResponse, for: SMBRequest)
|
||||
}
|
||||
|
||||
class SMB2ProtocolClient: FPSStreamTask {
|
||||
var timeout: TimeInterval = 30
|
||||
|
||||
func negotiateToSMB2() -> SMB2.NegotiateResponse? {
|
||||
let smbHeader = SMB2.Header(command: .NEGOTIATE, creditRequestResponse: 126, messageId: messageId(), treeId: 0, sessionId: 0)
|
||||
currentMessageID += 1
|
||||
let negMessage = SMB2.NegotiateRequest(request: SMB2.NegotiateRequest.Header(capabilities: []))
|
||||
SMBProtocolClient.createSMB2Message(smbHeader, message: negMessage)
|
||||
do {
|
||||
try self.send(data: nil)
|
||||
} catch _ {
|
||||
return nil
|
||||
}
|
||||
self.waitUntilResponse()
|
||||
let response = try? SMBProtocolClient.digestSMB2Message(dataReceived)
|
||||
return response??.message as? SMB2.NegotiateResponse
|
||||
}
|
||||
|
||||
func sessionSetupForSMB2() -> SMB2.SessionSetupResponse? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func messageId() -> UInt64 {
|
||||
private(set) var lastMessageID: UInt64 = 0
|
||||
private(set) var sessionId: UInt64 = 0
|
||||
private func messageId() -> UInt64 {
|
||||
defer {
|
||||
currentMessageID += 1
|
||||
lastMessageID += 1
|
||||
}
|
||||
return currentMessageID
|
||||
return lastMessageID
|
||||
}
|
||||
|
||||
// MARK: create and analyse messages
|
||||
private(set) var establishedTrees = Array<SMB2.TreeConnectResponse>()
|
||||
private(set) var requestStack = [Int: SMBRequest]()
|
||||
private(set) var responseStack = [Int: SMBResponse]()
|
||||
|
||||
class func determineSMBVersion(data: NSData) -> Float {
|
||||
var smbverChar: Int8 = 0
|
||||
data.getBytes(&smbverChar, length: 1)
|
||||
weak var delegate: SMBProtocolClientDelegate?
|
||||
|
||||
func sendNegotiate(completionHandler: SimpleCompletionHandler) -> UInt64 {
|
||||
let mId = messageId()
|
||||
let smbHeader = SMB2.Header(command: .NEGOTIATE, creditRequestResponse: UInt16(126), messageId: mId, treeId: UInt32(0), sessionId: UInt64(0))
|
||||
let msg = SMB2.NegotiateRequest()
|
||||
let data = createSMB2Message(header: smbHeader, message: msg)
|
||||
self.writeData(data, timeout: 0, completionHandler: { (e) in
|
||||
completionHandler?(e)
|
||||
})
|
||||
return mId
|
||||
}
|
||||
|
||||
func sendSessionSetup(completionHandler: SimpleCompletionHandler) -> UInt64 {
|
||||
let mId = messageId()
|
||||
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(header: smbHeader, message: msg)
|
||||
self.writeData(data, timeout: 0, completionHandler: { (e) in
|
||||
if self.sessionId == 0 {
|
||||
self.readData(OfMinLength: 64, maxLength: 65536, timeout: self.timeout, completionHandler: { (data, eof, e2) in
|
||||
// TODO: set session id
|
||||
completionHandler?(e2 ?? e)
|
||||
})
|
||||
}
|
||||
})
|
||||
return mId
|
||||
}
|
||||
|
||||
func sendTreeConnect(completionHandler: SimpleCompletionHandler) -> UInt64 {
|
||||
let req = self.currentRequest ?? self.originalRequest
|
||||
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 = ""
|
||||
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(header: smbHeader, message: msg!)
|
||||
self.writeData(data, timeout: 0, completionHandler: { (e) in
|
||||
completionHandler?(e)
|
||||
|
||||
})
|
||||
return mId
|
||||
}
|
||||
|
||||
func sendTreeDisconnect(id treeId: UInt32, completionHandler: SimpleCompletionHandler) -> UInt64 {
|
||||
let mId = messageId()
|
||||
let smbHeader = SMB2.Header(command: .TREE_DISCONNECT, creditRequestResponse: 111, messageId: mId, treeId: treeId, sessionId: sessionId)
|
||||
let msg = SMB2.TreeDisconnect()
|
||||
let data = createSMB2Message(header: smbHeader, message: msg)
|
||||
self.writeData(data, timeout: 0, completionHandler: { (e) in
|
||||
completionHandler?(e)
|
||||
})
|
||||
return mId
|
||||
}
|
||||
|
||||
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(header: smbHeader, message: msg)
|
||||
self.writeData(data, timeout: 0, completionHandler: { (e) in
|
||||
completionHandler?(e)
|
||||
})
|
||||
return mId
|
||||
}
|
||||
|
||||
func reset() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
class 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)
|
||||
}
|
||||
|
||||
class 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))
|
||||
@@ -136,59 +184,53 @@ class SMBProtocolClient: TCPSocketClient {
|
||||
case .FLUSH:
|
||||
return (header, SMB2.FlushResponse(data: messageData))
|
||||
case .READ:
|
||||
return (header, nil) // FIXME:
|
||||
return (header, SMB2.ReadRespone(data: messageData))
|
||||
case .WRITE:
|
||||
return (header, nil) // FIXME:
|
||||
return (header, SMB2.WriteResponse(data: messageData))
|
||||
case .LOCK:
|
||||
return (header, nil) // FIXME:
|
||||
return (header, SMB2.LockResponse(data: messageData))
|
||||
case .IOCTL:
|
||||
return (header, nil)
|
||||
return (header, SMB2.IOCtlResponse(data: messageData))
|
||||
case .CANCEL:
|
||||
return (header, nil)
|
||||
case .ECHO:
|
||||
return (header, SMB2.Echo(data: messageData))
|
||||
case .QUERY_DIRECTORY:
|
||||
return (header, nil) // FIXME:
|
||||
return (header, SMB2.QueryDirectoryResponse(data: messageData))
|
||||
case .CHANGE_NOTIFY:
|
||||
return (header, nil) // FIXME:
|
||||
return (header, SMB2.ChangeNotifyResponse(data: messageData))
|
||||
case .QUERY_INFO:
|
||||
return (header, nil) // FIXME:
|
||||
return (header, SMB2.QueryInfoResponse(data: messageData))
|
||||
case .SET_INFO:
|
||||
return (header, nil) // FIXME:
|
||||
return (header, SMB2.SetInfoResponse(data: messageData))
|
||||
case .OPLOCK_BREAK:
|
||||
return (header, nil) // FIXME:
|
||||
case .INVALID:
|
||||
throw SMBFileProviderError.InvalidCommand
|
||||
throw SMBFileProviderError.invalidCommand
|
||||
}
|
||||
}
|
||||
|
||||
class 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(¶mWordsCount, length: sizeofValue(paramWordsCount))
|
||||
var paramWordsCount = UInt8(block.params?.count ?? 0)
|
||||
result.append(¶mWordsCount, 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
|
||||
}
|
||||
|
||||
class 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 }
|
||||
}
|
||||
@@ -8,104 +8,127 @@
|
||||
|
||||
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 var operation_queue: OperationQueue
|
||||
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)
|
||||
//let url = baseURL.uw_absoluteString
|
||||
self.baseURL = baseURL.appendingPathComponent("")
|
||||
dispatch_queue = DispatchQueue(label: "FileProvider.\(type(of: self).type)", attributes: DispatchQueue.Attributes.concurrent)
|
||||
operation_queue = OperationQueue()
|
||||
operation_queue.name = "FileProvider.\(type(of: self).type).Operation"
|
||||
|
||||
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 weak var fileOperationDelegate: FileOperationDelegate?
|
||||
|
||||
public func createFolder(folderName: String, atPath: String, completionHandler: SimpleCompletionHandler) {
|
||||
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObjectClass?, _ error: Error?) -> Void)) {
|
||||
NotImplemented()
|
||||
}
|
||||
|
||||
public func createFile(fileAttribs: FileObject, atPath: String, contents data: NSData?, completionHandler: SimpleCompletionHandler) {
|
||||
open func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void)) {
|
||||
NotImplemented()
|
||||
}
|
||||
|
||||
public func moveItemAtPath(path: String, toPath: String, overwrite: Bool = false, 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, overwrite: Bool, 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, overwrite: 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 copyItemAtPath(path: String, toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
|
||||
open func registerNotifcation(path: String, eventHandler: @escaping (() -> Void)) {
|
||||
NotImplemented()
|
||||
}
|
||||
|
||||
public func removeItemAtPath(path: String, completionHandler: SimpleCompletionHandler) {
|
||||
open func unregisterNotifcation(path: String) {
|
||||
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"
|
||||
@@ -113,8 +136,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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,34 +8,53 @@
|
||||
|
||||
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 IOCtlRequestProtocol: SMBRequest {}
|
||||
protocol IOCtlResponseProtocol: SMBResponse {}
|
||||
protocol SMBResponseBody {
|
||||
init? (data: Data)
|
||||
}
|
||||
|
||||
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 {
|
||||
var time: UInt64
|
||||
var time: Int64
|
||||
|
||||
init(time: UInt64) {
|
||||
init(time: Int64) {
|
||||
self.time = time
|
||||
}
|
||||
|
||||
init(unixTime: UInt) {
|
||||
self.time = (UInt64(unixTime) + 11644473600) * 10000000
|
||||
self.time = (Int64(unixTime) + 11644473600) * 10000000
|
||||
}
|
||||
|
||||
init(timeIntervalSince1970: NSTimeInterval) {
|
||||
self.time = UInt64((timeIntervalSince1970 + 11644473600) * 10000000)
|
||||
init(timeIntervalSince1970: TimeInterval) {
|
||||
self.time = Int64((timeIntervalSince1970 + 11644473600) * 10000000)
|
||||
}
|
||||
|
||||
init(date: NSDate) {
|
||||
init(date: Date) {
|
||||
self.init(timeIntervalSince1970: date.timeIntervalSince1970)
|
||||
}
|
||||
|
||||
@@ -43,7 +62,36 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(NSUTF8StringEncoding) {
|
||||
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(NSUTF8StringEncoding)!)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,10 +222,5 @@ extension SMB2 {
|
||||
self.size = 4
|
||||
self.reserved = 0
|
||||
}
|
||||
|
||||
func data() -> NSData {
|
||||
return encode(self)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
//
|
||||
// SMB2Notification.swift
|
||||
// FileProvider
|
||||
//
|
||||
// Created by Amir Abbas Mousavian on 5/18/95.
|
||||
//
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension SMB2 {
|
||||
// MARK: SMB2 Change Notify
|
||||
|
||||
struct ChangeNotifyRequest: SMBRequestBody {
|
||||
let size: UInt16
|
||||
let flags: ChangeNotifyRequest.Flags
|
||||
let outputBufferLength: UInt32
|
||||
let fileId: FileId
|
||||
let completionFilters: CompletionFilter
|
||||
fileprivate let reserved: UInt32
|
||||
|
||||
init(fileId: FileId, completionFilters: CompletionFilter, flags: ChangeNotifyRequest.Flags = [], outputBufferLength: UInt32 = 65535) {
|
||||
self.size = 32
|
||||
self.flags = flags
|
||||
self.outputBufferLength = outputBufferLength
|
||||
self.fileId = fileId
|
||||
self.completionFilters = completionFilters
|
||||
self.reserved = 0
|
||||
}
|
||||
|
||||
struct Flags: OptionSet {
|
||||
let rawValue: UInt16
|
||||
|
||||
init(rawValue: UInt16) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
static let WATCH_TREE = Flags(rawValue: 0x0001)
|
||||
}
|
||||
|
||||
struct CompletionFilter: OptionSet {
|
||||
let rawValue: UInt32
|
||||
|
||||
init(rawValue: UInt32) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
/// The client is notified if a file-name changes.
|
||||
static let FILE_NAME = CompletionFilter(rawValue: 0x00000001)
|
||||
/// The client is notified if a directory name changes.
|
||||
static let DIR_NAME = CompletionFilter(rawValue: 0x00000002)
|
||||
/// The client is notified if a file's attributes change.
|
||||
static let ATTRIBUTES = CompletionFilter(rawValue: 0x00000004)
|
||||
/// The client is notified if a file's size changes.
|
||||
static let SIZE = CompletionFilter(rawValue: 0x00000008)
|
||||
/// The client is notified if the last write time of a file changes.
|
||||
static let LAST_WRITE = CompletionFilter(rawValue: 0x00000010)
|
||||
/// The client is notified if the last access time of a file changes.
|
||||
static let LAST_ACCESS = CompletionFilter(rawValue: 0x00000020)
|
||||
/// The client is notified if the creation time of a file changes.
|
||||
static let CREATION = CompletionFilter(rawValue: 0x00000040)
|
||||
/// The client is notified if a file's extended attributes (EAs) change.
|
||||
static let EA = CompletionFilter(rawValue: 0x00000080)
|
||||
/// The client is notified of a file's access control list (ACL) settings change.
|
||||
static let SECURITY = CompletionFilter(rawValue: 0x00000100)
|
||||
/// The client is notified if a named stream is added to a file.
|
||||
static let STREAM_NAME = CompletionFilter(rawValue: 0x00000200)
|
||||
/// The client is notified if the size of a named stream is changed.
|
||||
static let STREAM_SIZE = CompletionFilter(rawValue: 0x00000400)
|
||||
/// The client is notified if a named stream is modified.
|
||||
static let STREAM_WRITE = Flags(rawValue: 0x00000800)
|
||||
|
||||
static let all = CompletionFilter(rawValue: 0x00000FFF)
|
||||
static let list: CompletionFilter = [.FILE_NAME, .DIR_NAME]
|
||||
}
|
||||
}
|
||||
|
||||
struct ChangeNotifyResponse: SMBResponseBody {
|
||||
let notifications: [(action: FileNotifyAction, fileName: String)]
|
||||
|
||||
init?(data: Data) {
|
||||
let maxLoop = 1000
|
||||
var i = 0
|
||||
var result = [(action: FileNotifyAction, fileName: String)]()
|
||||
|
||||
var offset = 0
|
||||
while i < maxLoop {
|
||||
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 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))
|
||||
|
||||
offset += Int(nextOffset)
|
||||
if nextOffset == 0 {
|
||||
break
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
|
||||
self.notifications = result
|
||||
}
|
||||
}
|
||||
|
||||
enum FileNotifyAction: UInt32 {
|
||||
/// The file was added to the directory.
|
||||
case ADDED = 0x00000001
|
||||
/// The file was removed from the directory.
|
||||
case REMOVED = 0x00000002
|
||||
/// The file was modified. This can be a change to the data or attributes of the file.
|
||||
case MODIFIED = 0x00000003
|
||||
/// The file was renamed, and this is the old name. If the new name resides outside of the directory being monitored, the client will not receive the FILE_ACTION_RENAMED_NEW_NAME bit value.
|
||||
case RENAMED_OLD_NAME = 0x00000004
|
||||
/// The file was renamed, and this is the new name. If the old name resides outside of the directory being monitored, the client will not receive the FILE_ACTION_RENAME_OLD_NAME bit value.
|
||||
case RENAMED_NEW_NAME = 0x00000005
|
||||
/// The file was added to a named stream.
|
||||
case ADDED_STREAM = 0x00000006
|
||||
/// The file was removed from the named stream.
|
||||
case REMOVED_STREAM = 0x00000007
|
||||
/// The file was modified. This can be a change to the data or attributes of the file.
|
||||
case MODIFIED_STREAM = 0x00000008
|
||||
/// An object ID was removed because the file the object ID referred to was deleted. This notification is only sent when the directory being monitored is the special directory "\$Extend\$ObjId:$O:$INDEX_ALLOCATION".
|
||||
case REMOVED_BY_DELETE = 0x00000009
|
||||
/// An attempt to tunnel object ID information to a file being created or renamed failed because the object ID is in use by another file on the same volume. This notification is only sent when the directory being monitored is the special directory "\$Extend\$ObjId:$O:$INDEX_ALLOCATION".
|
||||
case NOT_TUNNELLED = 0x0000000A
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
@@ -11,10 +11,315 @@ import Foundation
|
||||
extension SMB2 {
|
||||
// MARK: SMB2 Query Directory
|
||||
|
||||
struct QueryDirectoryRequest: SMBRequestBody {
|
||||
let header: QueryDirectoryRequest.Header
|
||||
let searchPattern: String?
|
||||
|
||||
/// - **bufferLength:** maximum number of bytes the server is allowed to return which is the same as maxTransactSize returned by negotiation.
|
||||
/// - **searchPattern:** can hold wildcards or be nil if all entries should be returned.
|
||||
/// - **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 ? 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() -> Data {
|
||||
var result = Data(value: header)
|
||||
if let patternData = searchPattern?.data(using: .utf16) {
|
||||
result.append(patternData)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
struct Header {
|
||||
let size: UInt8
|
||||
let infoClass: FileInformationEnum
|
||||
let flags: QueryDirectoryRequest.Flags
|
||||
let fileIndex: UInt32
|
||||
let fileId: FileId
|
||||
let searchPatternOffset: UInt8
|
||||
let searchPatternLength: UInt8
|
||||
let bufferLength: UInt32
|
||||
}
|
||||
|
||||
struct Flags: OptionSet {
|
||||
let rawValue: UInt8
|
||||
|
||||
init(rawValue: UInt8) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
static let RESTART_SCANS = Flags(rawValue: 0x01)
|
||||
static let RETURN_SINGLE_ENTRY = Flags(rawValue: 0x02)
|
||||
static let INDEX_SPECIFIED = Flags(rawValue: 0x04)
|
||||
static let REOPEN = Flags(rawValue: 0x10)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: SMB2 Change Notify
|
||||
struct QueryDirectoryResponse: SMBResponseBody {
|
||||
let buffer: Data
|
||||
|
||||
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:
|
||||
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 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
|
||||
}
|
||||
offset += Int(header.nextEntryOffset)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
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.subdata(in: offset..<(offset + length))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: SMB2 Query Info
|
||||
|
||||
}
|
||||
struct QueryInfoRequest: SMBRequestBody {
|
||||
let header: Header
|
||||
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)
|
||||
self.buffer = nil
|
||||
}
|
||||
|
||||
init(fileId: FileId, extendedAttributes: [String], flags: Flags = [], outputBufferLength: UInt32 = 65535) {
|
||||
var buffer = Data()
|
||||
for ea in extendedAttributes {
|
||||
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(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) {
|
||||
self.header = Header(size: 41, infoType: 2, infoClass: infoClass.rawValue, outputBufferLength: outputBufferLength, inputBufferOffset: 0, reserved: 0, inputBufferLength: 0, additionalInformation: [], flags: [], fileId: fileId)
|
||||
self.buffer = nil
|
||||
}
|
||||
|
||||
init(fileId: FileId, securityInfo: FileSecurityInfo, outputBufferLength: UInt32 = 65535) {
|
||||
self.header = Header(size: 41, infoType: 3, infoClass: 0, outputBufferLength: outputBufferLength, inputBufferOffset: 0, reserved: 0, inputBufferLength: 0, additionalInformation: securityInfo, flags: [], fileId: fileId)
|
||||
self.buffer = nil
|
||||
}
|
||||
|
||||
// TODO: Implement QUOTA_INFO init
|
||||
|
||||
func data() -> Data {
|
||||
var result = Data(value: header)
|
||||
if let buffer = buffer {
|
||||
result.append(buffer)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
struct Header {
|
||||
let size: UInt16
|
||||
let infoType: UInt8
|
||||
let infoClass: UInt8
|
||||
let outputBufferLength: UInt32
|
||||
let inputBufferOffset: UInt16
|
||||
fileprivate let reserved: UInt16
|
||||
let inputBufferLength: UInt32
|
||||
let additionalInformation: FileSecurityInfo
|
||||
let flags: QueryInfoRequest.Flags
|
||||
let fileId: FileId
|
||||
}
|
||||
|
||||
struct Flags: OptionSet {
|
||||
let rawValue: UInt32
|
||||
|
||||
init(rawValue: UInt32) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
static let RESTART_SCAN = Flags(rawValue: 0x00000001)
|
||||
static let RETURN_SINGLE_ENTRY = Flags(rawValue: 0x00000002)
|
||||
static let INDEX_SPECIFIED = Flags(rawValue: 0x00000004)
|
||||
}
|
||||
}
|
||||
|
||||
struct QueryInfoResponse: SMBResponseBody {
|
||||
let buffer: Data
|
||||
|
||||
init?(data: Data) {
|
||||
let structSize: UInt16 = data.scanValue()!
|
||||
guard structSize == 9 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
/*let offsetData = data.subdataWithRange(NSRange(location: 2, length: 2))
|
||||
let offset: UInt16 = decode(offsetData)*/
|
||||
|
||||
let length = Int(data.scanValue(start: 4) as UInt32!)
|
||||
|
||||
guard data.count >= 8 + length else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.buffer = data.subdata(in: 8..<(8 + length))
|
||||
}
|
||||
|
||||
var asAccessInformation: FileAccessInformation {
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asAlignmentInformation: FileAlignmentInformation {
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asAllInformation: (header: FileAllInformationHeader, name: String) {
|
||||
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 {
|
||||
return buffer.scanString(start: 0, length: buffer.count, encoding: .utf16) ?? ""
|
||||
}
|
||||
|
||||
var asAttributeTagInformation: FileAttributeTagInformation {
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asBasicInformation: FileBasicInformation {
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asCompressionInformation: FileCompressionInformation {
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asEaInformation: FileEaInformation {
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asFullEaInformation: FileFullEaInformation {
|
||||
// TODO:
|
||||
return FileFullEaInformation()
|
||||
}
|
||||
|
||||
var asInternalInformation: FileInternalInformation {
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asModeInformation: FileModeInformation {
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asNetworkOpenInformation: FileNetworkOpenInformation {
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asPipeInformation: FilePipeInformation {
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asPipeLocalInformation: FilePipeLocalInformation {
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asPipeRemoteInformation: FilePipeRemoteInformation {
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asPositionInformation: FilePositionInformation {
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asStandardInformation: FileStandardInformation {
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asStreamInformation: (header: FileStreamInformationHeader, name: String) {
|
||||
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 = 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 buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asFsDeviceInformation: FileFsDeviceInformation {
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asFsAttributeInformation: (header: FileFsAttributeInformationHeader, name: String) {
|
||||
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 buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asFsFullSizeInformation: FileFsFullSizeInformation {
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asFsObjectIdInformation: FileFsObjectIdInformation {
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
|
||||
var asFsSectorSizeInformation: FileFsSectorSizeInformation {
|
||||
return buffer.scanValue()!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,597 @@
|
||||
//
|
||||
// SMB2QueryTypes.swift
|
||||
// FileProvider
|
||||
//
|
||||
// Created by Amir Abbas Mousavian on 5/19/95.
|
||||
//
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol SMB2FilesInformationHeader: SMBResponseBody {
|
||||
var nextEntryOffset: UInt32 { get }
|
||||
var fileIndex: UInt32 { get }
|
||||
var fileNameLength : UInt32 { get }
|
||||
}
|
||||
|
||||
extension SMB2 {
|
||||
enum FileInformationEnum: UInt8 {
|
||||
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 queryInfoFile: [FileInformationEnum] = [.fileAccessInformation, .fileAlignmentInformation, .fileAllInformation, .fileAlternateNameInformation, .fileAttributeTagInformation, .fileBasicInformation, .fileCompressionInformation, fileEaInformation, .fileFullEaInformation, .fileInternalInformation, .fileModeInformation, .fileNetworkOpenInformation, .filePipeInformation, .filePipeLocalInformation, .filePipeRemoteInformation, .filePositionInformation, .fileStandardInformation, .fileStreamInformation]
|
||||
}
|
||||
|
||||
enum FileSystemInformationEnum: UInt8 {
|
||||
case none = 0
|
||||
case fileFsAttributeInformation
|
||||
case fileFsControlInformation
|
||||
case fileFsDeviceInformation
|
||||
case fileFsFullSizeInformation
|
||||
case fileFsObjectIdInformation
|
||||
case fileFsSectorSizeInformation
|
||||
case fileFsSizeInformation
|
||||
case fileFsVolumeInformation
|
||||
}
|
||||
|
||||
struct FileSecurityInfo: OptionSet {
|
||||
let rawValue: UInt32
|
||||
|
||||
init(rawValue: UInt32) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
static let OWNER = FileSecurityInfo(rawValue: 0x00000001)
|
||||
static let GROUP = FileSecurityInfo(rawValue: 0x00000002)
|
||||
static let DACL = FileSecurityInfo(rawValue: 0x00000004)
|
||||
static let SACL = FileSecurityInfo(rawValue: 0x00000008)
|
||||
static let LABEL = FileSecurityInfo(rawValue: 0x00000010)
|
||||
static let ATTRIBUTE = FileSecurityInfo(rawValue: 0x00000020)
|
||||
static let SCOPE = FileSecurityInfo(rawValue: 0x00000040)
|
||||
static let BACKUP = FileSecurityInfo(rawValue: 0x00010000)
|
||||
}
|
||||
|
||||
struct FileDirectoryInformationHeader: SMB2FilesInformationHeader {
|
||||
let nextEntryOffset: UInt32
|
||||
let fileIndex: UInt32
|
||||
let creationTime: SMBTime
|
||||
let lastAccesTime: SMBTime
|
||||
let lastWriteTime: SMBTime
|
||||
let changeTime: SMBTime
|
||||
let fileSize: UInt64
|
||||
let allocationSize: UInt64
|
||||
let fileAttributes: FileAttributes
|
||||
let fileNameLength : UInt32
|
||||
}
|
||||
|
||||
struct FileFullDirectoryInformationHeader: SMB2FilesInformationHeader {
|
||||
let nextEntryOffset: UInt32
|
||||
let fileIndex: UInt32
|
||||
let creationTime: SMBTime
|
||||
let lastAccesTime: SMBTime
|
||||
let lastWriteTime: SMBTime
|
||||
let changeTime: SMBTime
|
||||
let fileSize: UInt64
|
||||
let allocationSize: UInt64
|
||||
let fileAttributes: FileAttributes
|
||||
let fileNameLength : UInt32
|
||||
let extendedAttributesSize: UInt32
|
||||
}
|
||||
|
||||
struct FileIdFullDirectoryInformationHeader: SMB2FilesInformationHeader {
|
||||
let nextEntryOffset: UInt32
|
||||
let fileIndex: UInt32
|
||||
let creationTime: SMBTime
|
||||
let lastAccesTime: SMBTime
|
||||
let lastWriteTime: SMBTime
|
||||
let changeTime: SMBTime
|
||||
let fileSize: UInt64
|
||||
let allocationSize: UInt64
|
||||
let fileAttributes: FileAttributes
|
||||
let fileNameLength : UInt32
|
||||
let extendedAttributesSize: UInt32
|
||||
fileprivate let reserved: UInt32
|
||||
let fileId: FileId
|
||||
}
|
||||
|
||||
struct FileBothDirectoryInformationHeader: SMB2FilesInformationHeader {
|
||||
let nextEntryOffset: UInt32
|
||||
let fileIndex: UInt32
|
||||
let creationTime: SMBTime
|
||||
let lastAccesTime: SMBTime
|
||||
let lastWriteTime: SMBTime
|
||||
let changeTime: SMBTime
|
||||
let fileSize: UInt64
|
||||
let allocationSize: UInt64
|
||||
let fileAttributes: FileAttributes
|
||||
let fileNameLength : UInt32
|
||||
let extendedAttributesSize: UInt32
|
||||
fileprivate let shortNameLen: UInt8
|
||||
fileprivate let reserved: UInt8
|
||||
fileprivate let _shortName: FileShortNameType
|
||||
var shortName: String? {
|
||||
var data = Data(value: _shortName)
|
||||
data.count = Int(shortNameLen)
|
||||
return String(data: data, encoding: .utf16)
|
||||
}
|
||||
}
|
||||
|
||||
struct FileIdBothDirectoryInformationHeader: SMB2FilesInformationHeader {
|
||||
let nextEntryOffset: UInt32
|
||||
let fileIndex: UInt32
|
||||
let creationTime: SMBTime
|
||||
let lastAccesTime: SMBTime
|
||||
let lastWriteTime: SMBTime
|
||||
let changeTime: SMBTime
|
||||
let fileSize: Int64
|
||||
let allocationSize: Int64
|
||||
let fileAttributes: FileAttributes
|
||||
let fileNameLength : UInt32
|
||||
let extendedAttributesSize: UInt32
|
||||
fileprivate let shortNameLen: UInt8
|
||||
fileprivate let reserved: UInt8
|
||||
fileprivate let _shortName: FileShortNameType
|
||||
var shortName: String? {
|
||||
var data = Data(value: _shortName)
|
||||
data.count = Int(shortNameLen)
|
||||
return String(data: data, encoding: .utf16)
|
||||
}
|
||||
fileprivate let reserved2: UInt16
|
||||
let fileId : FileId
|
||||
}
|
||||
|
||||
struct FileNamesInformationHeader: SMB2FilesInformationHeader {
|
||||
let nextEntryOffset: UInt32
|
||||
let fileIndex: UInt32
|
||||
let fileNameLength : UInt32
|
||||
}
|
||||
|
||||
typealias FileShortNameType = (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
|
||||
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
|
||||
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)
|
||||
|
||||
struct FileAccessInformation {
|
||||
let accessMask: FileAccessMask
|
||||
}
|
||||
|
||||
struct FileAlignmentInformation {
|
||||
fileprivate let _alignment: UInt32
|
||||
var alignmentLength: UInt32 {
|
||||
return _alignment + 1
|
||||
}
|
||||
}
|
||||
|
||||
struct FileAllInformationHeader {
|
||||
let basic: FileBasicInformation
|
||||
let standard: FileStandardInformation
|
||||
let `internal`: FileInternalInformation
|
||||
let ea: FileEaInformation
|
||||
let access: FileAccessInformation
|
||||
let position: FilePositionInformation
|
||||
let mode: FileModeInformation
|
||||
let alignment: FileAlignmentInformation
|
||||
let nameLength: UInt32
|
||||
}
|
||||
|
||||
struct FileAttributeTagInformation {
|
||||
let fileAttributes: FileAttributes
|
||||
let reparseTag: UInt32
|
||||
}
|
||||
|
||||
struct FileBasicInformation {
|
||||
let creationTime: SMBTime
|
||||
let lastAccesTime: SMBTime
|
||||
let lastWriteTime: SMBTime
|
||||
let changeTime: SMBTime
|
||||
let fileAttributes: FileAttributes
|
||||
fileprivate let reserved: UInt32 = 0
|
||||
}
|
||||
|
||||
struct FileCompressionInformation {
|
||||
let compressedFileSize: Int64
|
||||
let compressionFormat: UInt16
|
||||
static let COMPRESSION_FORMAT_LZNT1 = 0x0002
|
||||
let compressionUnitShift: UInt8
|
||||
let chunkShift: UInt8
|
||||
let clusterShift: UInt8
|
||||
fileprivate let reserved: (UInt8, UInt16)
|
||||
}
|
||||
|
||||
struct FileEaInformation {
|
||||
let eaSize: UInt32
|
||||
}
|
||||
|
||||
struct FileFullEaInformation {
|
||||
// TODO
|
||||
}
|
||||
|
||||
struct FileInternalInformation {
|
||||
let indexNumber: UInt64
|
||||
}
|
||||
|
||||
struct FileModeInformation {
|
||||
let mode: Mode
|
||||
|
||||
struct Mode: OptionSet {
|
||||
let rawValue: UInt32
|
||||
|
||||
init(rawValue: UInt32) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
static let FILE_WRITE_THROUGH = Mode(rawValue: 0x00000002)
|
||||
static let FILE_SEQUENTIAL_ONLY = Mode(rawValue: 0x00000004)
|
||||
static let FILE_NO_INTERMEDIATE_BUFFERING = Mode(rawValue: 0x00000008)
|
||||
static let FILE_SYNCHRONOUS_IO_ALERT = Mode(rawValue: 0x00000010)
|
||||
static let FILE_SYNCHRONOUS_IO_NONALERT = Mode(rawValue: 0x00000020)
|
||||
static let FILE_DELETE_ON_CLOSE = Mode(rawValue: 0x00001000)
|
||||
}
|
||||
}
|
||||
|
||||
struct FileNetworkOpenInformation {
|
||||
let creationTime: SMBTime
|
||||
let lastAccesTime: SMBTime
|
||||
let lastWriteTime: SMBTime
|
||||
let changeTime: SMBTime
|
||||
let fileAttributes: FileAttributes
|
||||
fileprivate let reserved: UInt32
|
||||
}
|
||||
|
||||
struct FilePipeInformation {
|
||||
fileprivate let _readMode: UInt32
|
||||
var readMode: ReadMode {
|
||||
return ReadMode(rawValue: _readMode) ?? .BYTE_STREAM_MODE
|
||||
}
|
||||
fileprivate let _completionMode: UInt32
|
||||
var completionMode: CompletionMode {
|
||||
return CompletionMode(rawValue: _completionMode) ?? .QUEUE_OPERATION
|
||||
}
|
||||
|
||||
enum ReadMode: UInt32 {
|
||||
case BYTE_STREAM_MODE = 0x00000000
|
||||
case MESSAGE_MODE = 0x00000001
|
||||
}
|
||||
|
||||
enum CompletionMode: UInt32 {
|
||||
case QUEUE_OPERATION = 0x00000000
|
||||
case COMPLETE_OPERATION = 0x00000001
|
||||
}
|
||||
}
|
||||
|
||||
struct FilePipeLocalInformation {
|
||||
fileprivate let _namedPipeType: UInt32
|
||||
var namedPipeType: Type {
|
||||
return Type(rawValue: _namedPipeType) ?? .BYTE_STREAM_TYPE
|
||||
}
|
||||
fileprivate let _namedPipeConfiguration: UInt32
|
||||
var namedPipeConfiguration: Configuration {
|
||||
return Configuration(rawValue: _namedPipeConfiguration) ?? .INBOUND
|
||||
}
|
||||
let maximumInstances: UInt32
|
||||
let currentInstances: UInt32
|
||||
let inboundQuota: UInt32
|
||||
let readDataAvailable: UInt32
|
||||
let outboundQuota: UInt32
|
||||
let writeQuotaAvailable: UInt32
|
||||
fileprivate let _namedPipeState: UInt32
|
||||
var namedPipeState: State {
|
||||
return State(rawValue: _namedPipeState) ?? .DISCONNECTED_STATE
|
||||
}
|
||||
fileprivate let _namedPipeEnd: UInt32
|
||||
var namedPipeEnd: End {
|
||||
return End(rawValue: _namedPipeEnd) ?? .CLIENT_END
|
||||
}
|
||||
|
||||
enum `Type`: UInt32 {
|
||||
case BYTE_STREAM_TYPE = 0x00000000
|
||||
case MESSAGE_TYPE = 0x00000001
|
||||
}
|
||||
|
||||
enum Configuration: UInt32 {
|
||||
case INBOUND = 0x00000000
|
||||
case OUTBOUND = 0x00000001
|
||||
case FULL_DUPLEX = 0x00000002
|
||||
}
|
||||
|
||||
enum State: UInt32 {
|
||||
case DISCONNECTED_STATE = 0x00000001
|
||||
case LISTENING_STATE = 0x00000002
|
||||
case CONNECTED_STATE = 0x00000003
|
||||
case CLOSING_STATE = 0x00000004
|
||||
}
|
||||
|
||||
enum End: UInt32 {
|
||||
case CLIENT_END = 0x00000000
|
||||
case SERVER_END = 0x00000001
|
||||
}
|
||||
}
|
||||
|
||||
struct FilePipeRemoteInformation {
|
||||
let collectDataTime: SMBTime
|
||||
let maximumCollectionCount: UInt32
|
||||
}
|
||||
|
||||
struct FilePositionInformation {
|
||||
let currentByteOffset: Int64
|
||||
}
|
||||
|
||||
struct FileStandardInformation {
|
||||
let allocationSize: Int64
|
||||
let fileSize: Int64
|
||||
let numberOfLinks: UInt32
|
||||
let deletePending: Bool
|
||||
let directory: Bool
|
||||
fileprivate let reserved: UInt16
|
||||
}
|
||||
|
||||
struct FileStreamInformationHeader {
|
||||
let nextEntryOffset: UInt32
|
||||
let streamNameLength: UInt32
|
||||
let streamSize: Int64
|
||||
let streamAllocationSize: Int64
|
||||
}
|
||||
|
||||
struct FileFsVolumeInformationHeader {
|
||||
let creationTime: SMBTime
|
||||
let serial: UInt32
|
||||
let labelLength: UInt32
|
||||
let supportObjects: Bool
|
||||
let reserved: UInt8
|
||||
}
|
||||
|
||||
struct FileFsSizeInformation {
|
||||
let totalAllocationUnits: Int64
|
||||
let availableAllocationUnits: Int64
|
||||
let sectorsPerAllocationUnit: UInt32
|
||||
let bytesPerSector: UInt32
|
||||
}
|
||||
|
||||
struct FileFsDeviceInformation {
|
||||
fileprivate let _deviceType: UInt32
|
||||
var deviceType: DeviceType {
|
||||
return DeviceType(rawValue: _deviceType) ?? .DISK
|
||||
}
|
||||
let charactristics: Charactristics
|
||||
|
||||
enum DeviceType: UInt32 {
|
||||
case CD_ROM = 0x00000002
|
||||
case DISK = 0x00000007
|
||||
}
|
||||
|
||||
struct Charactristics: OptionSet {
|
||||
let rawValue: UInt32
|
||||
|
||||
init(rawValue: UInt32) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
/// Storage device supports removable media. For example, drivers for JAZ drive devices specify this characteristic, but drivers for PCMCIA flash disks do not.
|
||||
static let REMOVABLE_MEDIA = Charactristics(rawValue: 0x00000001)
|
||||
/// Indicates that the device cannot be written to.
|
||||
static let READ_ONLY_DEVICE = Charactristics(rawValue: 0x00000002)
|
||||
/// Indicates that the device is a floppy disk device.
|
||||
static let FLOPPY_DISKETTE = Charactristics(rawValue: 0x00000004)
|
||||
/// Indicates that the device supports write-once media.
|
||||
static let WRITE_ONCE_MEDIA = Charactristics(rawValue: 0x00000008)
|
||||
/// ndicates that the volume is for a remote file system like SMB or CIFS.
|
||||
static let REMOTE_DEVICE = Charactristics(rawValue: 0x00000010)
|
||||
/// Indicates that a file system is mounted on the device.
|
||||
static let DEVICE_IS_MOUNTED = Charactristics(rawValue: 0x00000020)
|
||||
/// Indicates that the volume does not directly reside on storage media, but resides on some other type of media (memory for example).
|
||||
static let VIRTUAL_VOLUME = Charactristics(rawValue: 0x00000040)
|
||||
/// By default, volumes do not check the ACL associated with the volume, but instead use the ACLs associated with individual files on the volume. When this flag is set the volume ACL is also checked.
|
||||
static let DEVICE_SECURE_OPEN = Charactristics(rawValue: 0x00000100)
|
||||
/// Indicates that the device object is part of a Terminal Services device stack.
|
||||
static let TS_DEVICE = Charactristics(rawValue: 0x00001000)
|
||||
/// ndicates that a web-based Distributed Authoring and Versioning (WebDAV) file system is mounted on the device.
|
||||
static let WEBDAV_DEVICE = Charactristics(rawValue: 0x00002000)
|
||||
/// The IO Manager normally performs a full security check for traverse access on every file open when the client is an appcontainer. Setting of this flag bypasses this enforced traverse access check if the client token already has traverse privileges.
|
||||
static let PORTABLE_DEVICE = Charactristics(rawValue: 0x0004000)
|
||||
/// Indicates that the given device resides on a portable bus like USB or Firewire and that the entire device (not just the media) can be removed from the system.
|
||||
static let DEVICE_ALLOW_APPCONTAINER_TRAVERSAL = Charactristics(rawValue: 0x00020000)
|
||||
}
|
||||
}
|
||||
|
||||
struct FileFsAttributeInformationHeader {
|
||||
let attributes: Attributes
|
||||
let maximumFileNameLength: Int32
|
||||
let nameLength: UInt32
|
||||
|
||||
struct Attributes: OptionSet {
|
||||
let rawValue: UInt32
|
||||
|
||||
init(rawValue: UInt32) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
/// The file system supports case-sensitive file names when looking up (searching for) file names in a directory.
|
||||
static let CASE_SENSITIVE_SEARCH = Attributes(rawValue: 0x00000001)
|
||||
/// The file system preserves the case of file names when it places a name on disk.
|
||||
static let CASE_PRESERVED_NAMES = Attributes(rawValue: 0x00000002)
|
||||
/// The file system supports Unicode in file and directory names. This flag applies only to file and directory names; the file system neither restricts nor interprets the bytes of data within a file.
|
||||
static let UNICODE_ON_DISK = Attributes(rawValue: 0x00000004)
|
||||
/// The file system preserves and enforces access control lists (ACLs).
|
||||
static let PERSISTENT_ACLS = Attributes(rawValue: 0x00000008)
|
||||
/// The file volume supports file-based compression. This flag is incompatible with the FILE_VOLUME_IS_COMPRESSED flag.
|
||||
static let FILE_COMPRESSION = Attributes(rawValue: 0x00000010)
|
||||
/// The file system supports per-user quotas.
|
||||
static let VOLUME_QUOTAS = Attributes(rawValue: 0x00000020)
|
||||
/// The file system supports sparse files.
|
||||
static let SUPPORTS_SPARSE_FILES = Attributes(rawValue: 0x00000040)
|
||||
/// The file system supports reparse points.
|
||||
static let SUPPORTS_REPARSE_POINTS = Attributes(rawValue: 0x00000080)
|
||||
/// The file system supports remote storage.
|
||||
static let REMOTE_STORAGE = Attributes(rawValue: 0x00000100)
|
||||
/// The specified volume is a compressed volume. This flag is incompatible with the FILE_FILE_COMPRESSION flag.
|
||||
static let IS_COMPRESSED = Attributes(rawValue: 0x00008000)
|
||||
/// The file system supports object identifiers.
|
||||
static let OBJECT_IDS = Attributes(rawValue: 0x00010000)
|
||||
/// The file system supports the Encrypted File System (EFS).
|
||||
static let ENCRYPTION = Attributes(rawValue: 0x00020000)
|
||||
/// The file system supports named streams. (aka. Resource Fork on MacOS)
|
||||
static let NAMED_STREAMS = Attributes(rawValue: 0x00040000)
|
||||
/// If set, the volume has been mounted in read-only mode.
|
||||
static let READ_ONLY_VOLUME = Attributes(rawValue: 0x00080000)
|
||||
/// The underlying volume is write once. (aka tapes)
|
||||
static let SEQUENTIAL_WRITE_ONCE = Attributes(rawValue: 0x00100000)
|
||||
/// The volume supports transactions.
|
||||
static let SUPPORTS_TRANSACTIONS = Attributes(rawValue: 0x00200000)
|
||||
/// The file system supports hard linking files.
|
||||
static let SUPPORTS_HARD_LINKS = Attributes(rawValue: 0x00400000)
|
||||
/// The file system persistently stores Extended Attribute information per file.
|
||||
static let SUPPORTS_EXTENDED_ATTRIBUTES = Attributes(rawValue: 0x00800000)
|
||||
/// The file system supports opening a file by FileID or ObjectID.
|
||||
static let SUPPORTS_OPEN_BY_FILE_ID = Attributes(rawValue: 0x01000000)
|
||||
/// The file system implements a USN change journal.
|
||||
static let USN_JOURNAL = Attributes(rawValue: 0x02000000)
|
||||
/// The file system supports integrity streams.
|
||||
static let SUPPORT_INTEGRITY_STREAMS = Attributes(rawValue: 0x04000000)
|
||||
/// The file system supports sharing logical clusters between files on the same volume. The file system reallocates on writes to shared clusters. Indicates that FSCTL_DUPLICATE_EXTENTS_TO_FILE is a supported operation.
|
||||
static let SUPPORTS_BLOCK_REFCOUNTING = Attributes(rawValue: 0x08000000)
|
||||
/// The file system tracks whether each cluster of a file contains valid data (either from explicit file writes or automatic zeros) or invalid data (has not yet been written to or zeroed).
|
||||
static let SUPPORTS_SPARSE_VDL = Attributes(rawValue: 0x10000000)
|
||||
}
|
||||
}
|
||||
|
||||
struct FileFsControlInformation {
|
||||
let freeSpaceStartFiltering: Int64
|
||||
let freeSpaceThreshold: Int64
|
||||
let freeSpaceStopFiltering: Int64
|
||||
let defaultQuotaThreshold: UInt64
|
||||
let defaultQuotaLimit: UInt64
|
||||
let flags: Flags
|
||||
fileprivate let padding: UInt32 = 0
|
||||
|
||||
struct Flags: OptionSet {
|
||||
let rawValue: UInt32
|
||||
|
||||
init(rawValue: UInt32) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
/// Quotas are tracked on the volume, but they are not enforced. Tracked quotas enable reporting on the file system space used by system users. If both this flag and FILE_VC_QUOTA_ENFORCE are specified, FILE_VC_QUOTA_ENFORCE is ignored.
|
||||
static let QUOTA_TRACK = Flags(rawValue: 0x00000001)
|
||||
/// Quotas are tracked and enforced on the volume.
|
||||
static let QUOTA_ENFORCE = Flags(rawValue: 0x00000002)
|
||||
/// Content indexing is disabled.
|
||||
static let CONTENT_INDEX_DISABLED = Flags(rawValue: 0x00000008)
|
||||
/// An event log entry will be created when the user exceeds his or her assigned quota warning threshold.
|
||||
static let LOG_QUOTA_THRESHOLD = Flags(rawValue: 0x00000010)
|
||||
/// An event log entry will be created when the user exceeds the assigned disk quota limit.
|
||||
static let LOG_QUOTA_LIMIT = Flags(rawValue: 0x00000020)
|
||||
/// An event log entry will be created when the volume's free space threshold is exceeded.
|
||||
static let LOG_VOLUME_THRESHOLD = Flags(rawValue: 0x00000040)
|
||||
/// An event log entry will be created when the volume's free space limit is exceeded.
|
||||
static let LOG_VOLUME_LIMIT = Flags(rawValue: 0x00000080)
|
||||
/// The quota information for the volume is incomplete because it is corrupt, or the system is in the process of rebuilding the quota information.
|
||||
static let QUOTAS_INCOMPLETE = Flags(rawValue: 0x00000100)
|
||||
/// The file system is rebuilding the quota information for the volume.
|
||||
static let QUOTAS_REBUILDING = Flags(rawValue: 0x00000200)
|
||||
}
|
||||
}
|
||||
|
||||
struct FileFsFullSizeInformation {
|
||||
let totalAllocationUnits: Int64
|
||||
let callerAvailableAllocationUnits: Int64
|
||||
let actualAvailableAllocationUnits: Int64
|
||||
let sectorsPerAllocationUnit: UInt32
|
||||
let bytesPerSector: UInt32
|
||||
}
|
||||
|
||||
struct FileFsObjectIdInformation {
|
||||
let objectId: uuid_t
|
||||
let extendedInfo: (UInt64, UInt64, UInt64, UInt64, UInt64, UInt64)
|
||||
}
|
||||
|
||||
struct FileFsSectorSizeInformation {
|
||||
let logicalBytesPerSector: UInt32
|
||||
let physicalBytesPerSectorForAtomicity: UInt32
|
||||
let physicalBytesPerSectorForPerformance: UInt32
|
||||
let effectivePhysicalBytesPerSectorForAtomicity: UInt32
|
||||
let flags: Flags
|
||||
let byteOffsetForSectorAlignment: UInt32
|
||||
let byteOffsetForPartitionAlignment: UInt32
|
||||
|
||||
struct Flags: OptionSet {
|
||||
let rawValue: UInt32
|
||||
|
||||
init(rawValue: UInt32) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
/// When set, this flag indicates that the first physical sector of the device is aligned with the first logical sector. When not set, the first physical sector of the device is misaligned with the first logical sector.
|
||||
static let ALIGNED_DEVICE = Flags(rawValue: 0x00000001)
|
||||
/// When set, this flag indicates that the partition is aligned to physical sector boundaries on the storage device.
|
||||
static let PARTITION_ALIGNED_ON_DEVICE = Flags(rawValue: 0x00000002)
|
||||
/// When set, the device reports that it does not incur a seek penalty (this typically indicates that the device does not have rotating media, such as flash-based disks).
|
||||
static let NO_SEEK_PENALTY = Flags(rawValue: 0x00000008)
|
||||
/// When set, the device supports TRIM operations, either T13 (ATA) TRIM or T10 (SCSI/SAS) UNMAP.
|
||||
static let TRIM_ENABLED = Flags(rawValue: 0x00000010)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,66 +11,70 @@ import Foundation
|
||||
extension SMB2 {
|
||||
// MARK: SMB2 Negotiating
|
||||
|
||||
struct NegotiateRequest: SMBRequest {
|
||||
let request: NegotiateRequest.Header
|
||||
struct NegotiateRequest: SMBRequestBody {
|
||||
let header: NegotiateRequest.Header
|
||||
let dialects: [UInt16]
|
||||
let contexts: [(type: NegotiateContextType, data: NSData)]
|
||||
let contexts: [(type: NegotiateContextType, data: Data)]
|
||||
|
||||
init(request: NegotiateRequest.Header, dialects: [UInt16] = [0x0202], contexts: [(type: NegotiateContextType, data: NSData)] = []) {
|
||||
self.request = request
|
||||
init(header: NegotiateRequest.Header, dialects: [UInt16] = [0x0202], contexts: [(type: NegotiateContextType, data: Data)] = []) {
|
||||
self.header = header
|
||||
self.dialects = dialects
|
||||
self.contexts = contexts
|
||||
}
|
||||
|
||||
func data() -> NSData {
|
||||
var request = self.request
|
||||
request.dialectCount = UInt16(dialects.count)
|
||||
let dialectData = NSMutableData()
|
||||
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() -> Data {
|
||||
var header = self.header
|
||||
header.dialectCount = UInt16(dialects.count)
|
||||
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)
|
||||
request.contextOffset = UInt32(sizeof(request.dynamicType.self)) + UInt32(dialectData.length)
|
||||
request.contextCount = UInt16(contexts.count)
|
||||
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(&request))
|
||||
result.appendData(dialectData)
|
||||
result.appendData(contextData)
|
||||
var result = Data(value: header)
|
||||
result.append(dialectData as Data)
|
||||
result.append(contextData as Data)
|
||||
return result
|
||||
}
|
||||
|
||||
struct Header {
|
||||
var size: UInt16
|
||||
var dialectCount: UInt16
|
||||
let singing: NegotiateSinging
|
||||
private let reserved: UInt16
|
||||
let signing: NegotiateSinging
|
||||
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 = UInt64(contextOffset) + (UInt64(contextCount) << 32) + (UInt64(contextCount) << 48)
|
||||
let time = Int64(contextOffset) + (Int64(contextCount) << 32) + (Int64(contextCount) << 48)
|
||||
return SMBTime(time: time)
|
||||
}
|
||||
|
||||
init(singing: NegotiateSinging = [.ENABLED], capabilities: GlobalCapabilities, guid: uuid_t? = nil, clientStartTime: SMBTime? = nil) {
|
||||
init(capabilities: GlobalCapabilities, clientStartTime: SMBTime? = nil, guid: uuid_t? = nil, signing: NegotiateSinging = [.ENABLED]) {
|
||||
self.size = 36
|
||||
self.dialectCount = 0
|
||||
self.singing = singing
|
||||
self.signing = signing
|
||||
self.reserved = 0
|
||||
self.capabilities = capabilities
|
||||
self.guid = guid ?? (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
|
||||
self.guid = guid ?? (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)
|
||||
if let clientStartTime = clientStartTime {
|
||||
let time = clientStartTime.time
|
||||
self.contextOffset = UInt32(time & 0xffffffff)
|
||||
@@ -85,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 = []
|
||||
@@ -133,7 +137,7 @@ extension SMB2 {
|
||||
}
|
||||
}
|
||||
|
||||
struct NegotiateSinging: OptionSetType {
|
||||
struct NegotiateSinging: OptionSet {
|
||||
let rawValue: UInt16
|
||||
|
||||
init(rawValue: UInt16) {
|
||||
@@ -143,7 +147,7 @@ extension SMB2 {
|
||||
static let REQUIRED = NegotiateSinging(rawValue: 0x0002)
|
||||
}
|
||||
|
||||
struct NegotiateContextType: OptionSetType {
|
||||
struct NegotiateContextType: OptionSet {
|
||||
let rawValue: UInt16
|
||||
|
||||
init(rawValue: UInt16) {
|
||||
@@ -153,7 +157,7 @@ extension SMB2 {
|
||||
static let ENCRYPTION_CAPABILITIES = NegotiateContextType(rawValue: 0x0002)
|
||||
}
|
||||
|
||||
struct GlobalCapabilities: OptionSetType {
|
||||
struct GlobalCapabilities: OptionSet {
|
||||
let rawValue: UInt32
|
||||
|
||||
init(rawValue: UInt32) {
|
||||
@@ -170,22 +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
|
||||
}
|
||||
|
||||
func data() -> NSData {
|
||||
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() -> 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
|
||||
}
|
||||
@@ -195,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
|
||||
@@ -213,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) {
|
||||
@@ -224,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
|
||||
}
|
||||
@@ -252,7 +261,7 @@ extension SMB2 {
|
||||
let bufferLength: UInt16
|
||||
}
|
||||
|
||||
struct Flags: OptionSetType {
|
||||
struct Flags: OptionSet {
|
||||
let rawValue: UInt16
|
||||
|
||||
init(rawValue: UInt16) {
|
||||
@@ -265,20 +274,20 @@ extension SMB2 {
|
||||
}
|
||||
}
|
||||
|
||||
struct SessionSetupSinging: OptionSetType {
|
||||
struct SessionSetupSinging: OptionSet {
|
||||
let rawValue: UInt8
|
||||
|
||||
init(rawValue: UInt8) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
static let ENABLED = NegotiateSinging(rawValue: 0x01)
|
||||
static let REQUIRED = NegotiateSinging(rawValue: 0x02)
|
||||
static let ENABLED = SessionSetupSinging(rawValue: 0x01)
|
||||
static let REQUIRED = SessionSetupSinging(rawValue: 0x02)
|
||||
}
|
||||
|
||||
// MARK: SMB2 Log off
|
||||
|
||||
struct LogOff: SMBRequest, SMBResponse {
|
||||
struct LogOff: SMBRequestBody, SMBResponseBody {
|
||||
let size: UInt16
|
||||
let reserved: UInt16
|
||||
|
||||
@@ -286,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
|
||||
|
||||
@@ -306,13 +307,5 @@ extension SMB2 {
|
||||
self.size = 4
|
||||
self.reserved = 0
|
||||
}
|
||||
|
||||
init? (data: NSData) {
|
||||
self = decode(data)
|
||||
}
|
||||
|
||||
func data() -> NSData {
|
||||
return encode(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,5 +10,33 @@ import Foundation
|
||||
|
||||
extension SMB2 {
|
||||
// MARK: SMB2 Set Info
|
||||
struct SetInfoRequest: SMBRequestBody {
|
||||
let header: Header
|
||||
let buffer: Data?
|
||||
|
||||
func data() -> Data {
|
||||
var result = Data(value: header)
|
||||
result.append(buffer ?? Data())
|
||||
return result
|
||||
}
|
||||
|
||||
struct Header {
|
||||
let size: UInt16 = 33
|
||||
let infoType: UInt8
|
||||
fileprivate let infoClass: UInt8
|
||||
let bufferLength: UInt32
|
||||
let bufferOffset: UInt16
|
||||
fileprivate let reserved: UInt16
|
||||
let securityInfo: FileSecurityInfo
|
||||
let fileId: FileId
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
struct SetInfoResponse: SMBResponseBody {
|
||||
let size: UInt16
|
||||
|
||||
init() {
|
||||
self.size = 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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("/") && !host.containsString("/") && !share.containsString("/") && !share.containsString("/") else {
|
||||
guard !host.contains("/") && !share.contains("/") else {
|
||||
return nil
|
||||
}
|
||||
self.header = header
|
||||
let path = "\\\\\(host)\\\(share)"
|
||||
self.buffer = path.dataUsingEncoding(NSUTF8StringEncoding)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol FileProviderSMBHeader {
|
||||
var protocolID: UInt32 { get }
|
||||
static var protocolConst: UInt32 { get }
|
||||
}
|
||||
|
||||
// SMB2 Types
|
||||
struct SMB2 {
|
||||
struct Header: FileProviderSMBHeader { // 64 bytes
|
||||
@@ -19,13 +24,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 +40,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 +50,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 +68,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 +82,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 +105,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 +135,5 @@ struct SMB2 {
|
||||
|
||||
// MARK: SMB2 Oplock Break
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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 ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,249 +0,0 @@
|
||||
//
|
||||
// SocketTransmitter.swift
|
||||
// FileProvider
|
||||
//
|
||||
// Created by Amir Abbas Mousavian.
|
||||
// Copyright © 2016 Mousavian. Distributed under MIT license.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public class TCPSocketClient: NSObject, NSStreamDelegate {
|
||||
public static let ports = ["http": 80,
|
||||
"https": 443,
|
||||
"smb": 445,
|
||||
"ftp": 21,
|
||||
"sftp": 22,
|
||||
"sftp": 2121,
|
||||
"telnet": 23,
|
||||
"pop": 110,
|
||||
"smtp": 25,
|
||||
"imap": 143]
|
||||
public static let securePorts = ["https": 443,
|
||||
"smb": 445,
|
||||
"sftp": 22,
|
||||
"sftp": 2121,
|
||||
"telnet": 992,
|
||||
"pop": 995,
|
||||
"smtp": 465,
|
||||
"imap": 993]
|
||||
|
||||
private var inputStream: NSInputStream?
|
||||
private var outputStream: NSOutputStream?
|
||||
private var dataToBeSent: NSMutableData = NSMutableData()
|
||||
/// holds data received from server
|
||||
public let dataReceived: NSMutableData = NSMutableData()
|
||||
/// a url with valid scheme, dns or ip host and ports path and query sections will be neglected
|
||||
public let baseURL: NSURL
|
||||
/// a url with valid scheme, dns or ip host and ports path and query sections will be neglected
|
||||
public let secureConnection: Bool
|
||||
/// server's ports which is value between 1 to 65535
|
||||
private let port: UInt32
|
||||
private var open = false
|
||||
|
||||
/**
|
||||
* - parameter baseURL: a url with valid scheme, dns or ip host and ports
|
||||
* path and query sections will be neglected
|
||||
*
|
||||
* **Note** Call `connect()` to establish connection
|
||||
* - parameter secure: establishing connection using an SSL/TLS connection
|
||||
*/
|
||||
|
||||
public init?(baseURL: NSURL, secure: Bool = false) {
|
||||
self.baseURL = baseURL
|
||||
self.secureConnection = secure
|
||||
let scheme = baseURL.uw_scheme.lowercaseString
|
||||
let defaultPort = secure ? UInt32(TCPSocketClient.securePorts[scheme] ?? 0) : UInt32(TCPSocketClient.ports[scheme] ?? 0)
|
||||
self.port = baseURL.port?.unsignedIntValue ?? defaultPort
|
||||
if self.port == 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
disconnect()
|
||||
}
|
||||
|
||||
/**
|
||||
* Establshes a connection to desired server
|
||||
* - returns: A bool value which indicated there where no system error during
|
||||
* creating connection
|
||||
*/
|
||||
|
||||
public func connect() -> Bool {
|
||||
guard let hostStr = baseURL.host else {
|
||||
return false
|
||||
}
|
||||
var readStream : Unmanaged<CFReadStream>?
|
||||
var writeStream : Unmanaged<CFWriteStream>?
|
||||
let host : CFString = NSString(string: hostStr)
|
||||
|
||||
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, host, self.port, &readStream, &writeStream)
|
||||
inputStream = readStream?.takeRetainedValue()
|
||||
outputStream = writeStream?.takeRetainedValue()
|
||||
|
||||
guard let inputStream = inputStream, outputStream = outputStream else {
|
||||
return false
|
||||
}
|
||||
|
||||
inputStream.delegate = self
|
||||
outputStream.delegate = self
|
||||
|
||||
inputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
|
||||
outputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
|
||||
|
||||
if secureConnection {
|
||||
inputStream.setProperty(NSStreamSocketSecurityLevelNegotiatedSSL, forKey: NSStreamSocketSecurityLevelKey)
|
||||
outputStream.setProperty(NSStreamSocketSecurityLevelNegotiatedSSL, forKey: NSStreamSocketSecurityLevelKey)
|
||||
}
|
||||
|
||||
inputStream.open()
|
||||
outputStream.open()
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminates connection to the server
|
||||
*/
|
||||
|
||||
public func disconnect() {
|
||||
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?.delegate = nil
|
||||
self.outputStream?.delegate = nil
|
||||
|
||||
self.inputStream = nil
|
||||
self.outputStream = nil
|
||||
}
|
||||
|
||||
public func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent) {
|
||||
switch (eventCode) {
|
||||
case NSStreamEvent.ErrorOccurred:
|
||||
open = false
|
||||
case NSStreamEvent.EndEncountered:
|
||||
break
|
||||
case NSStreamEvent.None:
|
||||
break
|
||||
case NSStreamEvent.OpenCompleted:
|
||||
let activeStatus: [NSStreamStatus] = [.Open, .Reading, .Writing, .AtEnd]
|
||||
open = activeStatus.contains(inputStream?.streamStatus ?? .NotOpen) && activeStatus.contains(outputStream?.streamStatus ?? .NotOpen)
|
||||
case NSStreamEvent.HasBytesAvailable:
|
||||
var buffer = [UInt8](count: 2048, repeatedValue: 0)
|
||||
if ( aStream == inputStream) {
|
||||
while (inputStream!.hasBytesAvailable ?? false) {
|
||||
let len = inputStream!.read(&buffer, maxLength: buffer.count)
|
||||
if len > 0 {
|
||||
dataReceived.appendBytes(&buffer, length: len)
|
||||
}
|
||||
}
|
||||
}
|
||||
case NSStreamEvent.HasSpaceAvailable:
|
||||
if aStream == outputStream {
|
||||
do {
|
||||
try send(data: nil)
|
||||
} catch _ {
|
||||
NSLog("Sending error")
|
||||
}
|
||||
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends data to server
|
||||
* - parameter data: data which is intended to be sent to server
|
||||
* - throws: NSURLError.NetworkConnectionLost in case of server disconnects disgracefully
|
||||
*/
|
||||
|
||||
public func send(data data: NSData?) throws {
|
||||
guard let outputStream = outputStream else {
|
||||
return
|
||||
}
|
||||
if outputStream.hasSpaceAvailable ?? false {
|
||||
if let data = data {
|
||||
dataToBeSent.appendData(data)
|
||||
}
|
||||
|
||||
if dataToBeSent.length > 0 {
|
||||
let bytesWritten = outputStream.write(UnsafePointer(dataToBeSent.bytes), maxLength: dataToBeSent.length) ?? -1
|
||||
if bytesWritten > 0 {
|
||||
let range = NSRange(location: 0, length: bytesWritten)
|
||||
dataToBeSent.replaceBytesInRange(range, withBytes: nil, length: 0)
|
||||
} else {
|
||||
throw NSError(domain: NSURLErrorDomain, code: NSURLError.NetworkConnectionLost.rawValue, userInfo: nil)
|
||||
}
|
||||
}
|
||||
//println("Sent the following")
|
||||
} else { //steam busy
|
||||
if let data = data {
|
||||
dataToBeSent.appendData(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears entire send and receive buffer
|
||||
*/
|
||||
|
||||
public func flush() {
|
||||
dataToBeSent.length = 0
|
||||
dataReceived.length = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Put's thread in sleep until all data is sent
|
||||
* **Note:** Don't call this method from main thread
|
||||
*/
|
||||
|
||||
internal func waitUntillDataSent() {
|
||||
if NSThread.isMainThread() {
|
||||
assert(false, "waitUntillDataSent() method can't be called from main thread")
|
||||
}
|
||||
while true {
|
||||
if dataToBeSent.length == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
NSRunLoop.currentRunLoop().runUntilDate(NSDate(timeIntervalSinceNow: 0.1));
|
||||
NSThread.sleepForTimeInterval(0.1)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put's thread in sleep until all response from server is loaded into tcp stack
|
||||
* server response can be retrieved by `dataReceived` property
|
||||
* **Note:** Don't call this method from main thread
|
||||
* - returns: A Bool value indicates all response loaded from server successfullt
|
||||
*/
|
||||
|
||||
internal func waitUntilResponse() -> Bool {
|
||||
if NSThread.isMainThread() {
|
||||
assert(false, "waitUntilResponse() method can't be called from main thread")
|
||||
}
|
||||
var finished = false
|
||||
while !finished {
|
||||
switch inputStream?.streamStatus ?? .Error {
|
||||
case .AtEnd:
|
||||
finished = true
|
||||
return true
|
||||
case .Closed, .Error:
|
||||
return false
|
||||
default:
|
||||
finished = false
|
||||
}
|
||||
|
||||
NSRunLoop.currentRunLoop().runUntilDate(NSDate(timeIntervalSinceNow: 0.1));
|
||||
NSThread.sleepForTimeInterval(0.1)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
+413
-482
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user