Compare commits

...

63 Commits

Author SHA1 Message Date
Amir Abbas Mousavian 1a44df3fd7 Refactoring, better webdav response handling
- Added Dropbox copy from reference method
- refactored `mapToFileObject` methods into `FileObject` initializers
- fixed `requestDictionary` type to `[String: AnyObject]`
2017-02-01 14:08:28 +03:30
Amir Abbas Mousavian eff3725680 Added release version badge 2017-02-01 00:21:48 +03:30
Amir Abbas Mousavian 9368f636d3 Added carthage badge 2017-01-31 23:53:06 +03:30
Amir Abbas Mousavian 3f2fda638f bugfix: path in LocalFileObject is not relative 2017-01-31 23:12:29 +03:30
Amir Abbas Mousavian 6737f98978 Updated travis.yml 2017-01-31 19:07:11 +03:30
Amir Abbas Mousavian 3e55cc60f7 Replaced absoluteURL with relative url (resolved #27), improving performance.
- renamed `DropboxFileProvider.copyItem(path:toRemoteURL:) ` to `DropboxFileProvider.copyItem(remoteURL:toPath:)` due to logic
2017-01-31 17:19:45 +03:30
Alek Slater d7ae709cf7 contentURL was missing the trailing slash causing it to fail for all contentURL calls with the dropbox api (#26) 2017-01-31 13:05:20 +03:30
Amir Abbas Mousavian 4f6e3c7bd5 Uncommented CloudFileProvider decription 2017-01-31 02:11:28 +03:30
Amir Abbas Mousavian b121fe6182 rectified macOS and tvOS version check 2017-01-31 01:42:03 +03:30
Amir Abbas Mousavian 29b7f720f5 Added iCloud provider change monitor and CloudOperationHandle
- fixed copyItem functions for local interoperability
2017-01-31 01:35:38 +03:30
Amir Abbas Mousavian 067e14b76f Added CloudFileProvider and coordination to LocalFileProvider
- changed `operation_queue` type to `OperationQueue`
- added `LocalFileProvider.init(sharedContainerId:)`
- added `destination(ofSymbolicLink:)` to LocalFileProvider
- added expirationDate to `temporaryLink(to:)` methods
- known issue: `CloudFileProvider` monitoring is not implemented
- known issue: `CloudFileProvider` copy from local and to local works imperfect
2017-01-30 18:51:06 +03:30
Amir Abbas Mousavian 09e39ada82 Resolved issue #24, off-by-one bug in Range header 2017-01-30 18:37:25 +03:30
Amir Abbas 6889b302b7 Removed CloudProvider due to incompleteness 2017-01-27 02:47:11 +03:30
Amir Abbas 96ce12f226 Added iCloud Drive provider CloudFileProvider
- Refactored initializers
- Replaced `NSSearchPathForDirectoriesInDomains` with `FileManager.urls(for:In:)`
2017-01-27 02:13:51 +03:30
Amir Abbas 091fd14a88 OneDrive drive name bug fix. Added sorting method to [FileObject] array.
- now return `Error` for uploading a file with size higher than limit in OneDrive / Dropbox instead of asserting
2017-01-25 16:04:16 +03:30
Amir Abbas 10aac532f7 Fixed crashing bug 2017-01-18 10:20:10 +03:30
Amir Abbas 3aac0a88c4 Fixed path in url percent encoding 2017-01-16 23:28:32 +03:30
Amir Abbas 24355a4c6c Added OneDrive support
- removed unnecessary MediaPlayer.framework dependency
- fixed multiple Dropbox provider bugs
2017-01-16 21:55:58 +03:30
Amir Abbas d2cf657ab2 Added Thumbnail and Meta-information generator to Local and Dropbox 2017-01-16 15:20:44 +03:30
Alek Slater f2d54c7f92 incorrect usage of string.remove in correctPath method
- string remove needs to be given the index before the endIndex or it will crash (#22) needs to be given the index before the endIndex or it will crash
2016-12-19 18:10:14 +03:30
Amir Abbas 0e9a82a288 Resolves #20, Dropbox/WebDav file size 2016-12-15 14:42:00 +03:30
Amir Abbas ea7e089718 Updated for Xcode 8.2 2016-12-13 19:12:08 +03:30
Amir Abbas 19ea14ebc1 Added FileObjectSorting to sort FileObjects conveniently 2016-12-12 01:57:34 +03:30
Amir Abbas 47885c0a4e Deprecated FileObject.fileType with .type 2016-12-09 21:06:11 +03:30
Alek Slater ac44aa190f Overwrite param added to copyItem + minor bugfix and improvements (#18)
- Added overwrite param to copyItem from local to remote
- fixed an issue with file upload on dropbox that wouldnt work for people in non UTC timezones.
- made filebased upload for dropbox not read file data into memory let URLSession stream from the disk instead
2016-12-09 20:58:50 +03:30
Amir Abbas 50f6a393a0 Compiler Optimizations, Dropbox readme 2016-12-09 19:07:46 +03:30
Amir Abbas 779b38f381 Default values implemented in protocol level 2016-12-09 10:04:41 +03:30
Amir Abbas a374841883 Refined SPM/manual installation guide in Readme 2016-12-08 20:41:52 +03:30
Amir Abbas 0123d8b117 Overwrite option to writeContents 2016-12-07 21:24:19 +03:30
Amir Abbas Mousavian fdc4bb818d Merge pull request #17 from skela/master
Added overwrite option to DropboxProvider.writeContents()
2016-12-07 20:13:17 +03:30
Amir Abbas Mousavian a08a9fe7a0 Updated semantics
and changed default overwrite value to false
2016-12-07 20:12:15 +03:30
Alek Slater 792ac6b015 Update DropboxFileProvider.swift
Exposes the overwrite parameter for writeContents in DropboxFileProvider
2016-12-07 11:27:51 +08:00
Amir Abbas 8bad5944bc Updated pod spec version to 0.8.1 2016-12-07 02:36:49 +03:30
Amir Abbas 6ef2ab11c4 Fixed bug in Data(value:) initializer. Switch uuid init via this 2016-12-07 02:34:34 +03:30
Amir Abbas Mousavian 7f27d46c70 Merge pull request #16 from RndmTsk/master
Fixes #15, FileObject.fileType always returns nil.
2016-12-07 02:29:28 +03:30
Terry Latanville b1ec99b1b8 Fixes #15, FileObject.fileType always returns nil. 2016-12-06 17:18:41 -05:00
Amir Abbas Mousavian c4b8065cd3 Merge pull request #14 from skela/master
Update DropboxFileProvider.swift
2016-12-06 10:51:58 +03:30
Alek Slater 2d8454c711 Update DropboxFileProvider.swift
Using the same request dict creation method that can be found in the contents method, to avoid error when trying to use copyItem to copy a remote file to a local file destination.
2016-12-06 14:51:48 +08:00
Amir Abbas Mousavian 3b35c066de fixed get_temporary_link url 2016-12-03 20:40:04 +03:30
Amir Abbas Mousavian 7d8de9cefe FileObject init accessors fixed, refactoring 2016-12-03 20:26:27 +03:30
Amir Abbas Mousavian e84fef20ea [gardening] Refactoring WebDAV copy, move and remove operations 2016-12-03 15:32:54 +03:30
Amir Abbas Mousavian 4a9a3196a2 Dropbox get_temporary_link and save_url implementations 2016-12-03 15:12:11 +03:30
Amir Abbas Mousavian f4e6d277ae Updated source highlighting 2016-12-03 13:34:32 +03:30
Amir Abbas Mousavian 1328a8e9e2 New FIleObject implementation, highlighted Readme 2016-12-03 13:21:13 +03:30
Amir Abbas Mousavian 5bf620916f Fixes #12, createFile method path in WebDAV is not correct.
- removed NSURL cast for resource values, now using swift 3 methods
2016-12-02 01:12:08 +03:30
Amir Abbas Mousavian 4f56e20441 createFile definition improved to resolve #10 2016-12-01 12:12:53 +03:30
Amir Abbas Mousavian 9dda618b73 Revert "createFile definition improved"
This reverts commit da60c05188.
2016-12-01 12:10:14 +03:30
Amir Abbas Mousavian da60c05188 createFile definition improved 2016-12-01 11:54:03 +03:30
Amir Abbas Mousavian 4366855d54 Added NSCopying conformance, SMB headers gardening 2016-11-28 19:40:09 +03:30
Amir Abbas Mousavian fe05fd83fe Replaced encode/decode methods with Data extension 2016-11-26 00:11:46 +03:30
Amir Abbas Mousavian 826d207e6b OperationHandle optimizations
- code refactoring for RemoteOperationHandle usage and description
- bug fix: move operation in Dropbox provider did copy
- bug fix: dynamic inProgress result for RemoteOperationHandle
2016-11-24 22:54:00 +03:30
Amir Abbas Mousavian 66fc1e1284 Fixed unexpected behaviors in Remote providers:
- Calling delegate and completion methods
- refactoring codes
2016-11-23 23:42:36 +03:30
Amir Abbas Mousavian a1d489f5a5 Optimization in project settings for dynamic linking 2016-11-23 19:43:28 +03:30
Amir Abbas Mousavian ad2699cd19 Update Readme for OperationHandle 2016-11-11 16:37:32 +03:30
Amir Abbas Mousavian 97ae86cedb - fixed a bug in fileByUniqueName() function
- more neat code
2016-11-11 03:45:37 +03:30
Amir Abbas Mousavian 71b07cac1b fixed NotImplement() issue on build 2016-10-30 01:55:19 +03:30
Amir Abbas Mousavian a15f8f3809 Added OperationHandle, to cancel remote operations 2016-10-29 23:11:48 +03:30
Amir Abbas Mousavian dc1270d8d1 Fixing wrong spellings in readme 2016-10-26 01:04:33 +03:30
Amir Abbas Mousavian 68b1e23be3 Using Data instead of NSData in many places 2016-10-24 20:31:19 +03:30
Amir Abbas Mousavian 057c9fd940 Update podspec to 0.5.2 2016-09-30 14:08:25 +03:30
Amir Abbas Mousavian 6d63322779 FileProviderOperationDelegate methods will call for WebDav and Dropbox providers
- Added Carthage to Travis
2016-09-30 14:07:02 +03:30
Amir Abbas Mousavian fc6b46d17a Updated swift 3 semantics 2016-09-28 19:15:05 +03:30
Amir Abbas Mousavian bb9c08e309 Add .swift-version to pass cocoapods's validation, added static type var 2016-09-27 16:59:54 +03:30
39 changed files with 4544 additions and 1626 deletions
+1
View File
@@ -0,0 +1 @@
3.0
+29 -7
View File
@@ -1,10 +1,32 @@
language: objective-c
osx_image: xcode8
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 -project FileProvider.xcodeproj -scheme "FileProvider OSX" -sdk macosx | xcpretty
- xcodebuild -project FileProvider.xcodeproj -scheme "FileProvider iOS" -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO | xcpretty
- xcodebuild -project FileProvider.xcodeproj -scheme "FileProvider tvOS" -sdk appletvsimulator ONLY_ACTIVE_ARCH=NO | xcpretty
- pod lib lint --quick
after_success:
- bash <(curl -s https://codecov.io/bash)
- 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
+4 -4
View File
@@ -16,8 +16,8 @@ Pod::Spec.new do |s|
#
s.name = "FileProvider"
s.version = "0.5.0"
s.summary = "FileManager replacement for Local and Remote (WebDAV/Dropbox/SMB2) files on iOS and MacOS."
s.version = "0.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"
+145 -64
View File
@@ -7,12 +7,10 @@
objects = {
/* Begin PBXBuildFile section */
7902C0861D61B56D00564440 /* SessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7902C0851D61B56D00564440 /* SessionDelegate.swift */; };
7902C0871D61B67100564440 /* SessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7902C0851D61B56D00564440 /* SessionDelegate.swift */; };
7902C0881D61B67100564440 /* SessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7902C0851D61B56D00564440 /* SessionDelegate.swift */; };
7924B1931D89DAE000589DB7 /* AEXML.h in Headers */ = {isa = PBXBuildFile; fileRef = 7924B18C1D89DAE000589DB7 /* AEXML.h */; };
7924B1941D89DAE000589DB7 /* AEXML.h in Headers */ = {isa = PBXBuildFile; fileRef = 7924B18C1D89DAE000589DB7 /* AEXML.h */; };
7924B1951D89DAE000589DB7 /* AEXML.h in Headers */ = {isa = PBXBuildFile; fileRef = 7924B18C1D89DAE000589DB7 /* AEXML.h */; };
7902C0861D61B56D00564440 /* RemoteSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7902C0851D61B56D00564440 /* RemoteSession.swift */; };
7902C0871D61B67100564440 /* RemoteSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7902C0851D61B56D00564440 /* RemoteSession.swift */; };
7902C0881D61B67100564440 /* RemoteSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7902C0851D61B56D00564440 /* RemoteSession.swift */; };
791950F51DE58A5400B4426E /* libxml2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 791950F41DE58A5400B4426E /* libxml2.tbd */; };
7924B1961D89DAE000589DB7 /* Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B18D1D89DAE000589DB7 /* Document.swift */; };
7924B1971D89DAE000589DB7 /* Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B18D1D89DAE000589DB7 /* Document.swift */; };
7924B1981D89DAE000589DB7 /* Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B18D1D89DAE000589DB7 /* Document.swift */; };
@@ -22,9 +20,6 @@
7924B19C1D89DAE000589DB7 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B18F1D89DAE000589DB7 /* Error.swift */; };
7924B19D1D89DAE000589DB7 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B18F1D89DAE000589DB7 /* Error.swift */; };
7924B19E1D89DAE000589DB7 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B18F1D89DAE000589DB7 /* Error.swift */; };
7924B19F1D89DAE000589DB7 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7924B1901D89DAE000589DB7 /* Info.plist */; };
7924B1A01D89DAE000589DB7 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7924B1901D89DAE000589DB7 /* Info.plist */; };
7924B1A11D89DAE000589DB7 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7924B1901D89DAE000589DB7 /* Info.plist */; };
7924B1A21D89DAE000589DB7 /* Options.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B1911D89DAE000589DB7 /* Options.swift */; };
7924B1A31D89DAE000589DB7 /* Options.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B1911D89DAE000589DB7 /* Options.swift */; };
7924B1A41D89DAE000589DB7 /* Options.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7924B1911D89DAE000589DB7 /* Options.swift */; };
@@ -37,6 +32,12 @@
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 */; };
@@ -94,18 +95,42 @@
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 /* SessionDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionDelegate.swift; sourceTree = "<group>"; };
7924B18C1D89DAE000589DB7 /* AEXML.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AEXML.h; sourceTree = "<group>"; };
7902C0851D61B56D00564440 /* RemoteSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteSession.swift; sourceTree = "<group>"; };
791950F41DE58A5400B4426E /* libxml2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libxml2.tbd; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.1.sdk/usr/lib/libxml2.tbd; sourceTree = DEVELOPER_DIR; };
7924B18D1D89DAE000589DB7 /* Document.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Document.swift; sourceTree = "<group>"; };
7924B18E1D89DAE000589DB7 /* Element.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Element.swift; sourceTree = "<group>"; };
7924B18F1D89DAE000589DB7 /* Error.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = "<group>"; };
7924B1901D89DAE000589DB7 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
7924B1911D89DAE000589DB7 /* Options.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Options.swift; sourceTree = "<group>"; };
7924B1921D89DAE000589DB7 /* Parser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Parser.swift; sourceTree = "<group>"; };
7924B1A81D89F79200589DB7 /* FPSStreamTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FPSStreamTask.swift; sourceTree = "<group>"; };
792572401DF23BDA006A1526 /* LocalHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalHelper.swift; sourceTree = "<group>"; };
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>"; };
@@ -133,6 +158,24 @@
799396A31D48C02300086753 /* SMB2Types.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2Types.swift; sourceTree = "<group>"; };
799396A41D48C02300086753 /* SMBErrorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMBErrorType.swift; sourceTree = "<group>"; };
799396A61D48C02300086753 /* WebDAVFileProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebDAVFileProvider.swift; sourceTree = "<group>"; };
79BD638B1E2CC2300035128C /* ExtendedLocalFileProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtendedLocalFileProvider.swift; sourceTree = "<group>"; };
79BD63A71E2CC2940035128C /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.2.sdk/System/Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; };
79BD63A91E2CC2BB0035128C /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.2.sdk/System/Library/Frameworks/AVFoundation.framework; sourceTree = DEVELOPER_DIR; };
79BD63AB1E2CC2C20035128C /* ImageIO.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ImageIO.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.2.sdk/System/Library/Frameworks/ImageIO.framework; sourceTree = DEVELOPER_DIR; };
79BD63AD1E2CC2EB0035128C /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.2.sdk/System/Library/Frameworks/MediaPlayer.framework; sourceTree = DEVELOPER_DIR; };
79BD63AF1E2CC3300035128C /* libxml2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libxml2.tbd; path = usr/lib/libxml2.tbd; sourceTree = SDKROOT; };
79BD63B11E2CC3350035128C /* ImageIO.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ImageIO.framework; path = System/Library/Frameworks/ImageIO.framework; sourceTree = SDKROOT; };
79BD63B31E2CC33D0035128C /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = System/Library/Frameworks/MediaPlayer.framework; sourceTree = SDKROOT; };
79BD63B51E2CC3860035128C /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
79BD63B71E2CC38D0035128C /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
79BD63B91E2CC39B0035128C /* libxml2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libxml2.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.1.sdk/usr/lib/libxml2.tbd; sourceTree = DEVELOPER_DIR; };
79BD63BB1E2CC3B90035128C /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.1.sdk/System/Library/Frameworks/MediaPlayer.framework; sourceTree = DEVELOPER_DIR; };
79BD63BD1E2CC3C20035128C /* ImageIO.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ImageIO.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.1.sdk/System/Library/Frameworks/ImageIO.framework; sourceTree = DEVELOPER_DIR; };
79BD63BF1E2CC3CD0035128C /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.1.sdk/System/Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; };
79BD63C11E2CC3D30035128C /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.1.sdk/System/Library/Frameworks/AVFoundation.framework; sourceTree = DEVELOPER_DIR; };
79BD63C31E2D17880035128C /* OneDriveFileProvide.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OneDriveFileProvide.swift; sourceTree = "<group>"; };
79BD63C41E2D17880035128C /* OneDriveHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OneDriveHelper.swift; sourceTree = "<group>"; };
79F5745A1DFDB10A00179ABF /* FileObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileObject.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -140,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;
};
@@ -147,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;
};
@@ -154,20 +205,44 @@
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 = (
7924B18C1D89DAE000589DB7 /* AEXML.h */,
7924B18D1D89DAE000589DB7 /* Document.swift */,
7924B18E1D89DAE000589DB7 /* Element.swift */,
7924B18F1D89DAE000589DB7 /* Error.swift */,
7924B1901D89DAE000589DB7 /* Info.plist */,
7924B1911D89DAE000589DB7 /* Options.swift */,
7924B1921D89DAE000589DB7 /* Parser.swift */,
);
@@ -177,9 +252,11 @@
7993965B1D48B7BF00086753 = {
isa = PBXGroup;
children = (
79E34A101E2AC6C600E1293B /* Extra */,
799396911D48C02300086753 /* Sources */,
7993968A1D48B8C700086753 /* Pod */,
799396681D48B7F600086753 /* Products */,
791950F31DE58A5300B4426E /* Frameworks */,
);
sourceTree = "<group>";
};
@@ -206,14 +283,20 @@
799396911D48C02300086753 /* Sources */ = {
isa = PBXGroup;
children = (
799396991D48C02300086753 /* SMBTypes */,
799396931D48C02300086753 /* DropboxFileProvider.swift */,
7924B18B1D89DAE000589DB7 /* AEXML */,
794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */,
799396991D48C02300086753 /* SMBTypes */,
799396941D48C02300086753 /* FileProvider.h */,
799396951D48C02300086753 /* FileProvider.swift */,
79F5745A1DFDB10A00179ABF /* FileObject.swift */,
799396961D48C02300086753 /* LocalFileProvider.swift */,
7902C0851D61B56D00564440 /* SessionDelegate.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 */,
@@ -242,6 +325,13 @@
path = SMBTypes;
sourceTree = "<group>";
};
79E34A101E2AC6C600E1293B /* Extra */ = {
isa = PBXGroup;
children = (
);
name = Extra;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
@@ -249,7 +339,6 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
7924B1931D89DAE000589DB7 /* AEXML.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -257,7 +346,6 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
7924B1941D89DAE000589DB7 /* AEXML.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -265,7 +353,6 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
7924B1951D89DAE000589DB7 /* AEXML.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -332,7 +419,7 @@
7993965C1D48B7BF00086753 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0800;
LastUpgradeCheck = 0810;
TargetAttributes = {
799396661D48B7F600086753 = {
CreatedOnToolsVersion = 7.3.1;
@@ -370,7 +457,6 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
7924B19F1D89DAE000589DB7 /* Info.plist in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -378,7 +464,6 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
7924B1A01D89DAE000589DB7 /* Info.plist in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -386,7 +471,6 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
7924B1A11D89DAE000589DB7 /* Info.plist in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -400,11 +484,16 @@
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 */,
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 */,
@@ -413,13 +502,14 @@
794C220A1D5893F800EC49B8 /* SMB2Notification.swift in Sources */,
799396D11D48C02300086753 /* SMB2SetInfo.swift in Sources */,
7924B1961D89DAE000589DB7 /* Document.swift in Sources */,
7902C0861D61B56D00564440 /* SessionDelegate.swift in Sources */,
7902C0861D61B56D00564440 /* RemoteSession.swift in Sources */,
799396CE1D48C02300086753 /* SMB2Session.swift in Sources */,
799396E01D48C02300086753 /* WebDAVFileProvider.swift in Sources */,
799396DA1D48C02300086753 /* SMBErrorType.swift in Sources */,
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 */,
@@ -434,11 +524,16 @@
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 */,
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 */,
@@ -447,13 +542,14 @@
794C220B1D5893F800EC49B8 /* SMB2Notification.swift in Sources */,
799396D21D48C02300086753 /* SMB2SetInfo.swift in Sources */,
7924B1971D89DAE000589DB7 /* Document.swift in Sources */,
7902C0871D61B67100564440 /* SessionDelegate.swift in Sources */,
7902C0871D61B67100564440 /* RemoteSession.swift in Sources */,
799396CF1D48C02300086753 /* SMB2Session.swift in Sources */,
799396E11D48C02300086753 /* WebDAVFileProvider.swift in Sources */,
799396DB1D48C02300086753 /* SMBErrorType.swift in Sources */,
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 */,
@@ -468,11 +564,16 @@
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 */,
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 */,
@@ -481,13 +582,14 @@
794C220C1D5893F800EC49B8 /* SMB2Notification.swift in Sources */,
799396D31D48C02300086753 /* SMB2SetInfo.swift in Sources */,
7924B1981D89DAE000589DB7 /* Document.swift in Sources */,
7902C0881D61B67100564440 /* SessionDelegate.swift in Sources */,
7902C0881D61B67100564440 /* RemoteSession.swift in Sources */,
799396D01D48C02300086753 /* SMB2Session.swift in Sources */,
799396E21D48C02300086753 /* WebDAVFileProvider.swift in Sources */,
799396DC1D48C02300086753 /* SMBErrorType.swift in Sources */,
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 */,
@@ -501,6 +603,7 @@
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;
@@ -510,9 +613,12 @@
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_BITCODE = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNDECLARED_SELECTOR = YES;
@@ -520,12 +626,14 @@
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;
@@ -535,6 +643,8 @@
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_BITCODE = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
@@ -544,6 +654,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 3.0;
};
name = Release;
};
@@ -552,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;
@@ -566,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*]" = "";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
@@ -595,13 +700,11 @@
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.mousavian.FileProvider-iOS";
PRODUCT_NAME = FileProvider;
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 3.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -613,9 +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;
@@ -627,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*]" = "";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
@@ -654,7 +752,6 @@
PRODUCT_NAME = FileProvider;
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_VERSION = 3.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
@@ -681,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)",
@@ -712,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;
@@ -742,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;
@@ -796,19 +885,15 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
@@ -823,14 +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 = "";
};
@@ -856,16 +940,13 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
@@ -882,7 +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 = "0800"
LastUpgradeVersion = "0810"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0800"
LastUpgradeVersion = "0810"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0800"
LastUpgradeVersion = "0810"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
+1 -1
View File
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.3.0</string>
<string>${BUNDLE_VERSION_STRING}</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
+1 -1
View File
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.3.0</string>
<string>${BUNDLE_VERSION_STRING}</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
+1 -1
View File
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.3.0</string>
<string>${BUNDLE_VERSION_STRING}</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
+247 -124
View File
@@ -1,22 +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]
[![Platform](https://img.shields.io/badge/Platform-iOS%2C%20macOS%2C%20tvOS-lightgray.svg)](#)
[![License][license-image]][license-url]
[![Platform](https://img.shields.io/badge/Platform-iOS%2C%20macOS-lightgray.svg)]()
[![release badge][release-image]][release-url]
[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/FileProvider.svg)](https://cocoapods.org/pods/FileProvider)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?)](https://github.com/Carthage/Carthage)
[![Build Status][travis-image]][travis-url]
[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/FileProvider.svg)](https://cocoapods.org/pods/FileProvider)
[![codebeat badge][codebeat-image]][codebeat-url]
<!---
[![codecov](https://codecov.io/gh/amosavian/FileProvider/branch/master/graph/badge.svg)](https://codecov.io/gh/amosavian/FileProvider)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
[![codecov](https://codecov.io/gh/amosavian/FileProvider/branch/master/graph/badge.svg)](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.
@@ -26,10 +27,17 @@ Local and WebDAV providers are fully tested and can be used in production enviro
- [x] **LocalFileProvider** a wrapper around `FileManager` with some additions like searching and reading a portion of file.
- [x] **WebDAVFileProvider** WebDAV protocol is defacto file transmission standard, replaced FTP.
- [x] **DropboxFileProvider** A wrapper around Dropbox Web API. For now it has limitation in uploading files up to 150MB.
- [ ] **SMBFileProvider** SMB2/3 introduced in 2006, which is a file and printer sharing protocol originated from Microsoft Windows and now is replacing AFP protocol on MacOS. I implemented data types and some basic functions but *main interface is not implemented yet!*. SMB1/CIFS is depericated and very tricky to be implemented
- [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
@@ -43,27 +51,48 @@ Legacy version is available in swift-2 branch
### Cocoapods / Carthage / Swift Package Manager
FileProvider supports both CocoaPods.
Add this line to your pods file:
pod "FileProvider"
```ruby
pod "FileProvider"
```
Or add this to Cartfile:
```
github "amosavian/FileProvider"
```
Or to use in Swift Package Manager add this line in `Dependencies`:
```swift
.Package(url: "https://github.com/amosavian/FileProvider.git", majorVersion: 0, minorVersion: 8)
```
### Manually
### Git
To have latest updates with ease, use this command on terminal to get a clone:
git clone https://github.com/amosavian/FileProvider FileProvider
```bash
git clone https://github.com/amosavian/FileProvider
```
You can update your library using this command in FileProvider folder:
git pull
```bash
git pull
```
if you have a git based project, use this command in your projects directory to add this project as a submodule to your project:
git submodule add https://github.com/amosavian/FileProvider FileProvider
```bash
git submodule add https://github.com/amosavian/FileProvider
```
Then you can do either:
### Manually
Copy Source folder to your project and Voila!
* Copy Source folder to your project and Voila!
* Drop FileProvider.xcodeproj to you Xcode workspace and add the framework to your Embeded Binaries in target.
## Usage
@@ -73,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 = URL(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 = URLCredential(user: "user", password: "pass", persistence: URLCredentialPersistence.Permanent)
let webdavProvider = WebDAVFileProvider(baseURL: NSURL(string: "http://www.example.com/dav")!, credential: credential)
``` swift
let credential = URLCredential(user: "user", password: "pass", persistence: .permanent)
let webdavProvider = WebDAVFileProvider(baseURL: URL(string: "http://www.example.com/dav")!, credential: credential)
```
* In case you want to connect non-secure servers for WebDAV (http) in iOS 9+ / macOS 10.11+ you should disable App Transport Security (ATS) according to [this guide.](https://gist.github.com/mlynch/284699d676fe9ed0abfa)
* 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 as FileProviderDelegate
}
```swift
override func viewDidLoad() {
documentsProvider.delegate = self as FileProviderDelegate
}
func fileproviderSucceed(_ fileProvider: FileProviderOperations, operation: FileOperation) {
switch operation {
case .copy(source: let source, destination: let dest):
print("\(source) copied to \(dest).")
case .remove(path: let path):
print("\(path) has been deleted.")
default:
break
}
}
func fileproviderFailed(_ fileProvider: FileProviderOperations, operation: FileOperation) {
switch operation {
case .copy(source: let source, destination: let dest):
print("copy of \(source) failed.")
case .remove(path: let path):
print("\(path) can't be deleted.")
default:
break
}
}
func fileproviderProgress(_ fileProvider: FileProviderOperations, operation: FileOperation, progress: Float) {
switch operation {
case .copy(source: let source, destination: let dest):
print("Copy\(source) to \(dest): \(progress * 100) completed.")
default:
break
}
func fileproviderSucceed(_ fileProvider: FileProviderOperations, operation: FileOperation) {
switch operation {
case .copy(source: let source, destination: let dest):
print("\(source) copied to \(dest).")
case .remove(path: let path):
print("\(path) has been deleted.")
default:
print("\(operation.actionDescription) from \(operation.source!) to \(operation.destination) succeed")
}
}
**Note:** `fileproviderProgress()` delegate method is not called by `LocalFileProvider`.
func fileproviderFailed(_ fileProvider: FileProviderOperations, operation: FileOperation) {
switch operation {
case .copy(source: let source, destination: let dest):
print("copy of \(source) failed.")
case .remove:
print("file can't be deleted.")
default:
print("\(operation.actionDescription) from \(operation.source!) to \(operation.destination) failed")
}
}
func fileproviderProgress(_ fileProvider: FileProviderOperations, operation: FileOperation, progress: Float) {
switch operation {
case .copy(source: let source, destination: let dest):
print("Copy\(source) to \(dest): \(progress * 100) completed.")
default:
break
}
}
```
**Note:** `fileproviderProgress()` delegate method is not called by `LocalFileProvider` currently.
It's recommended to use completion handlers for error handling or result processing.
@@ -147,9 +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.**
@@ -159,112 +209,183 @@ There is a `FileObject` class which holds file attributes like size and creation
For a single file:
documentsProvider.attributesOfItem(path: "/file.txt", completionHandler: {
(attributes: LocalFileObject?, error: ErrorType?) -> Void in
if let attributes = attributes {
print("File Size: \(attributes.size)")
print("Creation Date: \(attributes.createdDate)")
print("Modification Date: \(modifiedDate)")
print("Is Read Only: \(isReadOnly)")
}
})
```swift
documentsProvider.attributesOfItem(path: "/file.txt", completionHandler: {
attributes, error in
if let attributes = attributes {
print("File Size: \(attributes.size)")
print("Creation Date: \(attributes.creationDate)")
print("Modification Date: \(attributes.modifiedDate)")
print("Is Read Only: \(attributes.isReadOnly)")
}
})
```
To get list of files in a directory:
documentsProvider.contentsOfDirectory(path: "/", completionHandler: {
(contents: [LocalFileObject], error: ErrorType?) -> Void in
for file in contents {
print("Name: \(attributes.name)")
print("Size: \(attributes.size)")
print("Creation Date: \(attributes.createdDate)")
print("Modification Date: \(modifiedDate)")
}
})
```swift
documentsProvider.contentsOfDirectory(path: "/", completionHandler: {
contents, error in
for file in contents {
print("Name: \(attributes.name)")
print("Size: \(attributes.size)")
print("Creation Date: \(attributes.creationDate)")
print("Modification Date: \(attributes.modifiedDate)")
}
})
```
To get size of strage and used/free space:
func storageProperties(completionHandler: {(total: Int64, used: Int64) -> Void in
print("Total Storage Space: \(total)")
print("Used Space: \(used)")
print("Free Space: \(total - frees)")
})
```swift
func storageProperties(completionHandler: { total, used in
print("Total Storage Space: \(total)")
print("Used Space: \(used)")
print("Free Space: \(total - used)")
})
```
* if this function is unavailable on provider or an error has been occurred, total space will be reported "-1" and used space "0"
* if this function is unavailable on provider or an error has been occurred, total space will be reported `-1` and used space `0`
### Change current directory
documentsProvider.currentPath = "/New Folder"
// now path is ~/Documents/New Folder
```swift
documentsProvider.currentPath = "/New Folder"
// now path is ~/Documents/New Folder
```
You can then pass "" (empty string) to contentsOfDirectoryAtPath method to list files in current directory.
You can then pass "" (empty string) to `contentsOfDirectory` method to list files in current directory.
### Creating File and Folders
Creating new directory:
documentsProvider.create(folder: "new folder", at: "/", completionHandler: nil)
```swift
documentsProvider.create(folder: "new folder", at: "/", completionHandler: nil)
```
Creating new file from data stream:
Creating new file from data:
let data = "hello world!".data(encoding: String.encoding.utf8)
let file = FileObject(name: "old.txt", createdDate: Date(), modifiedDate: Date(), isHidden: false, isReadOnly: true)
documentsProvider.create(file: file, at: "/", contents: data, completionHandler: nil)
```swift
let data = "hello world!".data(encoding: .utf8)
documentsProvider.create(file: "newFile.txt", at: "/", contents: data, completionHandler: nil)
```
### Copy and Move/Rename Files
Copy file old.txt to new.txt in current path:
documentsProvider.copyItem(path: "new folder/old.txt", to: "new.txt", overwrite: false, completionHandler: nil)
```swift
documentsProvider.copyItem(path: "new folder/old.txt", to: "new.txt", overwrite: false, completionHandler: nil)
```
Move file old.txt to new.txt in current path:
documentsProvider.moveItem(path: "new folder/old.txt", to: "new.txt", overwrite: false, completionHandler: nil)
```swift
documentsProvider.moveItem(path: "new folder/old.txt", to: "new.txt", overwrite: false, completionHandler: nil)
```
**Note:** To have a consistent behavior, create intermediate directories first if necessary.
### Delete Files
documentsProvider.removeItem(path: "new.txt", completionHandler: nil)
```swift
documentsProvider.removeItem(path: "new.txt", completionHandler: nil)
```
***Caution:*** This method will delete directories with all it's content recursively.
***Caution:*** This method will delete directories with all it's contents recursively.
### Retrieve Content of File
### Fetching Contents of File
There is two method for this purpose, one of them loads entire file into NSData and another can load a portion of file.
documentsProvider.contents(path: "old.txt", completionHandler: {
(contents: Data?, error: ErrorType?) -> Void
if let contents = contents {
print(String(data: contents, encoding: String.encoding.utf8)) // "hello world!"
}
})
```swift
documentsProvider.contents(path: "old.txt", completionHandler: {
contents, error in
if let contents = contents {
print(String(data: contents, encoding: .utf8)) // "hello world!"
}
})
```
If you want to retrieve a portion of file you should can `contentsAtPath` method with offset and length arguments. Please note first byte of file has offset: 0.
If you want to retrieve a portion of file you can use `contents` method with offset and length arguments. Please note first byte of file has offset: 0.
documentsProvider.contents(path: "old.txt", offset: 2, length: 5, completionHandler: {
(contents: Data?, error: ErrorType?) -> Void
if let contents = contents {
print(String(data: contents, encoding: String.encoding.utf8)) // "llo w"
}
})
```swift
documentsProvider.contents(path: "old.txt", offset: 2, length: 5, completionHandler: {
contents, error in
if let contents = contents {
print(String(data: contents, encoding: .utf8)) // "llo w"
}
})
```
### Write Data To Files
let data = "What's up Newyork!".data(encoding: String.encoding.utf8)
documentsProvider.writeContents(path: "old.txt", content: data, atomically: true, completionHandler: nil)
```swift
let data = "What's up Newyork!".data(encoding: .utf8)
documentsProvider.writeContents(path: "old.txt", content: data, atomically: true, completionHandler: nil)
```
### Operation Handle
Creating/Copying/Deleting functions return a `OperationHandle` for remote operations. It provides operation type, progress and a `.cancel()` method which allows you to cancel operation in midst.
It's not supported by native `(NS)FileManager` so `LocalFileProvider`, but this functionality will be added to future `PosixFileProvider` class.
### Monitoring FIle Changes
You can monitor updates in some file system (Local and SMB2), there is three methods in supporting provider you can use to register a handler, to unregister and to check whether it's being monitored or not. It's useful to find out when new files added or removed from directory and update user interface. The handler will be dispatched to main threads to avoid UI bugs with a 0.25 sec delay.
documentsProvider.registerNotifcation(path: provider.currentPath)
{
// calling functions to update UI
}
```swift
// to register a new notification handler
documentsProvider.registerNotifcation(path: provider.currentPath)
{
// calling functions to update UI
}
// To discontinue monitoring folders:
documentsProvider.unregisterNotifcation(path: provider.currentPath)
// To discontinue monitoring folders:
documentsProvider.unregisterNotifcation(path: provider.currentPath)
```
* **Please note** in LocalFileProvider it will also monitor changes in subfolders. This behaviour can varies according to file system specification.
### 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.
@@ -291,4 +412,6 @@ Distributed under the MIT license. See `LICENSE` for more information.
[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/amosavian/FileProvider/master.svg
[travis-url]: https://travis-ci.org/amosavian/FileProvider
[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
+480
View File
@@ -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
}
}
+291 -136
View File
@@ -13,36 +13,57 @@ import CoreGraphics
// Because this class uses NSURLSession, it's necessary to disable App Transport Security
// in case of using this class with unencrypted HTTP connection.
open class DropboxFileProvider: NSObject, FileProviderBasic {
open let type: String = "WebDAV"
open let isPathRelative: Bool = true
open class DropboxFileProvider: FileProviderBasicRemote {
open static let type: String = "DropBox"
open let isPathRelative: Bool
open let baseURL: URL?
open var currentPath: String = ""
open var dispatch_queue: DispatchQueue {
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.")
}
}
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?
internal var session: URLSession {
public var session: URLSession {
if _session == nil {
self.sessionDelegate = SessionDelegate(fileProvider: self, credential: credential)
let queue = OperationQueue()
//queue.underlyingQueue = dispatch_queue
_session = URLSession(configuration: URLSessionConfiguration.default, delegate: sessionDelegate as URLSessionDelegate?, delegateQueue: queue)
let config = URLSessionConfiguration.default
config.urlCache = cache
config.requestCachePolicy = .returnCacheDataElseLoad
_session = URLSession(configuration: config, delegate: sessionDelegate as URLSessionDelegate?, delegateQueue: self.operation_queue)
}
return _session!
}
public init? (credential: URLCredential?) {
public init? (credential: URLCredential?, cache: URLCache? = nil) {
self.baseURL = nil
dispatch_queue = DispatchQueue(label: "FileProvider.\(type)", attributes: DispatchQueue.Attributes.concurrent)
//let url = baseURL.uw_absoluteString
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 {
@@ -56,45 +77,41 @@ open class DropboxFileProvider: NSObject, FileProviderBasic {
}
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
let url = URL(string: "https://api.dropboxapi.com/2/files/get_metadata")!
let url = URL(string: "files/get_metadata", relativeTo: apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let requestDictionary = ["path": correctPath(path)! as NSString]
request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: String.Encoding.utf8)
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 {
defer {
self.delegateNotify(FileOperation.create(path: path), error: error)
}
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: String(data: data ?? Data(), encoding: String.Encoding.utf8)) : nil
if let data = data, let jsonStr = String(data: data, encoding: String.Encoding.utf8), let json = jsonToDictionary(jsonStr), let file = self.mapToFileObject(json) {
completionHandler(file, dbError)
return
dbError = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8)) : nil
if let data = data, let jsonStr = String(data: data, encoding: .utf8), let json = jsonToDictionary(jsonStr), let file = DropboxFileObject(json: json) {
fileObject = file
}
completionHandler(nil, dbError)
return
}
completionHandler(nil, error)
completionHandler(fileObject, dbError ?? error)
})
task.resume()
}
open func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void)) {
let url = URL(string: "https://api.dropboxapi.com/2/users/get_space_usage")!
let url = URL(string: "users/get_space_usage", relativeTo: apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
if let data = data, let jsonStr = String(data: data, encoding: String.Encoding.utf8), let json = jsonToDictionary(jsonStr) {
let totalSize = ((json["allocation"] as? NSDictionary)?["allocated"] as? NSNumber)?.int64Value ?? -1
let usedSize = (json["used"] as? NSNumber)?.int64Value ?? 0
completionHandler(totalSize, usedSize)
return
var totalSize: Int64 = -1
var usedSize: Int64 = 0
if let data = data, let jsonStr = String(data: data, encoding: .utf8), let json = jsonToDictionary(jsonStr) {
totalSize = ((json["allocation"] as? NSDictionary)?["allocated"] as? NSNumber)?.int64Value ?? -1
usedSize = (json["used"] as? NSNumber)?.int64Value ?? 0
}
completionHandler(-1, 0)
completionHandler(totalSize, usedSize)
})
task.resume()
}
@@ -104,99 +121,98 @@ open class DropboxFileProvider: NSObject, FileProviderBasic {
extension DropboxFileProvider: FileProviderOperations {
public func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) {
public func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let path = (atPath as NSString).appendingPathComponent(folderName) + "/"
doOperation(.create(path: path), completionHandler: completionHandler)
return doOperation(.create(path: path), completionHandler: completionHandler)
}
public func create(file fileAttribs: FileObject, at path: String, contents data: Data?, completionHandler: SimpleCompletionHandler) {
self.writeContents(path: path, contents: data ?? Data(), completionHandler: completionHandler)
public func create(file fileName: String, at path: String, contents data: Data?, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let filePath = (path as NSString).appendingPathComponent(fileName)
return self.writeContents(path: filePath, contents: data ?? Data(), completionHandler: completionHandler)
}
public func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
doOperation(.move(source: path, destination: toPath), completionHandler: completionHandler)
public func moveItem(path: String, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
return doOperation(.move(source: path, destination: toPath), completionHandler: completionHandler)
}
public func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
doOperation(.copy(source: path, destination: toPath), completionHandler: completionHandler)
public func copyItem(path: String, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
return doOperation(.copy(source: path, destination: toPath), completionHandler: completionHandler)
}
public func removeItem(path: String, completionHandler: SimpleCompletionHandler) {
doOperation(.remove(path: path), completionHandler: completionHandler)
public func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
return doOperation(.remove(path: path), completionHandler: completionHandler)
}
fileprivate func doOperation(_ operation: FileOperation, completionHandler: SimpleCompletionHandler) {
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
}
var request = URLRequest(url: URL(string: url)!)
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) as NSString?
requestDictionary["from_path"] = correctPath(fromPath) as NSString?
requestDictionary["to_path"] = correctPath(toPath) as NSString?
request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: String.Encoding.utf8)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
if let response = response as? HTTPURLResponse {
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path ?? fromPath ?? "", errorDescription: String(data: data ?? Data(), encoding: String.Encoding.utf8)) : nil
defer {
self.delegateNotify(operation, error: error ?? dbError)
}
/*if let data = data, let jsonStr = String(data: data, encoding: NSUTF8StringEncoding) {
let json = self.jsonToDictionary(jsonStr)
}*/
completionHandler?(dbError)
return
}
completionHandler?(error)
})
task.resume()
}
public func copyItem(localFile: URL, to toPath: String, completionHandler: SimpleCompletionHandler) {
guard let data = try? Data(contentsOf: localFile) else {
let error = throwError(localFile.uw_absoluteString, code: URLError.fileDoesNotExist as FoundationErrorEnum)
completionHandler?(error)
return
if let dest = correctPath(destPath) as NSString? {
requestDictionary["from_path"] = correctPath(sourcePath) as NSString?
requestDictionary["to_path"] = dest
} else {
requestDictionary["path"] = correctPath(sourcePath) as NSString?
}
upload_simple(toPath, data: data, overwrite: true, operation: .copy(source: localFile.uw_absoluteString, destination: toPath), completionHandler: completionHandler)
request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: .utf8)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var dbError: FileProviderDropboxError?
if let response = response as? HTTPURLResponse, response.statusCode >= 300, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) {
dbError = FileProviderDropboxError(code: code, path: sourcePath, errorDescription: String(data: data ?? Data(), encoding: .utf8))
}
completionHandler?(dbError ?? error)
self.delegateNotify(operation, error: dbError ?? error)
})
task.taskDescription = operation.json
task.resume()
return RemoteOperationHandle(operationType: operation, tasks: [task])
}
public func copyItem(path: String, toLocalURL destURL: URL, completionHandler: SimpleCompletionHandler) {
let url = URL(string: "https://content.dropboxapi.com/2/files/download")!
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: "files/download", relativeTo: contentURL)!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let requestDictionary = ["path": path as NSString]
request.setValue(dictionaryToJSON(requestDictionary), forHTTPHeaderField: "Dropbox-API-Arg")
let requestDictionary: [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 dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: nil) : nil
let errorData : Data? = nil //Data(contentsOf:cacheURL) // TODO: Figure out how to get error response data for the error description
let dbError : FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: String(data: errorData ?? Data(), encoding: .utf8)) : nil
completionHandler?(dbError ?? error)
return
}
@@ -207,43 +223,46 @@ extension DropboxFileProvider: FileProviderOperations {
completionHandler?(e)
}
})
task.taskDescription = dictionaryToJSON(["type": "Copy" as NSString, "source": path as NSString, "dest": destURL.uw_absoluteString as NSString])
task.taskDescription = opType.json
task.resume()
return RemoteOperationHandle(operationType: opType, tasks: [task])
}
}
extension DropboxFileProvider: FileProviderReadWrite {
public func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) {
self.contents(path: path, offset: 0, length: -1, completionHandler: completionHandler)
}
public func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) {
let url = URL(string: "https://content.dropboxapi.com/2/files/download")!
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")
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]
request.setValue(dictionaryToJSON(requestDictionary as [String : AnyObject]), forHTTPHeaderField: "Dropbox-API-Arg")
let task = session.dataTask(with: request, completionHandler: { (datam, response, error) in
guard let data = datam, let httpResponse = response as? HTTPURLResponse , httpResponse.statusCode < 300 else {
let code = FileProviderHTTPErrorCode(rawValue: (response as? HTTPURLResponse)?.statusCode ?? -1)
let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: String(data: datam ?? Data(), encoding: String.Encoding.utf8)) : nil
completionHandler(nil, dbError ?? error)
return
let requestDictionary: [String: AnyObject] = ["path": correctPath(path)! as NSString]
request.setValue(dictionaryToJSON(requestDictionary), forHTTPHeaderField: "Dropbox-API-Arg")
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var dbError: FileProviderDropboxError?
if let httpResponse = response as? HTTPURLResponse , httpResponse.statusCode >= 300, let code = FileProviderHTTPErrorCode(rawValue: httpResponse.statusCode) {
dbError = FileProviderDropboxError(code: code, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8))
}
completionHandler(data, error)
let filedata = dbError ?? error == nil ? data : nil
completionHandler(filedata, dbError ?? error)
})
task.taskDescription = opType.json
task.resume()
return RemoteOperationHandle(operationType: opType, tasks: [task])
}
public func writeContents(path: String, contents data: Data, atomically: Bool = false, completionHandler: SimpleCompletionHandler) {
public func writeContents(path: String, contents data: Data, atomically: Bool, 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
upload_simple(path, data: data, overwrite: true, operation: .modify(path: path), completionHandler: completionHandler)
return upload_simple(path, data: data, overwrite: overwrite, operation: opType, completionHandler: completionHandler)
}
public func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
@@ -269,7 +288,93 @@ extension DropboxFileProvider: FileProviderReadWrite {
NotImplemented()
}
// TODO: Implement /copy_reference, /get_temporary_link, /save_url, /get_account & /get_current_account
// TODO: Implement /copy_reference, /get_account & /get_current_account
}
extension DropboxFileProvider {
@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)
}
}
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")
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)
}
if let attribDic = json["metadata"] as? [String: AnyObject] {
fileObject = DropboxFileObject(json: attribDic)
}
}
}
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()
}
}
extension DropboxFileProvider: ExtendedFileProvider {
@@ -277,42 +382,55 @@ extension DropboxFileProvider: ExtendedFileProvider {
switch (path as NSString).pathExtension.lowercased() {
case "jpg", "jpeg", "gif", "bmp", "png", "tif", "tiff":
return true
/*case "doc", "docx", "docm", "xls", "xlsx", "xlsm":
case "doc", "docx", "docm", "xls", "xlsx", "xlsm":
return true
case "ppt", "pps", "ppsx", "ppsm", "pptx", "pptm":
return true
case "rtf":
return true*/
return true
default:
return false
}
}
public func propertiesOfFileSupported(path: String) -> Bool {
return false
let fileExt = (path as NSString).pathExtension.lowercased()
switch fileExt {
case "jpg", "jpeg", "bmp", "gif", "png", "tif", "tiff":
return true
/*case "mp3", "aac", "m4a":
return true*/
case "mp4", "mpg", "3gp", "mov", "avi":
return true
default:
return false
}
}
public func thumbnailOfFile(path: String, dimension: CGSize, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void)) {
/// Default value for dimension is 64x64, according to Dropbox documentation
public func thumbnailOfFile(path: String, dimension: CGSize?, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void)) {
let url: URL
switch (path as NSString).pathExtension.lowercased() {
case "jpg", "jpeg", "gif", "bmp", "png", "tif", "tiff":
url = URL(string: "https://content.dropboxapi.com/2/files/get_thumbnail")!
/*case "doc", "docx", "docm", "xls", "xlsx", "xlsm":
url = URL(string: "files/get_thumbnail", relativeTo: contentURL)!
case "doc", "docx", "docm", "xls", "xlsx", "xlsm":
fallthrough
case "ppt", "pps", "ppsx", "ppsm", "pptx", "pptm":
fallthrough
case "rtf":
url = NSURL(string: "https://content.dropboxapi.com/2/files/get_preview")!*/
url = URL(string: "files/get_preview", relativeTo: contentURL)!
default:
return
}
var request = URLRequest(url: url)
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
var requestDictionary = ["path": path as NSString]
var requestDictionary: [String: AnyObject] = ["path": path as NSString]
requestDictionary["format"] = "jpeg" as NSString
requestDictionary["size"] = "w\(Int(dimension.width))h\(Int(dimension.height))" as NSString
if let dimension = dimension {
requestDictionary["size"] = "w\(Int(dimension.width))h\(Int(dimension.height))" as NSString
}
request.setValue(dictionaryToJSON(requestDictionary), forHTTPHeaderField: "Dropbox-API-Arg")
self.session.dataTask(with: request, completionHandler: { (data, response, error) in
let task = self.session.dataTask(with: request, completionHandler: { (data, response, error) in
var image: ImageClass? = nil
if let r = response as? HTTPURLResponse, let result = r.allHeaderFields["Dropbox-API-Result"] as? String, let jsonResult = jsonToDictionary(result) {
if jsonResult["error"] != nil {
@@ -320,15 +438,52 @@ extension DropboxFileProvider: ExtendedFileProvider {
}
}
if let data = data {
image = ImageClass(data: data)
if DropboxFileProvider.dataIsPDF(data) {
image = DropboxFileProvider.convertToImage(pdfData: data)
} else if let contentType = (response as? HTTPURLResponse)?.allHeaderFields["Content-Type"] as? String, contentType.contains("text/html") {
// TODO: Implement converting html returned type of get_preview to image
} else {
image = ImageClass(data: data)
}
}
completionHandler(image, error)
})
})
task.resume()
}
public func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String : Any], _ keys: [String], _ error: Error?) -> Void)) {
NotImplemented()
let url = URL(string: "files/get_metadata", relativeTo: apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let requestDictionary: [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 {}
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
}
}
+142 -75
View File
@@ -2,8 +2,8 @@
// DropboxHelper.swift
// FileProvider
//
// Created by Amir Abbas Mousavian on 5/18/95.
//
// Created by Amir Abbas Mousavian.
// Copyright © 2016 Mousavian. Distributed under MIT license.
//
import Foundation
@@ -19,30 +19,66 @@ public struct FileProviderDropboxError: Error, CustomStringConvertible {
}
public final class DropboxFileObject: FileObject {
public let serverTime: Date?
public let id: String?
public let rev: String?
// codebeat:disable[ARITY]
public init(name: String, path: String, size: Int64 = -1, serverTime: Date? = nil, modifiedDate: Date? = nil, fileType: FileType = .regular, isHidden: Bool = false, isReadOnly: Bool = false, id: String? = nil, rev: String? = nil) {
self.serverTime = serverTime
self.id = id
self.rev = rev
super.init(absoluteURL: URL(string: path), name: name, path: path, size: size, createdDate: nil, modifiedDate: modifiedDate, fileType: fileType, isHidden: isHidden, isReadOnly: isReadOnly)
internal init(name: String, path: String) {
super.init(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:enable[ARITY]
}
// codebeat:disable[ARITY]
// codebeat:disable[ARITY]
internal extension DropboxFileProvider {
func list(_ path: String, cursor: String? = nil, prevContents: [DropboxFileObject] = [], recursive: Bool = false, completionHandler: @escaping ((_ contents: [FileObject], _ cursor: String?, _ error: Error?) -> Void)) {
var requestDictionary = [String: AnyObject]()
let url: URL
if let cursor = cursor {
url = URL(string: "https://api.dropboxapi.com/2/files/list_folder/continue")!
url = URL(string: "files/list_folder/continue", relativeTo: apiURL)!
requestDictionary["cursor"] = cursor as NSString?
} else {
url = URL(string: "https://api.dropboxapi.com/2/files/list_folder")!
url = URL(string: "files/list_folder", relativeTo: apiURL)!
requestDictionary["path"] = correctPath(path) as NSString?
requestDictionary["recursive"] = recursive as NSNumber?
}
@@ -50,18 +86,18 @@ internal extension DropboxFileProvider {
request.httpMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: String.Encoding.utf8)
request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: .utf8)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var responseError: FileProviderDropboxError?
var files = prevContents
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderDropboxError(code: rCode, path: path, errorDescription: String(data: data ?? Data(), encoding: String.Encoding.utf8))
responseError = FileProviderDropboxError(code: rCode, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8))
}
if let data = data, let jsonStr = String(data: data, encoding: String.Encoding.utf8) {
if let data = data, let jsonStr = String(data: data, encoding: .utf8) {
let json = jsonToDictionary(jsonStr)
if let entries = json?["entries"] as? [AnyObject] , entries.count > 0 {
var files = prevContents
for entry in entries {
if let entry = entry as? [String: AnyObject], let file = self.mapToFileObject(entry) {
if let entry = entry as? [String: AnyObject], let file = DropboxFileObject(json: entry) {
files.append(file)
}
}
@@ -69,27 +105,29 @@ internal extension DropboxFileProvider {
let hasmore = (json?["has_more"] as? NSNumber)?.boolValue ?? false
if hasmore {
self.list(path, cursor: ncursor, prevContents: files, completionHandler: completionHandler)
} else {
completionHandler(files, ncursor, responseError ?? error)
return
}
return
}
}
completionHandler([], nil, responseError ?? error)
})
completionHandler(files, nil, responseError ?? error)
})
task.taskDescription = FileOperationType.fetch(path: path).json
task.resume()
}
func upload_simple(_ targetPath: String, data: Data, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperation, completionHandler: SimpleCompletionHandler) {
assert(data.count < 150*1024*1024, "Maximum size of allowed size to upload is 150MB")
func upload_simple(_ targetPath: String, data: Data, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
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: "https://content.dropboxapi.com/2/files/upload")!
url = URL(string: "files/upload", relativeTo: contentURL)!
requestDictionary["path"] = correctPath(targetPath) as NSString?
requestDictionary["mode"] = (overwrite ? "overwrite" : "add") as NSString
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssz"
requestDictionary["client_modified"] = dateFormatter.string(from: modifiedDate) as NSString
requestDictionary["client_modified"] = rfc3339utc(of: modifiedDate) as NSString
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
@@ -99,34 +137,50 @@ internal extension DropboxFileProvider {
let task = session.uploadTask(with: request, from: data, completionHandler: { (data, response, error) in
var responseError: FileProviderDropboxError?
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderDropboxError(code: rCode, path: targetPath, errorDescription: String(data: data ?? Data(), encoding: String.Encoding.utf8))
}
defer {
self.delegateNotify(.create(path: targetPath), error: responseError ?? error)
responseError = FileProviderDropboxError(code: rCode, path: targetPath, errorDescription: String(data: data ?? Data(), encoding: .utf8))
}
completionHandler?(responseError ?? error)
})
var dic: [String: AnyObject] = ["type": operation.description as NSString]
switch operation {
case .create(path: let s):
dic["source"] = s as NSString
case .copy(source: let s, destination: let d):
dic["source"] = s as NSString
dic["dest"] = d as NSString
case .modify(path: let s):
dic["source"] = s as NSString
case .move(source: let s, destination: let d):
dic["source"] = s as NSString
dic["dest"] = d as NSString
default:
break
}
task.taskDescription = dictionaryToJSON(dic)
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: "https://api.dropboxapi.com/2/files/search")!
let url = URL(string: "files/search", relativeTo: apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
@@ -135,17 +189,17 @@ internal extension DropboxFileProvider {
requestDictionary["query"] = query as NSString
requestDictionary["start"] = start as NSNumber
requestDictionary["max_results"] = maxResultPerPage as NSNumber
request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: String.Encoding.utf8)
request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: .utf8)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var responseError: FileProviderDropboxError?
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderDropboxError(code: rCode, path: startPath, errorDescription: String(data: data ?? Data(), encoding: String.Encoding.utf8))
responseError = FileProviderDropboxError(code: rCode, path: startPath, errorDescription: String(data: data ?? Data(), encoding: .utf8))
}
if let data = data, let jsonStr = String(data: data, encoding: String.Encoding.utf8) {
if let data = data, let jsonStr = String(data: data, encoding: .utf8) {
let json = jsonToDictionary(jsonStr)
if let entries = json?["matches"] as? [AnyObject] , entries.count > 0 {
for entry in entries {
if let entry = entry as? [String: AnyObject], let file = self.mapToFileObject(entry) {
if let entry = entry as? [String: AnyObject], let file = DropboxFileObject(json: entry) {
foundItem(file)
}
}
@@ -167,25 +221,38 @@ internal extension DropboxFileProvider {
// codebeat:enable[ARITY]
internal extension DropboxFileProvider {
func mapToFileObject(_ jsonStr: String) -> DropboxFileObject? {
guard let json = jsonToDictionary(jsonStr) else { return nil }
return self.mapToFileObject(json)
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 mapToFileObject(_ json: [String: AnyObject]) -> DropboxFileObject? {
guard let name = json["name"] as? String else { return nil }
guard let path = json["path_display"] as? String else { return nil }
let size = (json["size"] as? NSNumber)?.int64Value ?? -1
let serverTime = resolve(dateString: json["server_modified"] as? String ?? "")
let modifiedDate = resolve(dateString: json["client_modified"] as? String ?? "")
let isDirectory = (json[".tag"] as? String) == "folder"
let isReadonly = (json["sharing_info"]?["read_only"] as? NSNumber)?.boolValue ?? false
let id = json["id"] as? String
let rev = json["id"] as? String
return DropboxFileObject(name: name, path: path, size: size, serverTime: serverTime, modifiedDate: modifiedDate, fileType: isDirectory ? .directory : .regular, isReadOnly: isReadonly, id: id, rev: rev)
}
func delegateNotify(_ operation: FileOperation, error: Error?) {
func delegateNotify(_ operation: FileOperationType, error: Error?) {
DispatchQueue.main.async(execute: {
if error == nil {
self.delegate?.fileproviderSucceed(self, operation: operation)
+443
View File
@@ -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)
}
+8 -22
View File
@@ -401,8 +401,8 @@ open class FPSStreamTask: URLSessionTask, StreamDelegate {
if #available(iOS 9.0, OSX 10.11, *) {
_underlyingTask!.startSecureConnection()
} else {
inputStream!.setProperty(StreamSocketSecurityLevel.negotiatedSSL.rawValue, forKey: Stream.PropertyKey.socketSecurityLevelKey)
outputStream!.setProperty(StreamSocketSecurityLevel.negotiatedSSL.rawValue, forKey: Stream.PropertyKey.socketSecurityLevelKey)
inputStream!.setProperty(StreamSocketSecurityLevel.negotiatedSSL.rawValue, forKey: .socketSecurityLevelKey)
outputStream!.setProperty(StreamSocketSecurityLevel.negotiatedSSL.rawValue, forKey: .socketSecurityLevelKey)
}
}
@@ -414,8 +414,8 @@ open class FPSStreamTask: URLSessionTask, StreamDelegate {
if #available(iOS 9.0, OSX 10.11, *) {
_underlyingTask!.stopSecureConnection()
} else {
inputStream!.setProperty(StreamSocketSecurityLevel.none.rawValue, forKey: Stream.PropertyKey.socketSecurityLevelKey)
outputStream!.setProperty(StreamSocketSecurityLevel.none.rawValue, forKey: Stream.PropertyKey.socketSecurityLevelKey)
inputStream!.setProperty(StreamSocketSecurityLevel.none.rawValue, forKey: .socketSecurityLevelKey)
outputStream!.setProperty(StreamSocketSecurityLevel.none.rawValue, forKey: .socketSecurityLevelKey)
}
}
@@ -503,21 +503,7 @@ public protocol FPSStreamDelegate : URLSessionTaskDelegate {
@objc optional func urlSession(_ session: URLSession, streamTask: FPSStreamTask, didBecome inputStream: InputStream, outputStream: OutputStream)
}
private let ports = ["http": 80,
"https": 443,
"smb": 445,
"ftp": 21,
"sftp": 22,
"sftp": 2121,
"telnet": 23,
"pop": 110,
"smtp": 25,
"imap": 143]
private let securePorts = ["https": 443,
"smb": 445,
"sftp": 22,
"sftp": 2121,
"telnet": 992,
"pop": 995,
"smtp": 465,
"imap": 993]
private let ports: [String: Int] = ["http": 80, "https": 443, "smb": 445,"ftp": 21,"ftps": 22, "sftp": 2121,
"telnet": 23, "pop": 110, "smtp": 25, "imap": 143]
private let securePorts: [String: Int] = ["https": 443, "smb": 445, "ftps": 22, "sftp": 2121,
"telnet": 992, "pop": 995, "smtp": 465, "imap": 993]
+308
View File
@@ -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
}
+3
View File
@@ -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.
+366 -197
View File
@@ -10,176 +10,240 @@ import Foundation
#if os(iOS) || os(tvOS)
import UIKit
public typealias ImageClass = UIImage
#elseif os(OSX)
#elseif os(macOS)
import Cocoa
public typealias ImageClass = NSImage
#endif
public enum FileType: String {
case directory
case regular
case symbolicLink
case socket
case characterSpecial
case blockSpecial
case namedPipe
case unknown
public init(urlResourceTypeValue: URLFileResourceType) {
switch urlResourceTypeValue {
case URLFileResourceType.namedPipe: self = .namedPipe
case URLFileResourceType.characterSpecial: self = .characterSpecial
case URLFileResourceType.directory: self = .directory
case URLFileResourceType.blockSpecial: self = .blockSpecial
case URLFileResourceType.regular: self = .regular
case URLFileResourceType.symbolicLink: self = .symbolicLink
case URLFileResourceType.socket: self = .socket
case URLFileResourceType.unknown: self = .unknown
default: self = .unknown
}
}
public init(fileTypeValue: FileAttributeType) {
switch fileTypeValue {
case FileAttributeType.typeCharacterSpecial: self = .characterSpecial
case FileAttributeType.typeDirectory: self = .directory
case FileAttributeType.typeBlockSpecial: self = .blockSpecial
case FileAttributeType.typeRegular: self = .regular
case FileAttributeType.typeSymbolicLink: self = .symbolicLink
case FileAttributeType.typeSocket: self = .socket
case FileAttributeType.typeUnknown: self = .unknown
default: self = .unknown
}
}
}
public protocol FoundationErrorEnum {
init? (rawValue: Int)
var rawValue: Int { get }
}
extension URLError.Code: FoundationErrorEnum {}
extension CocoaError.Code: FoundationErrorEnum {}
open class FileObject {
open let absoluteURL: URL?
open let name: String
open let path: String
open let size: Int64
open let createdDate: Date?
open let modifiedDate: Date?
open let fileType: FileType
open let isHidden: Bool
open let isReadOnly: Bool
public init(absoluteURL: URL? = nil, name: String, path: String, size: Int64 = -1, createdDate: Date? = nil, modifiedDate: Date? = nil, fileType: FileType = .regular, isHidden: Bool = false, isReadOnly: Bool = false) {
self.absoluteURL = absoluteURL
self.name = name
self.path = path
self.size = size
self.createdDate = createdDate
self.modifiedDate = modifiedDate
self.fileType = fileType
self.isHidden = isHidden
self.isReadOnly = isReadOnly
}
open var isDirectory: Bool {
return self.fileType == .directory
}
open var isSymLink: Bool {
return self.fileType == .symbolicLink
}
}
public typealias SimpleCompletionHandler = ((_ error: Error?) -> Void)?
public protocol FileProviderBasic: class {
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 }
/**
*
*/
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 }
func create(folder: String, at: String, completionHandler: SimpleCompletionHandler)
func create(file: FileObject, at: String, contents data: Data?, completionHandler: SimpleCompletionHandler)
func moveItem(path: String, to: String, overwrite: Bool, completionHandler: SimpleCompletionHandler)
func copyItem(path: String, to: String, overwrite: Bool, completionHandler: SimpleCompletionHandler)
func removeItem(path: String, completionHandler: SimpleCompletionHandler)
@discardableResult
func create(folder: String, at: String, completionHandler: SimpleCompletionHandler) -> OperationHandle?
@discardableResult
func create(file: String, at: String, contents data: Data?, completionHandler: SimpleCompletionHandler) -> OperationHandle?
@discardableResult
func moveItem(path: String, to: String, 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)
}
func copyItem(localFile: URL, to: String, completionHandler: SimpleCompletionHandler)
func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler)
@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 {
func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void))
func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void))
func writeContents(path: String, contents: Data, atomically: Bool, completionHandler: SimpleCompletionHandler)
@discardableResult
func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle?
@discardableResult
func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle?
@discardableResult
func writeContents(path: String, contents: Data, 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 {
public protocol FileProvider: FileProviderBasic, FileProviderOperations, FileProviderReadWrite, NSCopying {
}
internal let pathTrimSet = CharacterSet(charactersIn: " /")
extension FileProviderBasic {
public var bareCurrentPath: String {
return currentPath.trimmingCharacters(in: CharacterSet(charactersIn: ". /"))
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 {
let rpath: String
return url(of: path).absoluteURL
}
public func url(of path: String? = nil) -> URL {
var rpath: String
if let path = path {
rpath = path
} else {
rpath = self.currentPath
}
if isPathRelative, let baseURL = baseURL {
if rpath.hasPrefix("/") && baseURL.uw_absoluteString.hasSuffix("/") {
var npath = rpath
npath.remove(at: npath.startIndex)
return baseURL.uw_URLByAppendingPathComponent(npath)
} else {
return baseURL.uw_URLByAppendingPathComponent(rpath)
rpath = rpath.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? rpath
if let baseURL = baseURL {
if isPathRelative && rpath.hasPrefix("/") {
rpath.remove(at: rpath.startIndex)
}
return URL(string: rpath, relativeTo: baseURL) ?? baseURL
} else {
return URL(fileURLWithPath: rpath).standardizedFileURL
return URL(string: rpath)!
}
}
public func relativePathOf(url: URL) -> String {
guard let baseURL = self.baseURL else { return url.uw_absoluteString }
return url.standardizedFileURL.uw_absoluteString.replacingOccurrences(of: baseURL.uw_absoluteString, with: "/").removingPercentEncoding!
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.characters.index(before: p.endIndex))
p.remove(at: p.index(before:p.endIndex))
}
return p
}
@@ -205,9 +269,9 @@ extension FileProviderBasic {
}
var i = number ?? 2
let similiar = contents.map {
$0.absoluteURL?.lastPathComponent ?? $0.name
$0.url?.lastPathComponent ?? $0.name
}.filter {
$0.hasPrefix(result) && (!fileExt.isEmpty && $0.hasSuffix("." + fileExt))
$0.hasPrefix(result)
}
while similiar.contains(result + (!fileExt.isEmpty ? "." + fileExt : "")) {
result = "\(bareFileName) \(i)"
@@ -221,7 +285,7 @@ extension FileProviderBasic {
}
internal func throwError(_ path: String, code: FoundationErrorEnum) -> NSError {
let fileURL = self.absoluteURL(path)
let fileURL = self.url(of: path)
let domain: String
switch code {
case is URLError:
@@ -229,129 +293,234 @@ extension FileProviderBasic {
default:
domain = NSCocoaErrorDomain
}
return NSError(domain: domain, code: code.rawValue, userInfo: [NSURLErrorFailingURLErrorKey: fileURL, NSURLErrorFailingURLStringErrorKey: fileURL.uw_absoluteString])
return NSError(domain: domain, code: code.rawValue, userInfo: [NSURLErrorFailingURLErrorKey: fileURL, NSURLErrorFailingURLStringErrorKey: fileURL.absoluteString])
}
internal func NotImplemented() {
assert(false, "method not implemented")
}
internal func resolve(dateString: String) -> Date? {
let dateFor: DateFormatter = DateFormatter()
dateFor.locale = Locale(identifier: "en_US")
dateFor.dateFormat = "EEE',' dd' 'MMM' 'yyyy HH':'mm':'ss zzz"
if let rfc1123 = dateFor.date(from: dateString) {
return rfc1123
}
dateFor.dateFormat = "EEEE',' dd'-'MMM'-'yy HH':'mm':'ss z"
if let rfc850 = dateFor.date(from: dateString) {
return rfc850
}
dateFor.dateFormat = "EEE MMM d HH':'mm':'ss yyyy"
if let asctime = dateFor.date(from: dateString) {
return asctime
}
dateFor.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssz"
if let isotime = dateFor.date(from: dateString) {
return isotime
}
//self.init()
return nil
internal func NotImplemented(_ fn: String = #function, file: StaticString = #file) {
assert(false, "\(fn) method is not yet implemented. \(file)")
}
}
public protocol ExtendedFileProvider: FileProvider {
public protocol ExtendedFileProvider: FileProviderBasic {
func thumbnailOfFileSupported(path: String) -> Bool
func propertiesOfFileSupported(path: String) -> Bool
func thumbnailOfFile(path: String, dimension: CGSize, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void))
func thumbnailOfFile(path: String, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void))
func thumbnailOfFile(path: String, dimension: CGSize?, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void))
func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String: Any], _ keys: [String], _ error: Error?) -> Void))
}
public enum FileOperation: CustomStringConvertible {
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(path: _): return "Create"
case .copy(source: _, destination: _): return "Copy"
case .move(source: _, destination: _): return "Move"
case .modify(path: _): return "Modify"
case .remove(path: _): return "Remove"
case .link(link: _, target: _): return "Link"
case .create: return "Create"
case .copy: return "Copy"
case .move: return "Move"
case .modify: return "Modify"
case .remove: return "Remove"
case .link: return "Link"
case .fetch: return "Fetch"
}
}
internal var actionDescription: String {
switch self {
case .create(path: _): return "Creating"
case .copy(source: _, destination: _): return "Copying"
case .move(source: _, destination: _): return "Moving"
case .modify(path: _): return "Modifying"
case .remove(path: _): return "Removing"
case .link(link: _, target: _): return "Linking"
}
public var actionDescription: String {
return description.trimmingCharacters(in: CharacterSet(charactersIn: "e")) + "ing"
}
public var source: String? {
guard let reflect = Mirror(reflecting: self).children.first?.value else { return nil }
let mirror = Mirror(reflecting: reflect)
return reflect as? String ?? mirror.children.first?.value as? String
}
public var destination: String? {
guard let reflect = Mirror(reflecting: self).children.first?.value else { return nil }
let mirror = Mirror(reflecting: reflect)
return mirror.children.dropFirst().first?.value as? String
}
internal var json: String? {
var dictionary: [String: AnyObject] = ["type": self.description as NSString]
dictionary["source"] = source as NSString?
dictionary["dest"] = destination as NSString?
return dictionaryToJSON(dictionary)
}
}
public protocol OperationHandle {
var operationType: FileOperationType { get }
var bytesSoFar: Int64 { get }
var totalBytes: Int64 { get }
var inProgress: Bool { get }
var progress: Float { get }
func cancel() -> Bool
}
public extension OperationHandle {
public var progress: Float {
let bytesSoFar = self.bytesSoFar
let totalBytes = self.totalBytes
return totalBytes > 0 ? Float(Double(bytesSoFar) / Double(totalBytes)) : Float.nan
}
}
public protocol FileProviderDelegate: class {
func fileproviderSucceed(_ fileProvider: FileProviderOperations, operation: FileOperation)
func fileproviderFailed(_ fileProvider: FileProviderOperations, operation: FileOperation)
func fileproviderProgress(_ fileProvider: FileProviderOperations, operation: FileOperation, progress: Float)
func fileproviderSucceed(_ fileProvider: FileProviderOperations, operation: FileOperationType)
func fileproviderFailed(_ fileProvider: FileProviderOperations, operation: FileOperationType)
func fileproviderProgress(_ fileProvider: FileProviderOperations, operation: FileOperationType, progress: Float)
}
public protocol FileOperationDelegate: class {
/// fileProvider(_:shouldOperate:) gives the delegate an opportunity to filter the file operation. Returning true from this method will allow the copy to happen. Returning false from this method causes the item in question to be skipped. If the item skipped was a directory, no children of that directory will be subject of the operation, nor will the delegate be notified of those children.
func fileProvider(_ fileProvider: FileProviderOperations, shouldDoOperation operation: FileOperation) -> Bool
func fileProvider(_ fileProvider: FileProviderOperations, shouldDoOperation operation: FileOperationType) -> Bool
/// fileProvider(_:shouldProceedAfterError:copyingItemAtPath:toPath:) gives the delegate an opportunity to recover from or continue copying after an error. If an error occurs, the error object will contain an ErrorType indicating the problem. The source path and destination paths are also provided. If this method returns true, the FileProvider instance will continue as if the error had not occurred. If this method returns false, the NSFileManager instance will stop copying, return false from copyItemAtPath:toPath:error: and the error will be provied there.
func fileProvider(_ fileProvider: FileProviderOperations, shouldProceedAfterError error: Error, operation: FileOperation) -> Bool
func fileProvider(_ fileProvider: FileProviderOperations, shouldProceedAfterError error: Error, operation: FileOperationType) -> Bool
}
// THESE ARE METHODS TO PROVIDE COMPATIBILITY WITH SWIFT 2.3 SIMOULTANIOUSLY!
internal extension URL {
var uw_scheme: String {
#if swift(>=2.3)
return self.scheme ?? ""
#else
return self.scheme
#endif
}
var uw_absoluteString: String {
return self.absoluteString
}
func uw_URLByAppendingPathComponent(_ pathComponent: String) -> URL {
return self.appendingPathComponent(pathComponent)
}
func uw_URLByAppendingPathExtension(_ pathExtension: String) -> URL {
return self.appendingPathExtension(pathExtension)
internal class Weak<T: AnyObject> {
weak var value : T?
init (_ value: T) {
self.value = value
}
}
internal func jsonToDictionary(_ jsonString: String) -> [String: AnyObject]? {
guard let data = jsonString.data(using: String.Encoding.utf8) else {
return nil
}
if let dic = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions()) as? [String: AnyObject] {
return dic
}
return nil
public protocol FoundationErrorEnum {
init? (rawValue: Int)
var rawValue: Int { get }
}
internal func dictionaryToJSON(_ dictionary: [String: AnyObject]) -> String? {
if let data = try? JSONSerialization.data(withJSONObject: dictionary, options: JSONSerialization.WritingOptions()) {
return String(data: data, encoding: String.Encoding.utf8)
}
return nil
}
extension URLError.Code: FoundationErrorEnum {}
extension CocoaError.Code: FoundationErrorEnum {}
+419 -325
View File
@@ -8,300 +8,479 @@
import Foundation
public final class LocalFileObject: FileObject {
public let allocatedSize: Int64
// codebeat:disable[ARITY]
public init(absoluteURL: URL, name: String, path: String, size: Int64 = -1, allocatedSize: Int64 = 0, createdDate: Date? = nil, modifiedDate: Date? = nil, fileType: FileType = .regular, isHidden: Bool = false, isReadOnly: Bool = false) {
self.allocatedSize = allocatedSize
super.init(absoluteURL: absoluteURL, name: name, path: path, size: size, createdDate: createdDate, modifiedDate: modifiedDate, fileType: fileType, isHidden: isHidden, isReadOnly: isReadOnly)
}
// codebeat:enable[ARITY]
}
open class LocalFileProvider: FileProvider, FileProviderMonitor {
open let type = "Local"
open var isPathRelative: Bool = true
open var baseURL: URL? = LocalFileProvider.defaultBaseURL()
open var currentPath: String = ""
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: DispatchQueue
open var operation_queue: OperationQueue
open weak var delegate: FileProviderDelegate?
open let credential: URLCredential? = nil
open internal(set) var credential: URLCredential?
open let fileManager = FileManager()
open let opFileManager = FileManager()
open private(set) var fileManager = FileManager()
open private(set) var opFileManager = FileManager()
fileprivate var fileProviderManagerDelegate: LocalFileProviderManagerDelegate? = nil
public init () {
dispatch_queue = DispatchQueue(label: "FileProvider.\(type)", attributes: DispatchQueue.Attributes.concurrent)
operation_queue = DispatchQueue(label: "FileProvider.\(type).Operation", attributes: [])
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 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 = DispatchQueue(label: "FileProvider.\(type)", attributes: DispatchQueue.Attributes.concurrent)
operation_queue = DispatchQueue(label: "FileProvider.\(type).Operation", attributes: [])
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
}
fileprivate static func defaultBaseURL() -> URL {
let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true);
return URL(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!
}
open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {
dispatch_queue.async {
do {
let contents = try self.fileManager.contentsOfDirectory(at: self.absoluteURL(path), includingPropertiesForKeys: [URLResourceKey.nameKey, URLResourceKey.fileSizeKey, URLResourceKey.fileAllocatedSizeKey, URLResourceKey.creationDateKey, URLResourceKey.contentModificationDateKey, URLResourceKey.isHiddenKey, URLResourceKey.volumeIsReadOnlyKey], options: FileManager.DirectoryEnumerationOptions.skipsSubdirectoryDescendants)
let filesAttributes = contents.map({ (fileURL) -> LocalFileObject in
return self.attributesOfItem(url: fileURL)
let contents = try self.fileManager.contentsOfDirectory(at: self.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(filesAttributes, nil)
} catch let e as NSError {
} catch let e {
completionHandler([], e)
}
}
}
internal func attributesOfItem(url fileURL: URL) -> LocalFileObject {
var namev, sizev, allocated, filetypev, creationDatev, modifiedDatev, hiddenv, readonlyv: AnyObject?
_ = try? (fileURL as NSURL).getResourceValue(&namev, forKey: URLResourceKey.nameKey)
_ = try? (fileURL as NSURL).getResourceValue(&sizev, forKey: URLResourceKey.fileSizeKey)
_ = try? (fileURL as NSURL).getResourceValue(&allocated, forKey: URLResourceKey.fileAllocatedSizeKey)
_ = try? (fileURL as NSURL).getResourceValue(&creationDatev, forKey: URLResourceKey.creationDateKey)
_ = try? (fileURL as NSURL).getResourceValue(&modifiedDatev, forKey: URLResourceKey.contentModificationDateKey)
_ = try? (fileURL as NSURL).getResourceValue(&filetypev, forKey: URLResourceKey.fileResourceTypeKey)
_ = try? (fileURL as NSURL).getResourceValue(&hiddenv, forKey: URLResourceKey.isHiddenKey)
_ = try? (fileURL as NSURL).getResourceValue(&readonlyv, forKey: URLResourceKey.volumeIsReadOnlyKey)
let path: String
if isPathRelative {
path = self.relativePathOf(url: fileURL)
} else {
path = fileURL.path
}
let filetype = URLFileResourceType(rawValue: filetypev as? String ?? "")
let fileAttr = LocalFileObject(absoluteURL: fileURL, name: namev as! String, path: path, size: sizev?.int64Value ?? -1, allocatedSize: allocated?.int64Value ?? -1, createdDate: creationDatev as? Date, modifiedDate: modifiedDatev as? Date, fileType: FileType(urlResourceTypeValue: filetype), isHidden: hiddenv?.boolValue ?? false, isReadOnly: readonlyv?.boolValue ?? false)
return fileAttr
}
open func storageProperties(completionHandler: (@escaping (_ total: Int64, _ used: Int64) -> Void)) {
let dict = (try? FileManager.default.attributesOfFileSystem(forPath: baseURL?.path ?? "/"))
let totalSize = (dict?[FileAttributeKey.systemSize] as? NSNumber)?.int64Value ?? -1;
let freeSize = (dict?[FileAttributeKey.systemFreeSize] as? NSNumber)?.int64Value ?? 0;
let totalSize = (dict?[.systemSize] as? NSNumber)?.int64Value ?? -1;
let freeSize = (dict?[.systemFreeSize] as? NSNumber)?.int64Value ?? 0;
completionHandler(totalSize, totalSize - freeSize)
}
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
dispatch_queue.async {
completionHandler(self.attributesOfItem(url: self.absoluteURL(path)), nil)
completionHandler(LocalFileObject(fileWithPath: path, relativeTo: self.baseURL), nil)
}
}
open weak var fileOperationDelegate : FileOperationDelegate?
@objc(createWithFolder:at:completionHandler:) open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) {
operation_queue.async {
@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.createDirectory(at: self.absoluteURL(atPath).uw_URLByAppendingPathComponent(folderName), withIntermediateDirectories: true, attributes: [:])
try self.opFileManager.createDirectory(at: url, withIntermediateDirectories: true, attributes: [:])
completionHandler?(nil)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderSucceed(self, operation: .create(path: (atPath as NSString).appendingPathComponent(folderName) + "/"))
})
} catch let e as NSError {
DispatchQueue.main.async {
self.delegate?.fileproviderSucceed(self, operation: opType)
}
} catch let e {
completionHandler?(e)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderFailed(self, operation: .create(path: (atPath as NSString).appendingPathComponent(folderName) + "/"))
})
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)
}
open func create(file fileAttribs: FileObject, at atPath: String, contents data: Data?, completionHandler: SimpleCompletionHandler) {
operation_queue.async {
let fileURL = self.absoluteURL(atPath).uw_URLByAppendingPathComponent(fileAttribs.name)
var attributes = [String : Any]()
if let createdDate = fileAttribs.createdDate {
attributes[FileAttributeKey.creationDate.rawValue] = createdDate as NSDate
}
if let modDate = fileAttribs.modifiedDate {
attributes[FileAttributeKey.modificationDate.rawValue] = modDate as NSDate
}
if fileAttribs.isReadOnly {
attributes[FileAttributeKey.posixPermissions.rawValue] = NSNumber(value: 365 as Int16)
}
let success = self.opFileManager.createFile(atPath: fileURL.path, contents: data, attributes: attributes)
@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 {
do {
try (fileURL as NSURL).setResourceValue(fileAttribs.isHidden, forKey: URLResourceKey.isHiddenKey)
} catch _ {}
completionHandler?(nil)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderSucceed(self, operation: .create(path: (atPath as NSString).appendingPathComponent(fileAttribs.name)))
})
DispatchQueue.main.async {
self.delegate?.fileproviderSucceed(self, operation: opType)
}
} else {
completionHandler?(self.throwError(atPath, code: URLError.cannotCreateFile as FoundationErrorEnum))
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderFailed(self, operation: .create(path: (atPath as NSString).appendingPathComponent(fileAttribs.name)))
})
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)
}
open func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
// FIXME: progress
operation_queue.async {
if !overwrite && self.fileManager.fileExists(atPath: self.absoluteURL(toPath).path) {
@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: self.absoluteURL(path), to: self.absoluteURL(toPath))
try self.opFileManager.moveItem(at: sourceUrl, to: destUrl)
completionHandler?(nil)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderSucceed(self, operation: .move(source: path, destination: toPath))
})
} catch let e as NSError {
DispatchQueue.main.async {
self.delegate?.fileproviderSucceed(self, operation: opType)
}
} catch let e {
completionHandler?(e)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderFailed(self, operation: .move(source: path, destination: toPath))
})
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)
}
open func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
// FIXME: progress, for files > 100mb, monitor file by another thread, for dirs check copied items count
operation_queue.async {
if !overwrite && self.fileManager.fileExists(atPath: self.absoluteURL(toPath).path) {
completionHandler?(self.throwError(toPath, code: URLError.cannotWriteToFile as FoundationErrorEnum))
@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: self.absoluteURL(path), to: self.absoluteURL(toPath))
try self.opFileManager.copyItem(at: sourceUrl, to: destUrl)
completionHandler?(nil)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderSucceed(self, operation: .copy(source: path, destination: toPath))
})
} catch let e as NSError {
DispatchQueue.main.async {
self.delegate?.fileproviderSucceed(self, operation: opType)
}
} catch let e {
completionHandler?(e)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderFailed(self, operation: .copy(source: path, destination: toPath))
})
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)
}
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) {
operation_queue.async {
@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 {
try self.opFileManager.removeItem(at: self.absoluteURL(path))
let successfulSecurityScopedResourceAccess = url.startAccessingSecurityScopedResource()
try self.opFileManager.removeItem(at: url)
if successfulSecurityScopedResourceAccess {
url.stopAccessingSecurityScopedResource()
}
completionHandler?(nil)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderSucceed(self, operation: .remove(path: path))
})
} catch let e as NSError {
DispatchQueue.main.async {
self.delegate?.fileproviderSucceed(self, operation: opType)
}
} catch let e {
completionHandler?(e)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderFailed(self, operation: .remove(path: path))
})
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)
}
open func copyItem(localFile: URL, to toPath: String, completionHandler: SimpleCompletionHandler) {
operation_queue.async {
@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.absoluteURL(toPath))
try self.opFileManager.copyItem(at: localFile, to: self.url(of: toPath))
completionHandler?(nil)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderSucceed(self, operation: .copy(source: localFile.uw_absoluteString, destination: toPath))
})
} catch let e as NSError {
DispatchQueue.main.async {
self.delegate?.fileproviderSucceed(self, operation: opType)
}
} catch let e {
completionHandler?(e)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderFailed(self, operation: .copy(source: localFile.uw_absoluteString, destination: toPath))
})
DispatchQueue.main.async {
self.delegate?.fileproviderFailed(self, operation: opType)
}
}
}
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
}
open func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) {
operation_queue.async {
@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.absoluteURL(path), to: toLocalURL)
try self.opFileManager.copyItem(at: self.url(of: path), to: toLocalURL)
completionHandler?(nil)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderSucceed(self, operation: .copy(source: path, destination: toLocalURL.uw_absoluteString))
})
} catch let e as NSError {
DispatchQueue.main.async {
self.delegate?.fileproviderSucceed(self, operation: opType)
}
} catch let e {
completionHandler?(e)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderFailed(self, operation: .copy(source: path, destination: toLocalURL.uw_absoluteString))
})
DispatchQueue.main.async {
self.delegate?.fileproviderFailed(self, operation: opType)
}
}
}
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
}
open func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) {
dispatch_queue.async {
let data = self.fileManager.contents(atPath: self.absoluteURL(path).path)
@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)
}
}
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) {
// Unfortunatlely there is no method provided in NSFileManager to read a segment of file.
// So we have to fallback to POSIX provided methods
dispatch_queue.async {
let aPath = self.absoluteURL(path).path
guard !self.attributesOfItem(url: self.absoluteURL(path)).isDirectory && self.fileManager.fileExists(atPath: aPath) else {
completionHandler(nil, self.throwError(path, code: URLError.cannotOpenFile as FoundationErrorEnum))
return
}
let fd_from = open(aPath, O_RDONLY)
if fd_from < 0 {
completionHandler(nil, self.throwError(path, code: URLError.cannotOpenFile as FoundationErrorEnum))
return
}
defer { precondition(close(fd_from) >= 0) }
lseek(fd_from, offset, SEEK_SET)
var buf = [UInt8](repeating: 0, count: length)
let nread = read(fd_from, &buf, buf.count)
if nread < 0 {
completionHandler(nil, self.throwError(path, code: URLError.noPermissionsToReadFile as FoundationErrorEnum))
} else if nread == 0 {
completionHandler(nil, nil)
} else {
let data = Data(bytesNoCopy: UnsafeMutablePointer<UInt8>(&buf), count: nread, deallocator: .free)
completionHandler(data, nil)
}
}
}
open func writeContents(path: String, contents data: Data, atomically: Bool, completionHandler: SimpleCompletionHandler) {
operation_queue.async {
try? data.write(to: self.absoluteURL(path), options: atomically ? [.atomic] : [])
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderSucceed(self, operation: .modify(path: path))
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.absoluteURL(path), includingPropertiesForKeys: nil, options: recursive ? FileManager.DirectoryEnumerationOptions() : .skipsSubdirectoryDescendants) { (url, e) -> Bool in
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? URL {
if fileURL.lastPathComponent.lowercased().contains(query.lowercased()) {
let fileObject = self.attributesOfItem(url: fileURL)
result.append(self.attributesOfItem(url: fileURL))
foundItemHandler?(fileObject)
let path = self.relativePathOf(url: fileURL)
if let fileObject = LocalFileObject(fileWithPath: path, relativeTo: self.baseURL) {
result.append(fileObject)
foundItemHandler?(fileObject)
}
}
}
completionHandler(result, nil)
@@ -312,16 +491,12 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor {
open func registerNotifcation(path: String, eventHandler: @escaping (() -> Void)) {
self.unregisterNotifcation(path: path)
let absurl = self.absoluteURL(path)
var isdirv: AnyObject?
do {
try (absurl as NSURL).getResourceValue(&isdirv, forKey: URLResourceKey.isDirectoryKey)
} catch _ {
}
if !(isdirv?.boolValue ?? false) {
let 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()
@@ -342,155 +517,74 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor {
open func isRegisteredForNotification(path: String) -> Bool {
return monitors.map( { self.relativePathOf(url: $0.url) } ).contains(path)
}
open func copy(with zone: NSZone? = nil) -> Any {
let copy = LocalFileProvider(baseURL: self.baseURL!)
copy.currentPath = self.currentPath
copy.delegate = self.delegate
copy.fileOperationDelegate = self.fileOperationDelegate
copy.isPathRelative = self.isPathRelative
return copy
}
}
public extension LocalFileProvider {
public func create(symbolicLink path: String, withDestinationPath destPath: String, completionHandler: SimpleCompletionHandler) {
operation_queue.async {
operation_queue.addOperation {
do {
try self.opFileManager.createSymbolicLink(at: self.absoluteURL(path), withDestinationURL: self.absoluteURL(destPath))
try self.opFileManager.createSymbolicLink(at: self.url(of: path), withDestinationURL: self.url(of: destPath))
completionHandler?(nil)
DispatchQueue.main.async(execute: {
DispatchQueue.main.async {
self.delegate?.fileproviderSucceed(self, operation: .link(link: path, target: destPath))
})
} catch let e as NSError {
}
} catch let e {
completionHandler?(e)
DispatchQueue.main.async(execute: {
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, FileManagerDelegate {
weak var provider: LocalFileProvider?
init(provider: LocalFileProvider) {
self.provider = provider
}
func fileManager(_ fileManager: FileManager, shouldCopyItemAt srcURL: URL, to dstURL: URL) -> Bool {
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
return true
}
let srcPath = provider.relativePathOf(url: srcURL)
let dstPath = provider.relativePathOf(url: dstURL)
return delegate.fileProvider(provider, shouldDoOperation: .copy(source: srcPath, destination: dstPath))
}
func fileManager(_ fileManager: FileManager, shouldMoveItemAt srcURL: URL, to dstURL: URL) -> Bool {
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
return true
}
let srcPath = provider.relativePathOf(url: srcURL)
let dstPath = provider.relativePathOf(url: dstURL)
return delegate.fileProvider(provider, shouldDoOperation: .move(source: srcPath, destination: dstPath))
}
func fileManager(_ fileManager: FileManager, shouldRemoveItemAt URL: URL) -> Bool {
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
return true
}
let path = provider.relativePathOf(url: URL)
return delegate.fileProvider(provider, shouldDoOperation: .remove(path: path))
}
func fileManager(_ fileManager: FileManager, shouldLinkItemAt srcURL: URL, to dstURL: URL) -> Bool {
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
return true
}
let srcPath = provider.relativePathOf(url: srcURL)
let dstPath = provider.relativePathOf(url: dstURL)
return delegate.fileProvider(provider, shouldDoOperation: .link(link: srcPath, target: dstPath))
}
func fileManager(_ fileManager: FileManager, shouldProceedAfterError error: Error, copyingItemAt srcURL: URL, to dstURL: URL) -> Bool {
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
return false
}
let srcPath = provider.relativePathOf(url: srcURL)
let dstPath = provider.relativePathOf(url: dstURL)
return delegate.fileProvider(provider, shouldProceedAfterError: error, operation: .copy(source: srcPath, destination: dstPath))
}
func fileManager(_ fileManager: FileManager, shouldProceedAfterError error: Error, movingItemAt srcURL: URL, to dstURL: URL) -> Bool {
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
return false
}
let srcPath = provider.relativePathOf(url: srcURL)
let dstPath = provider.relativePathOf(url: dstURL)
return delegate.fileProvider(provider, shouldProceedAfterError: error, operation: .move(source: srcPath, destination: dstPath))
}
func fileManager(_ fileManager: FileManager, shouldProceedAfterError error: Error, removingItemAt URL: URL) -> Bool {
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
return false
}
let path = provider.relativePathOf(url: URL)
return delegate.fileProvider(provider, shouldProceedAfterError: error, operation: .remove(path: path))
}
func fileManager(_ fileManager: FileManager, shouldProceedAfterError error: Error, linkingItemAt srcURL: URL, to dstURL: URL) -> Bool {
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
return false
}
let srcPath = provider.relativePathOf(url: srcURL)
let dstPath = provider.relativePathOf(url: dstURL)
return delegate.fileProvider(provider, shouldProceedAfterError: error, operation: .link(link: srcPath, target: dstPath))
}
}
internal class LocalFolderMonitor {
fileprivate let source: DispatchSourceFileSystemObject
fileprivate let descriptor: CInt
fileprivate let qq: DispatchQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.default)
fileprivate var state: Bool = false
fileprivate var monitoredTime: TimeInterval = Date().timeIntervalSinceReferenceDate
var url: URL
/// Creates a folder monitor object with monitoring enabled.
init(url: URL, handler: @escaping ()->Void) {
self.url = url
descriptor = open((url as NSURL).fileSystemRepresentation, O_EVTONLY)
source = DispatchSource.makeFileSystemObjectSource(fileDescriptor: descriptor, eventMask: DispatchSource.FileSystemEvent.write, queue: qq)
// Folder monitoring is recursive and deep. Monitoring a root folder may be very costly
// We have a 0.2 second delay to ensure we wont call handler 1000s times when there is
// a huge file operation. This ensures app will work smoothly while this 250 milisec won't
// affect user experince much
let main_handler: ()->Void = {
if Date().timeIntervalSinceReferenceDate < self.monitoredTime + 0.2 {
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 = 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()
completionHandler(intents[0].url)
}
}
/// Stops sending notifications if currently enabled
func stop() {
if state {
state = false
source.suspend()
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 {
source.cancel()
}
}
+306
View File
@@ -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)
}
}
+360
View File
@@ -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
}
}
+317
View File
@@ -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)
}
})
}
}
@@ -8,6 +8,59 @@
import Foundation
open class RemoteOperationHandle: OperationHandle {
internal var tasks: [Weak<URLSessionTask>]
open private(set) var operationType: FileOperationType
init(operationType: FileOperationType, tasks: [URLSessionTask]) {
self.operationType = operationType
self.tasks = tasks.map { Weak<URLSessionTask>($0) }
}
internal func add(task: URLSessionTask) {
tasks.append(Weak<URLSessionTask>(task))
}
private func reape() {
self.tasks = tasks.filter { $0.value != nil }
}
open var bytesSoFar: Int64 {
return tasks.reduce(0) {
if let task = $1.value as? URLSessionUploadTask {
return $0 + task.countOfBytesSent
} else {
return $0 + ($1.value?.countOfBytesReceived ?? 0)
}
}
}
open var totalBytes: Int64 {
return tasks.reduce(0) {
if let task = $1.value as? URLSessionUploadTask {
return $0 + task.countOfBytesExpectedToSend
} else {
return $0 + ($1.value?.countOfBytesExpectedToSend ?? 0)
}
}
}
open func cancel() -> Bool {
var canceled = false
for taskbox in tasks {
taskbox.value?.cancel()
canceled = true
}
return canceled
}
open var inProgress: Bool {
return tasks.reduce(false) { $0 || $1.value?.state ?? .canceling == .running }
}
}
class SessionDelegate: NSObject, URLSessionDataDelegate, URLSessionDownloadDelegate {
weak var fileProvider: FileProvider?
@@ -38,7 +91,7 @@ class SessionDelegate: NSObject, URLSessionDataDelegate, URLSessionDownloadDeleg
return
}
let dest = json["dest"] as? String
let op : FileOperation
let op : FileOperationType
switch type {
case "Create":
op = .create(path: source)
@@ -148,11 +201,11 @@ public enum FileProviderHTTPErrorCode: Int {
case notExtended = 510
case networkAuthenticationRequired = 511
fileprivate static let status1xx = [100: "Continue", 101: "Switching Protocols", 102: "Processing"]
fileprivate static let status2xx = [200: "OK", 201: "Created", 202: "Accepted", 203: "Non-Authoritative Information", 204: "No Content", 205: "Reset Content", 206: "Partial Content", 207: "Multi-Status", 208: "Already Reported", 226: "IM Used"]
fileprivate static let status3xx = [300: "Multiple Choices", 301: "Moved Permanently", 302: "Found", 303: "See Other", 304: "Not Modified", 305: "Use Proxy", 306: "Switch Proxy", 307: "Temporary Redirect", 308: "Permanent Redirect"]
fileprivate static let status4xx = [400: "Bad Request", 401: "Unauthorized/Expired Session", 402: "Payment Required", 403: "Forbidden", 404: "Not Found", 405: "Method Not Allowed", 406: "Not Acceptable", 407: "Proxy Authentication Required", 408: "Request Timeout", 409: "Conflict", 410: "Gone", 411: "Length Required", 412: "Precondition Failed", 413: "Payload Too Large", 414: "URI Too Long", 415: "Unsupported Media Type", 416: "Range Not Satisfiable", 417: "Expectation Failed", 421: "Misdirected Request", 422: "Unprocessable Entity", 423: "Locked", 424: "Failed Dependency", 425: "Unordered Collection", 426: "Upgrade Required", 428: "Precondition Required", 429: "Too Many Requests", 431: "Request Header Fields Too Large", 451: "Unavailable For Legal Reasons"]
fileprivate static let status5xx = [500: "Internal Server Error", 501: "Not Implemented", 502: "Bad Gateway", 503: "Service Unavailable", 504: "Gateway Timeout", 505: "HTTP Version Not Supported", 506: "Variant Also Negotiates", 507: "Insufficient Storage", 508: "Loop Detected", 509: "Bandwidth Limit Exceeded", 510: "Not Extended", 511: "Network Authentication Required"]
fileprivate static let status1xx: [Int: String] = [100: "Continue", 101: "Switching Protocols", 102: "Processing"]
fileprivate static let status2xx: [Int: String] = [200: "OK", 201: "Created", 202: "Accepted", 203: "Non-Authoritative Information", 204: "No Content", 205: "Reset Content", 206: "Partial Content", 207: "Multi-Status", 208: "Already Reported", 226: "IM Used"]
fileprivate static let status3xx: [Int: String] = [300: "Multiple Choices", 301: "Moved Permanently", 302: "Found", 303: "See Other", 304: "Not Modified", 305: "Use Proxy", 306: "Switch Proxy", 307: "Temporary Redirect", 308: "Permanent Redirect"]
fileprivate static let status4xx: [Int: String] = [400: "Bad Request", 401: "Unauthorized/Expired Session", 402: "Payment Required", 403: "Forbidden", 404: "Not Found", 405: "Method Not Allowed", 406: "Not Acceptable", 407: "Proxy Authentication Required", 408: "Request Timeout", 409: "Conflict", 410: "Gone", 411: "Length Required", 412: "Precondition Failed", 413: "Payload Too Large", 414: "URI Too Long", 415: "Unsupported Media Type", 416: "Range Not Satisfiable", 417: "Expectation Failed", 421: "Misdirected Request", 422: "Unprocessable Entity", 423: "Locked", 424: "Failed Dependency", 425: "Unordered Collection", 426: "Upgrade Required", 428: "Precondition Required", 429: "Too Many Requests", 431: "Request Header Fields Too Large", 451: "Unavailable For Legal Reasons"]
fileprivate static let status5xx: [Int: String] = [500: "Internal Server Error", 501: "Not Implemented", 502: "Bad Gateway", 503: "Service Unavailable", 504: "Gateway Timeout", 505: "HTTP Version Not Supported", 506: "Variant Also Negotiates", 507: "Insufficient Storage", 508: "Loop Detected", 509: "Bandwidth Limit Exceeded", 510: "Not Extended", 511: "Network Authentication Required"]
public var description: String {
switch self.rawValue {
+36 -28
View File
@@ -12,12 +12,24 @@ import Foundation
// For big-endian platforms like PowerPC, there must be a huge overhaul
protocol SMBProtocolClientDelegate: class {
func receivedSMB2Response(_ header: SMB2.Header, response: SMBResponse)
func receivedResponse(client: SMB2ProtocolClient, response: SMBResponse, for: SMBRequest)
}
class SMB2ProtocolClient: FPSStreamTask {
var currentMessageID: UInt64 = 0
var sessionId: UInt64 = 0
var timeout: TimeInterval = 30
private(set) var lastMessageID: UInt64 = 0
private(set) var sessionId: UInt64 = 0
private func messageId() -> UInt64 {
defer {
lastMessageID += 1
}
return lastMessageID
}
private(set) var establishedTrees = Array<SMB2.TreeConnectResponse>()
private(set) var requestStack = [Int: SMBRequest]()
private(set) var responseStack = [Int: SMBResponse]()
weak var delegate: SMBProtocolClientDelegate?
@@ -40,7 +52,7 @@ class SMB2ProtocolClient: FPSStreamTask {
let data = createSMB2Message(header: smbHeader, message: msg)
self.writeData(data, timeout: 0, completionHandler: { (e) in
if self.sessionId == 0 {
self.readData(OfMinLength: 64, maxLength: 65536, timeout: 30, completionHandler: { (data, eof, e2) in
self.readData(OfMinLength: 64, maxLength: 65536, timeout: self.timeout, completionHandler: { (data, eof, e2) in
// TODO: set session id
completionHandler?(e2 ?? e)
})
@@ -70,6 +82,7 @@ class SMB2ProtocolClient: FPSStreamTask {
})
return mId
}
func sendTreeDisconnect(id treeId: UInt32, completionHandler: SimpleCompletionHandler) -> UInt64 {
let mId = messageId()
let smbHeader = SMB2.Header(command: .TREE_DISCONNECT, creditRequestResponse: 111, messageId: mId, treeId: treeId, sessionId: sessionId)
@@ -92,18 +105,15 @@ class SMB2ProtocolClient: FPSStreamTask {
return mId
}
func messageId() -> UInt64 {
defer {
currentMessageID += 1
}
return currentMessageID
func reset() {
}
// MARK: create and analyse messages
}
// MARK: create and analyse messages
extension SMB2ProtocolClient {
func determineSMBVersion(_ data: Data) -> Float {
var smbverChar: Int8 = 0
(data as NSData).getBytes(&smbverChar, length: 1)
let smbverChar: Int8 = Int8(bitPattern: data.first ?? 0)
let version = 0 - smbverChar
return Float(version)
}
@@ -117,7 +127,7 @@ class SMB2ProtocolClient: FPSStreamTask {
throw SMBFileProviderError.incompatibleHeader
}
let headersize = MemoryLayout<SMB1.Header>.size
let header: SMB1.Header = decode(data)
let header: SMB1.Header = data.scanValue()!
var blocks = [(params: [UInt16], message: Data?)]()
var offset = headersize
while offset < data.count {
@@ -129,22 +139,22 @@ class SMB2ProtocolClient: FPSStreamTask {
offset += MemoryLayout<UInt8>.size
var rawParamWords = [UInt8](buffer[offset..<(offset + paramWordsCount * 2)])
let paramData = Data(bytesNoCopy: UnsafeMutablePointer<UInt8>(&rawParamWords), count: rawParamWords.count, deallocator: .free)
paramWords = decode(paramData)
paramWords = paramData.scanValue()!
offset += paramWordsCount * 2
let messageBytesCount = Int(UInt16(buffer[0]) + UInt16(buffer[1]) << 8)
offset += MemoryLayout<UInt16>.size
guard data.count >= (offset + messageBytesCount) else {
throw SMBFileProviderError.incorrectMessageLength
}
var rawMessage = [UInt8](buffer[offset..<(offset + messageBytesCount)])
let rawMessage = [UInt8](buffer[offset..<(offset + messageBytesCount)])
offset += messageBytesCount
let message = NSData(bytes: &rawMessage, length: rawMessage.count) as Data
let message = Data(bytes: rawMessage)
blocks.append((params: paramWords, message: message))
}
return (header, blocks)
}
func digestSMB2Message(_ data: Data) throws -> (header: SMB2.Header, message: SMBResponse?)? {
func digestSMB2Message(_ data: Data) throws -> SMBResponse? {
guard data.count > 65 else {
throw URLError(.badServerResponse)
}
@@ -152,10 +162,10 @@ class SMB2ProtocolClient: FPSStreamTask {
throw SMBFileProviderError.incompatibleHeader
}
let headersize = MemoryLayout<SMB2.Header>.size
let headerData = data.subdata(in: NSRange(location: 0, length: headersize))
let headerData = data.subdata(in: 0..<headersize)
let messageSize = data.count - headersize
let messageData = data.subdata(in: NSRange(location: headersize, length: messageSize))
let header: SMB2.Header = decode(headerData)
let messageData = data.subdata(in: headersize..<(headersize + messageSize))
let header: SMB2.Header = headerData.scanValue()!
switch header.command {
case .NEGOTIATE:
return (header, SMB2.NegotiateResponse(data: messageData))
@@ -201,8 +211,7 @@ class SMB2ProtocolClient: FPSStreamTask {
}
func createSMBMessage(header: SMB1.Header, blocks: [(params: Data?, message: Data?)]) -> Data {
var headerv = header
var result = NSData(data: encode(&headerv)) as Data
var result = Data(value: header)
for block in blocks {
var paramWordsCount = UInt8(block.params?.count ?? 0)
result.append(&paramWordsCount, count: MemoryLayout.size(ofValue: paramWordsCount))
@@ -219,10 +228,9 @@ class SMB2ProtocolClient: FPSStreamTask {
return result
}
func createSMB2Message(header: SMB2.Header, message: SMBRequest) -> Data {
var headerv = header
var result = NSData(data: encode(&headerv)) as Data
result.append(message.data() as Data)
func createSMB2Message(header: SMB2.Header, message: SMBRequestBody) -> Data {
var result = Data(value: header)
result.append(message.data())
return result
}
}
+37 -18
View File
@@ -8,12 +8,14 @@
import Foundation
open class SMBFileProvider: FileProvider, FileProviderMonitor {
open var type: String = "Samba"
class SMBFileProvider: FileProvider, FileProviderMonitor {
open static var type: String = "Samba"
open var isPathRelative: Bool = true
open var baseURL: URL?
open var currentPath: String = ""
open var dispatch_queue: DispatchQueue
open var operation_queue: OperationQueue
open weak var delegate: FileProviderDelegate?
open let credential: URLCredential?
@@ -23,17 +25,16 @@ open class SMBFileProvider: FileProvider, FileProviderMonitor {
guard baseURL.uw_scheme.lowercased() == "smb" else {
return nil
}
self.baseURL = baseURL
dispatch_queue = DispatchQueue(label: "FileProvider.\(type)", attributes: DispatchQueue.Attributes.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
}
open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObjectClass], _ error: Error?) -> Void)) {
NotImplemented()
dispatch_queue.async {
}
}
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObjectClass?, _ error: Error?) -> Void)) {
@@ -46,44 +47,54 @@ open class SMBFileProvider: FileProvider, FileProviderMonitor {
open weak var fileOperationDelegate: FileOperationDelegate?
open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) {
open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
NotImplemented()
return nil
}
open func create(file fileAttribs: FileObject, at atPath: String, contents data: Data?, completionHandler: SimpleCompletionHandler) {
open func create(file fileName: String, at atPath: String, contents data: Data?, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
NotImplemented()
return nil
}
open func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
open func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
NotImplemented()
return nil
}
open func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
open func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
NotImplemented()
return nil
}
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) {
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
NotImplemented()
return nil
}
open func copyItem(localFile: URL, to toPath: String, completionHandler: SimpleCompletionHandler) {
open func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
NotImplemented()
return nil
}
open func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) {
open func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
NotImplemented()
return nil
}
open func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) {
open func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
NotImplemented()
return nil
}
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) {
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
NotImplemented()
return nil
}
open func writeContents(path: String, contents data: Data, atomically: Bool, completionHandler: SimpleCompletionHandler) {
open func writeContents(path: String, contents data: Data, atomically: Bool, 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)) {
@@ -101,6 +112,14 @@ open class SMBFileProvider: FileProvider, FileProviderMonitor {
open func isRegisteredForNotification(path: String) -> Bool {
return false
}
open func copy(with zone: NSZone? = nil) -> Any {
let copy = SMBFileProvider(baseURL: self.baseURL!, credential: self.credential!, afterInitialized: { _ in })!
copy.currentPath = self.currentPath
copy.delegate = self.delegate
copy.fileOperationDelegate = self.fileOperationDelegate
return copy
}
}
// MARK: basic CIFS interactivity
+51 -4
View File
@@ -8,17 +8,35 @@
import Foundation
protocol SMBRequest {
protocol SMBRequestBody {
func data() -> Data
}
protocol SMBResponse {
extension SMBRequestBody {
func data() -> Data {
return Data(value: self)
}
}
protocol SMBResponseBody {
init? (data: Data)
}
extension SMBResponseBody {
init? (data: Data) {
if let v: Self = data.scanValue() {
self = v
} else {
return nil
}
}
}
protocol IOCtlRequestProtocol: SMBRequest {}
protocol IOCtlResponseProtocol: SMBResponse {}
typealias SMBRequest = (header: SMB2.Header, body: SMBRequestBody?)
typealias SMBResponse = (header: SMB2.Header, body: SMBResponseBody?)
protocol IOCtlRequestProtocol: SMBRequestBody {}
protocol IOCtlResponseProtocol: SMBResponseBody {}
struct SMBTime {
@@ -48,3 +66,32 @@ struct SMBTime {
return Date(timeIntervalSince1970: Double(self.time) / 10000000 - 11644473600)
}
}
extension Data {
init<T>(value: T) {
var value = value
self = Data(buffer: UnsafeBufferPointer(start: &value, count: 1))
}
func scanValue<T>() -> T? {
guard MemoryLayout<T>.size <= self.count else { return nil }
return self.withUnsafeBytes { $0.pointee }
}
func scanValue<T>(start: Int) -> T? {
let length = MemoryLayout<T>.size
guard self.count >= start + length else { return nil }
return self.subdata(in: start..<start+length).withUnsafeBytes { $0.pointee }
}
func scanString(start: Int = 0, length: Int, encoding: String.Encoding) -> String? {
guard self.count >= start + length else { return nil }
return String(data: self.subdata(in: start..<start+length), encoding: encoding)
}
static func mapMemory<T, U>(from: T) -> U? {
guard MemoryLayout<T>.size >= MemoryLayout<U>.size else { return nil }
let data = Data(value: from)
return data.scanValue()
}
}
+21 -43
View File
@@ -11,7 +11,7 @@ import Foundation
extension SMB2 {
// MARK: SMB2 Create
struct CreateRequest: SMBRequest {
struct CreateRequest: SMBRequestBody {
let header: CreateRequest.Header
let name: String?
let contexts: [CreateContext]
@@ -25,8 +25,8 @@ extension SMB2 {
func data() -> Data {
var header = self.header
var offset = 0x78 //UInt16(sizeof(SMB2.Header.self) + sizeof(CreateContext.Header.self) - 1)
let body = NSMutableData()
if let name = self.name, let nameData = name.data(using: String.Encoding.utf16) {
var body = Data()
if let name = self.name, let nameData = name.data(using: .utf16) {
header.nameOffset = UInt16(offset)
header.nameLength = UInt16(nameData.count)
offset += nameData.count
@@ -40,8 +40,8 @@ extension SMB2 {
header.contextLength = 0
//result.appendData(nameData)
}
var result = NSData(data: encode(&header)) as Data
result.append(body as Data)
var result = Data(value: header)
result.append(body)
return result
}
@@ -158,7 +158,7 @@ extension SMB2 {
}
}
struct CreateResponse: SMBResponse {
struct CreateResponse: SMBResponseBody {
struct Header {
let size: UInt16
fileprivate let _oplockLevel: UInt8
@@ -186,7 +186,7 @@ extension SMB2 {
guard data.count >= MemoryLayout<CreateResponse.Header>.size else {
return nil
}
self.header = decode(data)
self.header = data.scanValue()!
if self.header.contextsOffset > 0 {
var contexts = [CreateContext]()
var contextOffset = Int(self.header.contextsOffset) - MemoryLayout<SMB2.Header>.size
@@ -195,14 +195,9 @@ extension SMB2 {
self.contexts = contexts
return
}
let contextDataHeader = data.subdata(in: NSRange(location: contextOffset, length: MemoryLayout<CreateContext.Header>.size))
if let lastContextHeader = CreateContext(data: contextDataHeader) {
let lastContextLen = Int(lastContextHeader.header.dataOffset) + Int(lastContextHeader.header.dataLength) - contextOffset
let lastContextData = data.subdata(in: NSRange(location: contextOffset, length: lastContextLen))
if let newContext = CreateContext(data: lastContextData) {
contexts.append(newContext)
}
contextOffset = Int(lastContextHeader.header.next) - MemoryLayout<SMB2.Header>.size
while contextOffset > 0, let context: CreateContext = data.scanValue(start: contextOffset) {
contexts.append(context)
contextOffset = Int(context.header.next) - MemoryLayout<SMB2.Header>.size
}
}
self.contexts = contexts
@@ -226,16 +221,15 @@ extension SMB2 {
let buffer: Data
init(name: ContextNames, data: Data) {
let nameData = NSData(data: (name.rawValue).data(using: String.Encoding.utf16)!) as Data
let nameData = (name.rawValue).data(using: .utf16) ?? Data()
self.header = CreateContext.Header(next: 0, nameOffset: 32, nameLength: UInt16(nameData.count), reserved: 0, dataOffset: UInt16(nameData.count), dataLength: UInt32(data.count))
self.buffer = data
}
init(name: UUID, data: Data) {
var uuid = uuid_t(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)
(name as NSUUID).getBytes(&uuid.0)
let nameData = NSMutableData(bytes: &uuid, length: 16)
self.header = CreateContext.Header(next: 0, nameOffset: 32, nameLength: UInt16(nameData.length), reserved: 0, dataOffset: UInt16(nameData.length), dataLength: UInt32(data.count))
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
}
@@ -244,12 +238,12 @@ extension SMB2 {
guard data.count > headersize else {
return nil
}
self.header = decode(data)
self.buffer = data.subdata(in: NSRange(location: headersize, length: data.count - headersize))
self.header = data.scanValue()!
self.buffer = data.subdata(in: headersize..<data.count)
}
func data() -> Data {
var result = NSData(data: encode(header)) as Data
var result = Data(value: header)
result.append(buffer)
return result
}
@@ -363,7 +357,7 @@ extension SMB2 {
// MARK: SMB2 Close
struct CloseRequest: SMBRequest {
struct CloseRequest: SMBRequestBody {
let size: UInt16
let flags: CloseFlags
fileprivate let reserved2: UInt32
@@ -377,13 +371,9 @@ extension SMB2 {
self.flags = []
self.reserved2 = 0
}
func data() -> Data {
return encode(self)
}
}
struct CloseResponse: SMBResponse {
struct CloseResponse: SMBResponseBody {
let size: UInt16
let flags: CloseFlags
fileprivate let reserved: UInt32
@@ -394,10 +384,6 @@ extension SMB2 {
let allocationSize: UInt64
let endOfFile: UInt64
let fileAttributes: FileAttributes
init? (data: Data) {
self = decode(data)
}
}
struct CloseFlags: OptionSet {
@@ -412,7 +398,7 @@ extension SMB2 {
// MARK: SMB2 Flush
struct FlushRequest: SMBRequest {
struct FlushRequest: SMBRequestBody {
let size: UInt16
fileprivate let reserved: UInt16
fileprivate let reserved2: UInt32
@@ -426,13 +412,9 @@ extension SMB2 {
self.reserved = 0
self.reserved2 = 0
}
func data() -> Data {
return encode(self)
}
}
struct FlushResponse: SMBResponse {
struct FlushResponse: SMBResponseBody {
let size: UInt16
let reserved: UInt16
@@ -440,9 +422,5 @@ extension SMB2 {
self.size = 4
self.reserved = 0
}
init? (data: Data) {
self = decode(data)
}
}
}
+14 -38
View File
@@ -11,7 +11,7 @@ import Foundation
extension SMB2 {
// MARK: SMB2 Read
struct ReadRequest: SMBRequest {
struct ReadRequest: SMBRequestBody {
let size: UInt16
fileprivate let padding: UInt8
let flags: ReadRequest.Flags
@@ -43,10 +43,6 @@ extension SMB2 {
self.channelBuffer = 0
}
func data() -> Data {
return encode(read)
}
struct Flags: OptionSet {
let rawValue: UInt8
@@ -58,7 +54,7 @@ extension SMB2 {
}
}
struct ReadRespone: SMBResponse {
struct ReadRespone: SMBResponseBody {
struct Header {
let size: UInt16
let offset: UInt8
@@ -75,9 +71,9 @@ extension SMB2 {
guard data.count > 16 else {
return nil
}
self.header = decode(data)
self.header = data.scanValue()!
let headersize = MemoryLayout<Header>.size
self.buffer = data.subdata(in: NSRange(location: headersize, length: data.count - headersize))
self.buffer = data.subdata(in: headersize..<data.count)
}
}
@@ -89,7 +85,7 @@ extension SMB2 {
// MARK: SMB2 Write
struct WriteRequest: SMBRequest {
struct WriteRequest: SMBRequestBody {
let header: WriteRequest.Header
let channelInfo: ChannelInfo?
let fileData: Data
@@ -124,7 +120,7 @@ extension SMB2 {
}
func data() -> Data {
var result = NSData(data: encode(self.header)) as Data
var result = Data(value: self.header)
if let channelInfo = channelInfo {
result.append(channelInfo.data())
}
@@ -144,41 +140,29 @@ extension SMB2 {
}
}
struct WriteResponse: SMBResponse {
struct WriteResponse: SMBResponseBody {
let size: UInt16
fileprivate let reserved: UInt16
let writtenBytes: UInt32
fileprivate let remaining: UInt32
fileprivate let channelInfoOffset: UInt16
fileprivate let channelInfoLength: UInt16
init?(data: Data) {
self = decode(data)
}
}
struct ChannelInfo: SMBRequest {
struct ChannelInfo: SMBRequestBody {
let offset: UInt64
let token: UInt32
let length: UInt32
func data() -> Data {
return encode(data)
}
}
// MARK: SMB2 Lock
struct LockElement: SMBRequest {
struct LockElement: SMBRequestBody {
let offset: UInt64
let length: UInt64
let flags: LockElement.Flags
fileprivate let reserved: UInt32
func data() -> Data {
return encode(self)
}
struct Flags: OptionSet {
let rawValue: UInt32
@@ -193,7 +177,7 @@ extension SMB2 {
}
}
struct LockRequest: SMBRequest {
struct LockRequest: SMBRequestBody {
let header: LockRequest.Header
let locks: [LockElement]
@@ -203,9 +187,9 @@ extension SMB2 {
}
func data() -> Data {
var result = NSData(data: encode(header)) as Data
var result = Data(value: header)
for lock in locks {
result.append(encode(lock))
result.append(Data(value: lock))
}
return result
}
@@ -218,7 +202,7 @@ extension SMB2 {
}
}
struct LockResponse: SMBResponse {
struct LockResponse: SMBResponseBody {
let size: UInt16
let reserved: UInt16
@@ -226,15 +210,11 @@ extension SMB2 {
self.size = 4
self.reserved = 0
}
init? (data: Data) {
self = decode(data)
}
}
// MARK: SMB2 Cancel
struct CancelRequest: SMBRequest {
struct CancelRequest: SMBRequestBody {
let size: UInt16
let reserved: UInt16
@@ -242,9 +222,5 @@ extension SMB2 {
self.size = 4
self.reserved = 0
}
func data() -> Data {
return encode(self)
}
}
}
+24 -57
View File
@@ -15,7 +15,7 @@ extension SMB2 {
* IOCtl usage is usually limited in SMB to pipe requests and duplicating file inside server
*/
struct IOCtlRequest: SMBRequest {
struct IOCtlRequest: SMBRequestBody {
let header: Header
let requestData: IOCtlRequestProtocol?
@@ -26,7 +26,7 @@ extension SMB2 {
}
func data() -> Data {
var result = NSData(data: encode(self.header)) as Data
var result = Data(value: self.header)
if let reqData = requestData?.data() {
result.append(reqData)
}
@@ -63,14 +63,14 @@ extension SMB2 {
}
}
struct IOCtlResponse: SMBResponse {
struct IOCtlResponse: SMBResponseBody {
let header: Header
let responseData: IOCtlResponseProtocol?
init?(data: Data) {
self.header = decode(data)
let responseRange = NSRange(location: Int(self.header.outputOffset - 64), length: Int(self.header.outputCount))
let response = data.subdata(in: responseRange)
self.header = data.scanValue()!
let endRange = Int(self.header.outputOffset - 64) + Int(self.header.outputCount)
let response = data.subdata(in: Int(self.header.outputOffset - 64)..<endRange)
switch self.header.ctlCode {
case .SRV_COPYCHUNK, .SRV_COPYCHUNK_WRITE:
self.responseData = IOCtlResponseData.SrvCopyChunk(data: response)
@@ -140,10 +140,10 @@ extension SMB2 {
let chunks: [Chunk]
func data() -> Data {
var result = NSData(data: encode(sourceKey)) as Data
result.append(encode(chunkCount))
var reserved: UInt32 = 0
result.append(encode(&reserved))
var result = Data(value: sourceKey)
result.append(Data(value: chunkCount))
let reserved: UInt32 = 0
result.append(Data(value: reserved))
return Data()
}
@@ -152,10 +152,6 @@ extension SMB2 {
let targetOffset: UInt64
let length: UInt32
fileprivate let reserved: UInt32
func data() -> Data {
return encode(self)
}
}
}
@@ -182,10 +178,6 @@ extension SMB2 {
self.length = length
self.offset = offset
}
func data() -> Data {
return encode(self)
}
}
struct ResilencyRequest: IOCtlRequestProtocol {
@@ -197,10 +189,6 @@ extension SMB2 {
self.timeout = timeout
self.reserved = 0
}
func data() -> Data {
return encode(self)
}
}
struct ValidateNegotiateInfo: IOCtlRequestProtocol {
@@ -213,8 +201,8 @@ extension SMB2 {
}
func data() -> Data {
var result = NSData(data: encode(self.header)) as Data
dialects.forEach { result.append(encode($0)) }
var result = Data(value: self.header)
dialects.forEach { result.append(Data(value: $0)) }
return result
}
@@ -234,10 +222,6 @@ extension SMB2 {
let chunksCount: UInt32
let chunksBytesWritten: UInt32
let totalBytesWriiten: UInt32
init?(data: Data) {
self = decode(data)
}
}
// SRV_ENUMERATE_SNAPSHOTS
@@ -247,8 +231,9 @@ extension SMB2 {
let snapshots: [SMBTime]
init?(data: Data) {
self.count = decode(data)
self.returnedCount = decode(data.subdata(in: NSRange(location: 4, length: 4)))
guard data.count > 8 else { return nil }
self.count = data.scanValue()!
self.returnedCount = data.scanValue(start: 4)!
//let size: UInt32 = decode(data.subdataWithRange(NSRange(location: 8, length: 4)))
var snapshots = [SMBTime]()
let dateFormatter = DateFormatter()
@@ -258,7 +243,7 @@ extension SMB2 {
if data.count < offset + 48 {
return nil
}
let datestring = String(data: data.subdata(in: NSRange(location: offset, length: 48)), encoding: String.Encoding.utf16)
let datestring = data.scanString(start: offset, length: 48, encoding: .utf16)
if let datestring = datestring, let date = dateFormatter.date(from: datestring) {
snapshots.append(SMBTime(date: date))
}
@@ -271,32 +256,21 @@ extension SMB2 {
let key: (UInt64, UInt64, UInt64)
fileprivate let contextLength: UInt32
fileprivate let context: UInt32
init?(data: Data) {
self = decode(data)
}
}
struct ReadHash: IOCtlResponseProtocol {
// TODO: Implement IOCTL READ_HASH
init?(data: Data) {
self = decode(data)
}
}
struct NetworkInterfaceInfo: IOCtlResponseProtocol {
let items: [NetworkInterfaceInfo.Item]
init?(data: Data) {
let count = data.count / MemoryLayout<Item>.size
guard count > 0 else {
return nil
}
var items = [Item]()
for i in 0..<count {
let itemdata = data.subdata(in: NSRange(location: i * MemoryLayout<Item>.size, length: MemoryLayout<Item>.size))
items.append(decode(itemdata))
var offset = 0
while let item: Item = data.scanValue(start: offset) {
items.append(item)
offset += MemoryLayout<Item>.size
}
self.items = items
}
@@ -310,7 +284,8 @@ extension SMB2 {
fileprivate let reserved: UInt32
/// Speed of the network interface in bits per second
let linkSpeed: UInt64
fileprivate let sockaddrStorage: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
fileprivate let sockaddrStorage:
(UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
@@ -334,15 +309,11 @@ extension SMB2 {
static let ipv6: sa_family_t = 0x17
var sockaddr: sockaddr_in {
var sockaddrStorage = self.sockaddrStorage
let data = Data(bytes: &sockaddrStorage, count: 16)
return decode(data)
return Data.mapMemory(from: self.sockaddrStorage)!
}
var sockaddr6: sockaddr_in6 {
var sockaddrStorage = self.sockaddrStorage
let data = Data(bytes: &sockaddrStorage, count: 28)
return decode(data)
return Data.mapMemory(from: self.sockaddrStorage)!
}
}
}
@@ -355,10 +326,6 @@ extension SMB2 {
var dialect: (major: Int, minor: Int) {
return (major: Int(_dialect & 0xFF), minor: Int(_dialect >> 8))
}
init?(data: Data) {
self = decode(data)
}
}
}
+9 -16
View File
@@ -11,7 +11,7 @@ import Foundation
extension SMB2 {
// MARK: SMB2 Change Notify
struct ChangeNotifyRequest: SMBRequest {
struct ChangeNotifyRequest: SMBRequestBody {
let size: UInt16
let flags: ChangeNotifyRequest.Flags
let outputBufferLength: UInt32
@@ -28,10 +28,6 @@ extension SMB2 {
self.reserved = 0
}
func data() -> Data {
return encode(self)
}
struct Flags: OptionSet {
let rawValue: UInt16
@@ -79,7 +75,7 @@ extension SMB2 {
}
}
struct ChangeNotifyResponse: SMBResponse {
struct ChangeNotifyResponse: SMBResponseBody {
let notifications: [(action: FileNotifyAction, fileName: String)]
init?(data: Data) {
@@ -87,22 +83,19 @@ extension SMB2 {
var i = 0
var result = [(action: FileNotifyAction, fileName: String)]()
var offset: UInt32 = 0
var offset = 0
while i < maxLoop {
let actionData = data.subdata(in: NSRange(location: Int(offset + 4), length: 4))
let actionValue: UInt32 = decode(actionData)
let nextOffset: UInt32 = data.scanValue(start: offset) ?? 0
let actionValue: UInt32 = data.scanValue(start: offset + 4) ?? 0
guard let action = FileNotifyAction(rawValue: actionValue) else {
continue
}
let fileLenData = data.subdata(in: NSRange(location: Int(offset + 8), length: 4))
let fileNameLen: UInt32 = decode(fileLenData)
let fileNameData = data.subdata(in: NSRange(location: Int(offset + 12), length: Int(12 + fileNameLen)))
let fileName = String(data: fileNameData, encoding: String.Encoding.utf16) ?? ""
let fileNameLen = Int(data.scanValue(start: offset + 8) as UInt32? ?? 0)
let fileName = data.scanString(start: offset + 12, length: fileNameLen, encoding: .utf16) ?? ""
result.append((action: action, fileName: fileName))
let nextOffsetData = data.subdata(in: NSRange(location: Int(offset), length: 4))
let nextOffset: UInt32 = decode(nextOffsetData)
offset += nextOffset
offset += Int(nextOffset)
if nextOffset == 0 {
break
}
+67 -81
View File
@@ -11,7 +11,7 @@ import Foundation
extension SMB2 {
// MARK: SMB2 Query Directory
struct QueryDirectoryRequest: SMBRequest {
struct QueryDirectoryRequest: SMBRequestBody {
let header: QueryDirectoryRequest.Header
let searchPattern: String?
@@ -22,14 +22,14 @@ extension SMB2 {
assert(FileInformationEnum.queryDirectory.contains(infoClass), "Invalid FileInformationClass used for QueryDirectoryRequest")
let searchPatternOffset = searchPattern != nil ? MemoryLayout<SMB2.Header>.size + MemoryLayout<QueryDirectoryRequest.Header>.size : 0
let nflags = flags.intersection(fileIndex > 0 ? [.INDEX_SPECIFIED] : [])
let searchPatternLength = searchPattern?.data(using: String.Encoding.utf16)?.count ?? 0
let searchPatternLength = searchPattern?.data(using: .utf16)?.count ?? 0
self.header = Header(size: 53, infoClass: infoClass, flags: nflags, fileIndex: fileIndex, fileId: fileId, searchPatternOffset: UInt8(searchPatternOffset), searchPatternLength: UInt8(searchPatternLength), bufferLength: bufferLength)
self.searchPattern = searchPattern
}
func data() -> Data {
var result = NSData(data: encode(header)) as Data
if let patternData = searchPattern?.data(using: String.Encoding.utf16) {
var result = Data(value: header)
if let patternData = searchPattern?.data(using: .utf16) {
result.append(patternData)
}
return result
@@ -60,7 +60,7 @@ extension SMB2 {
}
}
struct QueryDirectoryResponse: SMBResponse {
struct QueryDirectoryResponse: SMBResponseBody {
let buffer: Data
func parseAs(type: FileInformationEnum) -> [(header: SMB2FilesInformationHeader, fileName: String)] {
@@ -70,34 +70,22 @@ extension SMB2 {
let header: SMB2FilesInformationHeader
switch type {
case .fileDirectoryInformation:
let headerData = buffer.subdata(in: NSRange(location: offset, length: MemoryLayout<FileDirectoryInformationHeader>.size))
let h: FileDirectoryInformationHeader = decode(headerData)
header = h
header = buffer.scanValue(start: offset) as FileDirectoryInformationHeader!
case .fileFullDirectoryInformation:
let headerData = buffer.subdata(in: NSRange(location: offset, length: MemoryLayout<FileFullDirectoryInformationHeader>.size))
let h: FileFullDirectoryInformationHeader = decode(headerData)
header = h
header = buffer.scanValue(start: offset) as FileFullDirectoryInformationHeader!
case .fileIdFullDirectoryInformation:
let headerData = buffer.subdata(in: NSRange(location: offset, length: MemoryLayout<FileIdFullDirectoryInformationHeader>.size))
let h: FileIdFullDirectoryInformationHeader = decode(headerData)
header = h
header = buffer.scanValue(start: offset) as FileIdFullDirectoryInformationHeader!
case .fileBothDirectoryInformation:
let headerData = buffer.subdata(in: NSRange(location: offset, length: MemoryLayout<FileBothDirectoryInformationHeader>.size))
let h: FileBothDirectoryInformationHeader = decode(headerData)
header = h
header = buffer.scanValue(start: offset) as FileBothDirectoryInformationHeader!
case .fileIdBothDirectoryInformation:
let headerData = buffer.subdata(in: NSRange(location: offset, length: MemoryLayout<FileIdBothDirectoryInformationHeader>.size))
let h: FileIdBothDirectoryInformationHeader = decode(headerData)
header = h
header = buffer.scanValue(start: offset) as FileIdBothDirectoryInformationHeader!
case .fileNamesInformation:
let headerData = buffer.subdata(in: NSRange(location: offset, length: MemoryLayout<FileNamesInformationHeader>.size))
let h: FileNamesInformationHeader = decode(headerData)
header = h
header = buffer.scanValue(start: offset) as FileNamesInformationHeader!
default:
return []
}
let fnData = buffer.subdata(in: NSRange(location: offset + MemoryLayout.size(ofValue: header), length: Int(header.fileNameLength)))
let fileName = String(data: fnData, encoding: String.Encoding.utf16) ?? ""
let headersize = MemoryLayout.size(ofValue: header)
let fileName = buffer.scanString(start: headersize, length: Int(header.fileNameLength), encoding: .utf16) ?? ""
result.append((header: header, fileName: fileName))
if header.nextEntryOffset == 0 {
break
@@ -108,18 +96,18 @@ extension SMB2 {
}
init? (data: Data) {
let offset: UInt16 = decode(data.subdata(in: NSRange(location: 2, length: 2)))
let length: UInt32 = decode(data.subdata(in: NSRange(location: 4, length: 4)))
guard data.count > Int(offset) + Int(length) else {
let offset = Int(data.scanValue(start: 2) as UInt16!)
let length = Int(data.scanValue(start: 4) as UInt32!)
guard data.count > offset + length else {
return nil
}
self.buffer = data.subdata(in: NSRange(location: Int(offset), length: Int(length)))
self.buffer = data.subdata(in: offset..<(offset + length))
}
}
// MARK: SMB2 Query Info
struct QueryInfoRequest: SMBRequest {
struct QueryInfoRequest: SMBRequestBody {
let header: Header
let buffer: Data?
@@ -129,22 +117,24 @@ extension SMB2 {
}
init(fileId: FileId, extendedAttributes: [String], flags: Flags = [], outputBufferLength: UInt32 = 65535) {
let buffer = NSMutableData()
var buffer = Data()
for ea in extendedAttributes {
let strData = ea.data(using: String.Encoding.ascii)!
guard let strData = ea.data(using: .ascii) else {
continue
}
let strLength = UInt8(strData.count)
let nextOffset = UInt32(4 + 1 + strData.count)
let data = (encode(nextOffset) as NSData).mutableCopy() as! NSMutableData
data.append(encode(strLength))
var data = Data(value: nextOffset)
data.append(Data(value: strLength))
data.append(strData)
data.length += 1
let padSize = (data.length) % 4
data.length += padSize
data.count += 1
let padSize = (data.count) % 4
data.count += padSize
buffer.append(data as Data)
}
let bufferOffset = UInt16(MemoryLayout<SMB2.Header>.size + MemoryLayout<QueryInfoRequest.Header>.size)
self.header = Header(size: 41, infoType: 1, infoClass: FileInformationEnum.fileFullEaInformation.rawValue, outputBufferLength: outputBufferLength, inputBufferOffset: bufferOffset, reserved: 0, inputBufferLength: UInt32(buffer.length), additionalInformation: [], flags: flags, fileId: fileId)
self.header = Header(size: 41, infoType: 1, infoClass: FileInformationEnum.fileFullEaInformation.rawValue, outputBufferLength: outputBufferLength, inputBufferOffset: bufferOffset, reserved: 0, inputBufferLength: UInt32(buffer.count), additionalInformation: [], flags: flags, fileId: fileId)
self.buffer = buffer as Data
}
@@ -161,8 +151,7 @@ extension SMB2 {
// TODO: Implement QUOTA_INFO init
func data() -> Data {
let headerData = encode(header)
var result = NSData(data: headerData) as Data
var result = Data(value: header)
if let buffer = buffer {
result.append(buffer)
}
@@ -195,12 +184,11 @@ extension SMB2 {
}
}
struct QueryInfoResponse: SMBResponse {
struct QueryInfoResponse: SMBResponseBody {
let buffer: Data
init?(data: Data) {
let structSizeData = data.subdata(in: NSRange(location: 0, length: 2))
let structSize: UInt16 = decode(structSizeData)
let structSize: UInt16 = data.scanValue()!
guard structSize == 9 else {
return nil
}
@@ -208,50 +196,48 @@ extension SMB2 {
/*let offsetData = data.subdataWithRange(NSRange(location: 2, length: 2))
let offset: UInt16 = decode(offsetData)*/
let lengthData = data.subdata(in: NSRange(location: 4, length: 4))
let length: UInt32 = decode(lengthData)
let length = Int(data.scanValue(start: 4) as UInt32!)
guard data.count >= 8 + Int(length) else {
guard data.count >= 8 + length else {
return nil
}
self.buffer = data.subdata(in: NSRange(location: 8, length: Int(length)))
self.buffer = data.subdata(in: 8..<(8 + length))
}
var asAccessInformation: FileAccessInformation {
return decode(buffer)
return buffer.scanValue()!
}
var asAlignmentInformation: FileAlignmentInformation {
return decode(buffer)
return buffer.scanValue()!
}
var asAllInformation: (header: FileAllInformationHeader, name: String) {
let header: FileAllInformationHeader = decode(buffer)
let nameData = buffer.subdata(in: NSRange(location: MemoryLayout<FileAllInformationHeader>.size, length: Int(header.nameLength)))
let name = String(data: nameData, encoding: String.Encoding.utf16) ?? ""
let header: FileAllInformationHeader = buffer.scanValue()!
let headersize = MemoryLayout<FileAllInformationHeader>.size
let name = buffer.scanString(start: headersize, length: Int(header.nameLength), encoding: .utf16) ?? ""
return (header, name)
}
var asAlternateNameInformation: String {
let b = (buffer as NSData).bytes.bindMemory(to: CChar.self, capacity: buffer.count)
return String(cString: b, encoding: String.Encoding.utf16) ?? ""
return buffer.scanString(start: 0, length: buffer.count, encoding: .utf16) ?? ""
}
var asAttributeTagInformation: FileAttributeTagInformation {
return decode(buffer)
return buffer.scanValue()!
}
var asBasicInformation: FileBasicInformation {
return decode(buffer)
return buffer.scanValue()!
}
var asCompressionInformation: FileCompressionInformation {
return decode(buffer)
return buffer.scanValue()!
}
var asEaInformation: FileEaInformation {
return decode(buffer)
return buffer.scanValue()!
}
var asFullEaInformation: FileFullEaInformation {
@@ -260,80 +246,80 @@ extension SMB2 {
}
var asInternalInformation: FileInternalInformation {
return decode(buffer)
return buffer.scanValue()!
}
var asModeInformation: FileModeInformation {
return decode(buffer)
return buffer.scanValue()!
}
var asNetworkOpenInformation: FileNetworkOpenInformation {
return decode(buffer)
return buffer.scanValue()!
}
var asPipeInformation: FilePipeInformation {
return decode(buffer)
return buffer.scanValue()!
}
var asPipeLocalInformation: FilePipeLocalInformation {
return decode(buffer)
return buffer.scanValue()!
}
var asPipeRemoteInformation: FilePipeRemoteInformation {
return decode(buffer)
return buffer.scanValue()!
}
var asPositionInformation: FilePositionInformation {
return decode(buffer)
return buffer.scanValue()!
}
var asStandardInformation: FileStandardInformation {
return decode(buffer)
return buffer.scanValue()!
}
var asStreamInformation: (header: FileStreamInformationHeader, name: String) {
let header: FileStreamInformationHeader = decode(buffer)
let nameData = buffer.subdata(in: NSRange(location: MemoryLayout<FileStreamInformationHeader>.size, length: Int(header.streamNameLength)))
let name = String(data: nameData, encoding: String.Encoding.utf16) ?? ""
let header: FileStreamInformationHeader = buffer.scanValue()!
let headersize = MemoryLayout<FileStreamInformationHeader>.size
let name = buffer.scanString(start: headersize, length: Int(header.streamNameLength), encoding: .utf16) ?? ""
return (header, name)
}
var asFsVolumeInformation: (header: FileFsVolumeInformationHeader, name: String) {
let header: FileFsVolumeInformationHeader = decode(buffer)
let nameData = buffer.subdata(in: NSRange(location: MemoryLayout<FileFsVolumeInformationHeader>.size, length: Int(header.labelLength)))
let name = String(data: nameData, encoding: String.Encoding.utf16) ?? ""
let header: FileFsVolumeInformationHeader = buffer.scanValue()!
let headersize = MemoryLayout<FileFsVolumeInformationHeader>.size
let name = buffer.scanString(start: headersize, length: Int(header.labelLength), encoding: .utf16) ?? ""
return (header, name)
}
var asFsSizeInformation: FileFsSizeInformation {
return decode(buffer)
return buffer.scanValue()!
}
var asFsDeviceInformation: FileFsDeviceInformation {
return decode(buffer)
return buffer.scanValue()!
}
var asFsAttributeInformation: (header: FileFsAttributeInformationHeader, name: String) {
let header: FileFsAttributeInformationHeader = decode(buffer)
let nameData = buffer.subdata(in: NSRange(location: MemoryLayout<FileFsAttributeInformationHeader>.size, length: Int(header.nameLength)))
let name = String(data: nameData, encoding: String.Encoding.utf16) ?? ""
let header: FileFsAttributeInformationHeader = buffer.scanValue()!
let headersize = MemoryLayout<FileFsAttributeInformationHeader>.size
let name = buffer.scanString(start: headersize, length: Int(header.nameLength), encoding: .utf16) ?? ""
return (header, name)
}
var asFsControlInformation: FileFsControlInformation {
return decode(buffer)
return buffer.scanValue()!
}
var asFsFullSizeInformation: FileFsFullSizeInformation {
return decode(buffer)
return buffer.scanValue()!
}
var asFsObjectIdInformation: FileFsObjectIdInformation {
return decode(buffer)
return buffer.scanValue()!
}
var asFsSectorSizeInformation: FileFsSectorSizeInformation {
return decode(buffer)
return buffer.scanValue()!
}
}
}
+9 -35
View File
@@ -8,7 +8,7 @@
import Foundation
protocol SMB2FilesInformationHeader: SMBResponse {
protocol SMB2FilesInformationHeader: SMBResponseBody {
var nextEntryOffset: UInt32 { get }
var fileIndex: UInt32 { get }
var fileNameLength : UInt32 { get }
@@ -16,7 +16,7 @@ protocol SMB2FilesInformationHeader: SMBResponse {
extension SMB2 {
enum FileInformationEnum: UInt8 {
case `nil` = 0x00
case none = 0x00
case fileDirectoryInformation = 0x01
case fileFullDirectoryInformation = 0x02
case fileBothDirectoryInformation = 0x03
@@ -88,7 +88,7 @@ extension SMB2 {
}
enum FileSystemInformationEnum: UInt8 {
case `nil` = 0
case none = 0
case fileFsAttributeInformation
case fileFsControlInformation
case fileFsDeviceInformation
@@ -127,10 +127,6 @@ extension SMB2 {
let allocationSize: UInt64
let fileAttributes: FileAttributes
let fileNameLength : UInt32
init?(data: Data) {
self = decode(data)
}
}
struct FileFullDirectoryInformationHeader: SMB2FilesInformationHeader {
@@ -145,10 +141,6 @@ extension SMB2 {
let fileAttributes: FileAttributes
let fileNameLength : UInt32
let extendedAttributesSize: UInt32
init?(data: Data) {
self = decode(data)
}
}
struct FileIdFullDirectoryInformationHeader: SMB2FilesInformationHeader {
@@ -165,10 +157,6 @@ extension SMB2 {
let extendedAttributesSize: UInt32
fileprivate let reserved: UInt32
let fileId: FileId
init?(data: Data) {
self = decode(data)
}
}
struct FileBothDirectoryInformationHeader: SMB2FilesInformationHeader {
@@ -187,14 +175,9 @@ extension SMB2 {
fileprivate let reserved: UInt8
fileprivate let _shortName: FileShortNameType
var shortName: String? {
let s = encode(_shortName)
var d = NSData(data: s) as Data
d.count = Int(shortNameLen)
return String(data: d, encoding: String.Encoding.utf16)
}
init?(data: Data) {
self = decode(data)
var data = Data(value: _shortName)
data.count = Int(shortNameLen)
return String(data: data, encoding: .utf16)
}
}
@@ -214,27 +197,18 @@ extension SMB2 {
fileprivate let reserved: UInt8
fileprivate let _shortName: FileShortNameType
var shortName: String? {
let s = encode(_shortName)
var d = NSData(data: s) as Data
d.count = Int(shortNameLen)
return String(data: d, encoding: String.Encoding.utf16)
var data = Data(value: _shortName)
data.count = Int(shortNameLen)
return String(data: data, encoding: .utf16)
}
fileprivate let reserved2: UInt16
let fileId : FileId
init?(data: Data) {
self = decode(data)
}
}
struct FileNamesInformationHeader: SMB2FilesInformationHeader {
let nextEntryOffset: UInt32
let fileIndex: UInt32
let fileNameLength : UInt32
init?(data: Data) {
self = decode(data)
}
}
typealias FileShortNameType = (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
+23 -41
View File
@@ -11,7 +11,7 @@ import Foundation
extension SMB2 {
// MARK: SMB2 Negotiating
struct NegotiateRequest: SMBRequest {
struct NegotiateRequest: SMBRequestBody {
let header: NegotiateRequest.Header
let dialects: [UInt16]
let contexts: [(type: NegotiateContextType, data: Data)]
@@ -31,25 +31,23 @@ extension SMB2 {
func data() -> Data {
var header = self.header
header.dialectCount = UInt16(dialects.count)
let dialectData = NSMutableData()
var dialectData = Data()
for dialect in dialects {
var dialect = dialect
dialectData.append(&dialect, length: 2)
dialectData.append(UnsafeBufferPointer(start: &dialect, count: 2))
}
let pad = ((1024 - dialectData.length) % 8)
dialectData.increaseLength(by: pad)
header.contextOffset = UInt32(MemoryLayout<NegotiateRequest.Header>.size) + UInt32(dialectData.length)
let pad = ((1024 - dialectData.count) % 8)
dialectData.count += pad
header.contextOffset = UInt32(MemoryLayout<NegotiateRequest.Header>.size) + UInt32(dialectData.count)
header.contextCount = UInt16(contexts.count)
let contextData = NSMutableData()
var contextData = Data()
for context in contexts {
var contextType = context.type.rawValue
contextData.append(&contextType, length: 2)
var dataLen = UInt16(context.data.count)
contextData.increaseLength(by: 4)
contextData.append(&dataLen, length: 2)
contextData.append(Data(value: context.type.rawValue))
contextData.count += 4
contextData.append(Data(value: UInt16(context.data.count)))
}
var result = NSData(data: encode(&header)) as Data
var result = Data(value: header)
result.append(dialectData as Data)
result.append(contextData as Data)
return result
@@ -91,23 +89,23 @@ extension SMB2 {
}
}
struct NegotiateResponse: SMBResponse {
struct NegotiateResponse: SMBResponseBody {
let header: NegotiateResponse.Header
let buffer: Data?
let contexts: [(type: NegotiateContextType, data: Data)]
init? (data: Data) {
if data.count < 64 {
guard data.count >= 64 else {
return nil
}
self.header = decode(data)
self.header = data.scanValue()!
if Int(header.size) != 65 {
return nil
}
let bufOffset = Int(self.header.bufferOffset) - MemoryLayout<SMB2.Header>.size
let bufLen = Int(self.header.bufferLength)
if bufOffset > 0 && bufLen > 0 && data.count >= bufOffset + bufLen {
self.buffer = data.subdata(in: NSRange(location: bufOffset, length: bufLen))
self.buffer = data.subdata(in: bufOffset..<(bufOffset + bufLen))
} else {
self.buffer = nil
}
@@ -176,7 +174,7 @@ extension SMB2 {
// MARK: SMB2 Session Setup
struct SessionSetupRequest: SMBRequest {
struct SessionSetupRequest: SMBRequestBody {
let header: SessionSetupRequest.Header
let buffer: Data?
@@ -194,7 +192,7 @@ extension SMB2 {
var header = self.header
header.bufferOffset = UInt16(MemoryLayout<SMB2.Header>.size + MemoryLayout<SessionSetupRequest.Header>.size)
header.bufferLength = UInt16(buffer?.count ?? 0)
var result = NSData(data: encode(&header)) as Data
var result = Data(value: header)
if let buffer = self.buffer {
result.append(buffer)
}
@@ -235,22 +233,22 @@ extension SMB2 {
}
}
struct SessionSetupResponse: SMBResponse {
struct SessionSetupResponse: SMBResponseBody {
let header: SessionSetupResponse.Header
let buffer: Data?
init? (data: Data) {
if data.count < 64 {
guard data.count >= 64 else {
return nil
}
self.header = decode(data)
self.header = data.scanValue()!
if Int(header.size) != 9 {
return nil
}
let bufOffset = Int(self.header.bufferOffset) - MemoryLayout<SMB2.Header>.size
let bufLen = Int(self.header.bufferLength)
if bufOffset > 0 && bufLen > 0 && data.count >= bufOffset + bufLen {
self.buffer = data.subdata(in: NSRange(location: bufOffset, length: bufLen))
self.buffer = data.subdata(in: bufOffset..<(bufOffset + bufLen))
} else {
self.buffer = nil
}
@@ -289,7 +287,7 @@ extension SMB2 {
// MARK: SMB2 Log off
struct LogOff: SMBRequest, SMBResponse {
struct LogOff: SMBRequestBody, SMBResponseBody {
let size: UInt16
let reserved: UInt16
@@ -297,19 +295,11 @@ extension SMB2 {
self.size = 4
self.reserved = 0
}
init? (data: Data) {
self = decode(data)
}
func data() -> Data {
return encode(self)
}
}
// MARK: SMB2 Echo
struct Echo: SMBRequest, SMBResponse {
struct Echo: SMBRequestBody, SMBResponseBody {
let size: UInt16
let reserved: UInt16
@@ -317,13 +307,5 @@ extension SMB2 {
self.size = 4
self.reserved = 0
}
init? (data: Data) {
self = decode(data)
}
func data() -> Data {
return encode(self)
}
}
}
+5 -9
View File
@@ -10,14 +10,14 @@ import Foundation
extension SMB2 {
// MARK: SMB2 Set Info
struct SetInfoRequest: SMBRequest {
struct SetInfoRequest: SMBRequestBody {
let header: Header
let buffer: Data?
func data() -> Data {
return Data()
var result = Data(value: header)
result.append(buffer ?? Data())
return result
}
struct Header {
@@ -32,15 +32,11 @@ extension SMB2 {
}
}
struct SetInfoResponse: SMBResponse {
struct SetInfoResponse: SMBResponseBody {
let size: UInt16
init() {
self.size = 2
}
init? (data: Data) {
self = decode(data)
}
}
}
+5 -20
View File
@@ -11,7 +11,7 @@ import Foundation
extension SMB2 {
// MARK: SMB2 Tree Connect
struct TreeConnectRequest: SMBRequest {
struct TreeConnectRequest: SMBRequestBody {
let header: TreeConnectRequest.Header
let buffer: Data?
var path: String {
@@ -27,14 +27,14 @@ extension SMB2 {
}
self.header = header
let path = "\\\\\(host)\\\(share)"
self.buffer = path.data(using: String.Encoding.utf16)
self.buffer = path.data(using: .utf16)
}
func data() -> Data {
var header = self.header
header.pathOffset = UInt16(MemoryLayout<SMB2.Header>.size + MemoryLayout<TreeConnectRequest.Header>.size)
header.pathLength = UInt16(buffer?.count ?? 0)
var result = NSData(data: encode(&header)) as Data
var result = Data(value: header)
if let buffer = self.buffer {
result.append(buffer)
}
@@ -66,7 +66,7 @@ extension SMB2 {
}
}
struct TreeConnectResponse: SMBResponse {
struct TreeConnectResponse: SMBResponseBody {
let size: UInt16 // = 16
fileprivate let _type: UInt8
var type: ShareType {
@@ -77,13 +77,6 @@ extension SMB2 {
let capabilities: TreeConnectResponse.Capabilities
let maximalAccess: FileAccessMask
init? (data: Data) {
if data.count != 16 {
return nil
}
self = decode(data)
}
enum ShareType: UInt8 {
case UNKNOWN = 0x00
case DISK = 0x01
@@ -131,7 +124,7 @@ extension SMB2 {
// MARK: SMB2 Tree Disconnect
struct TreeDisconnect: SMBRequest, SMBResponse {
struct TreeDisconnect: SMBRequestBody, SMBResponseBody {
let size: UInt16
let reserved: UInt16
@@ -139,13 +132,5 @@ extension SMB2 {
self.size = 4
self.reserved = 0
}
init? (data: Data) {
self = decode(data)
}
func data() -> Data {
return encode(self)
}
}
}
+1 -26
View File
@@ -8,26 +8,6 @@
import Foundation
internal func encode<T>(_ value: inout T) -> Data {
return withUnsafePointer(to: &value) { p in
NSData(bytes: p, length: MemoryLayout.size(ofValue: value)) as Data
}
}
internal func encode<T>(_ value: T) -> Data {
var value = value
return withUnsafePointer(to: &value) { p in
NSData(bytes: p, length: MemoryLayout.size(ofValue: value)) as Data
}
}
internal func decode<T>(_ data: Data) -> T {
let pointer = UnsafeMutablePointer<T>.allocate(capacity: MemoryLayout<T.Type>.size)
(data as NSData).getBytes(pointer, length: MemoryLayout<T.Type>.size)
return pointer.move()
}
protocol FileProviderSMBHeader {
var protocolID: UInt32 { get }
static var protocolConst: UInt32 { get }
@@ -155,10 +135,5 @@ struct SMB2 {
// MARK: SMB2 Oplock Break
}
extension Data {
func subdata(in range: NSRange) -> Data {
return (self as NSData).subdata(with: range)
}
}
+1 -1
View File
@@ -10,7 +10,7 @@ import Foundation
/// Error Types and Description
public enum NTStatus: UInt32, Error, CustomStringConvertible {
enum NTStatus: UInt32, Error, CustomStringConvertible {
case SUCCESS = 0x00000000
case NOT_IMPLEMENTED = 0xC0000002
case INVALID_DEVICE_REQUEST = 0xC0000010
+307 -243
View File
@@ -7,76 +7,58 @@
//
import Foundation
fileprivate func < <T : Comparable>(lhs: T?, rhs: T?) -> Bool {
switch (lhs, rhs) {
case let (l?, r?):
return l < r
case (nil, _?):
return true
default:
return false
}
}
fileprivate func >= <T : Comparable>(lhs: T?, rhs: T?) -> Bool {
switch (lhs, rhs) {
case let (l?, r?):
return l >= r
default:
return !(lhs < rhs)
}
}
/// Because this class uses NSURLSession, it's necessary to disable App Transport Security
/// in case of using this class with unencrypted HTTP connection.
public final class WebDavFileObject: FileObject {
public let contentType: String
public let entryTag: String?
// codebeat:disable[ARITY]
public init(absoluteURL: URL, name: String, path: String, size: Int64 = -1, contentType: String = "", createdDate: Date? = nil, modifiedDate: Date? = nil, fileType: FileType = .regular, isHidden: Bool = false, isReadOnly: Bool = false, entryTag: String? = nil) {
self.contentType = contentType
self.entryTag = entryTag
super.init(absoluteURL: absoluteURL, name: name, path: path, size: size, createdDate: createdDate, modifiedDate: modifiedDate, fileType: fileType, isHidden: isHidden, isReadOnly: isReadOnly)
}
// codebeat:enable[ARITY]
}
// Because this class uses NSURLSession, it's necessary to disable App Transport Security
// in case of using this class with unencrypted HTTP connection.
open class WebDAVFileProvider: NSObject, FileProviderBasic {
open let type: String = "WebDAV"
open let isPathRelative: Bool = true
open class WebDAVFileProvider: FileProviderBasicRemote {
open static let type: String = "WebDAV"
open let isPathRelative: Bool
open let baseURL: URL?
open var currentPath: String = ""
open var dispatch_queue: DispatchQueue {
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?
public 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?
fileprivate var session: URLSession {
public var session: URLSession {
if _session == nil {
self.sessionDelegate = SessionDelegate(fileProvider: self, credential: credential)
let queue = OperationQueue()
//queue.underlyingQueue = dispatch_queue
_session = URLSession(configuration: URLSessionConfiguration.default, delegate: sessionDelegate as URLSessionDownloadDelegate?, delegateQueue: queue)
let config = URLSessionConfiguration.default
config.urlCache = cache
config.requestCachePolicy = .returnCacheDataElseLoad
_session = URLSession(configuration: config, delegate: sessionDelegate as URLSessionDownloadDelegate?, delegateQueue: queue)
}
return _session!
}
public init? (baseURL: URL, credential: URLCredential?) {
public init? (baseURL: URL, credential: URLCredential?, cache: URLCache? = nil) {
if !["http", "https"].contains(baseURL.uw_scheme.lowercased()) {
return nil
}
self.baseURL = baseURL
dispatch_queue = DispatchQueue(label: "FileProvider.\(type)", attributes: DispatchQueue.Attributes.concurrent)
//let url = baseURL.uw_absoluteString
self.baseURL = baseURL.appendingPathComponent("")
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 {
@@ -84,58 +66,55 @@ open class WebDAVFileProvider: NSObject, FileProviderBasic {
}
open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {
let url = absoluteURL(path)
let opType = FileOperationType.fetch(path: path)
let url = self.url(of: path).appendingPathComponent("")
var request = URLRequest(url: url)
request.httpMethod = "PROPFIND"
request.setValue("1", forHTTPHeaderField: "Depth")
request.setValue("text/xml; charset=\"utf-8\"", forHTTPHeaderField: "Content-Type")
request.httpBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/></D:propfind>".data(using: String.Encoding.utf8)
request.httpBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/></D:propfind>".data(using: .utf8)
request.setValue(String(request.httpBody!.count), forHTTPHeaderField: "Content-Length")
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
runDataTask(with: request, operationHandle: RemoteOperationHandle(operationType: opType, tasks: []), completionHandler: { (data, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: url)
}
var fileObjects = [WebDavFileObject]()
if let data = data {
let xresponse = self.parseXMLResponse(data)
var fileObjects = [WebDavFileObject]()
let xresponse = DavResponse.parse(xmlResponse: data, baseURL: self.baseURL)
for attr in xresponse {
if attr.href.path == url.path {
continue
}
fileObjects.append(self.mapToFileObject(attr))
fileObjects.append(WebDavFileObject(attr))
}
completionHandler(fileObjects, responseError ?? error)
return
}
completionHandler([], responseError ?? error)
})
task.resume()
completionHandler(fileObjects, responseError ?? error)
})
}
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
let url = absoluteURL(path)
let url = self.url(of: path)
var request = URLRequest(url: url)
request.httpMethod = "PROPFIND"
request.setValue("1", forHTTPHeaderField: "Depth")
request.setValue("text/xml; charset=\"utf-8\"", forHTTPHeaderField: "Content-Type")
request.httpBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/></D:propfind>".data(using: String.Encoding.utf8)
request.httpBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/></D:propfind>".data(using: .utf8)
request.setValue(String(request.httpBody!.count), forHTTPHeaderField: "Content-Length")
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
runDataTask(with: request, completionHandler: { (data, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? HTTPURLResponse)?.statusCode, code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: url)
}
if let data = data {
let xresponse = self.parseXMLResponse(data)
let xresponse = DavResponse.parse(xmlResponse: data, baseURL: self.baseURL)
if let attr = xresponse.first {
completionHandler(self.mapToFileObject(attr), responseError ?? error)
completionHandler(WebDavFileObject(attr), responseError ?? error)
return
}
}
completionHandler(nil, responseError ?? error)
})
task.resume()
}
open func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void)) {
@@ -149,29 +128,33 @@ open class WebDAVFileProvider: NSObject, FileProviderBasic {
request.httpMethod = "PROPFIND"
request.setValue("0", forHTTPHeaderField: "Depth")
request.setValue("text/xml; charset=\"utf-8\"", forHTTPHeaderField: "Content-Type")
request.httpBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:prop><D:quota-available-bytes/><D:quota-used-bytes/></D:prop>\n</D:propfind>".data(using: String.Encoding.utf8)
request.httpBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:prop><D:quota-available-bytes/><D:quota-used-bytes/></D:prop>\n</D:propfind>".data(using: .utf8)
request.setValue(String(request.httpBody!.count), forHTTPHeaderField: "Content-Length")
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
runDataTask(with: request, completionHandler: { (data, response, error) in
var totalSize: Int64 = -1
var usedSize: Int64 = 0
if let data = data {
let xresponse = self.parseXMLResponse(data)
let xresponse = DavResponse.parse(xmlResponse: data, baseURL: self.baseURL)
if let attr = xresponse.first {
let totalSize = Int64(attr.prop["quota-available-bytes"] ?? "")
let usedSize = Int64(attr.prop["quota-used-bytes"] ?? "")
completionHandler(totalSize ?? -1, usedSize ?? 0)
return
totalSize = Int64(attr.prop["quota-available-bytes"] ?? "") ?? -1
usedSize = Int64(attr.prop["quota-used-bytes"] ?? "") ?? 0
}
}
completionHandler(-1, 0)
})
task.resume()
completionHandler(totalSize, usedSize)
})
}
open weak var fileOperationDelegate: FileOperationDelegate?
}
extension WebDAVFileProvider: FileProviderOperations {
public func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) {
let url = absoluteURL((atPath as NSString).appendingPathComponent(folderName) + "/")
@discardableResult
public func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let opType = FileOperationType.create(path: (atPath as NSString).appendingPathComponent(folderName) + "/")
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
return nil
}
let url = self.url(of: atPath).appendingPathComponent(folderName, isDirectory: true)
var request = URLRequest(url: url)
request.httpMethod = "MKCOL"
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
@@ -179,18 +162,21 @@ extension WebDAVFileProvider: FileProviderOperations {
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: url)
}
if let response = response as? HTTPURLResponse, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) , code != .ok {
completionHandler?(FileProviderWebDavError(code: code, url: url))
return
}
completionHandler?(responseError ?? error)
self.delegateNotify(.create(path: (atPath as NSString).appendingPathComponent(folderName) + "/"), error: responseError ?? error)
})
self.delegateNotify(opType, error: responseError ?? error)
})
task.taskDescription = opType.json
task.resume()
return RemoteOperationHandle(operationType: opType, tasks: [task])
}
public func create(file fileAttribs: FileObject, at path: String, contents data: Data?, completionHandler: SimpleCompletionHandler) {
let url = absoluteURL(path)
@discardableResult
public func create(file fileName: String, at path: String, contents data: Data?, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let opType = FileOperationType.create(path: (path as NSString).appendingPathComponent(fileName))
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
return nil
}
let url = self.url(of: path).appendingPathComponent(fileName)
var request = URLRequest(url: url)
request.httpMethod = "PUT"
let task = session.uploadTask(with: request, from: data, completionHandler: { (data, response, error) in
@@ -199,79 +185,92 @@ extension WebDAVFileProvider: FileProviderOperations {
responseError = FileProviderWebDavError(code: rCode, url: url)
}
completionHandler?(responseError ?? error)
self.delegateNotify(.create(path: (path as NSString).appendingPathComponent(fileAttribs.name)), error: responseError ?? error)
})
task.taskDescription = dictionaryToJSON(["type": "Create" as NSString, "source": (path as NSString).appendingPathComponent(fileAttribs.name) as NSString])
self.delegateNotify(opType, error: responseError ?? error)
})
task.taskDescription = opType.json
task.resume()
return RemoteOperationHandle(operationType: opType, tasks: [task])
}
public func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
self.copyMoveItem(move: true, path: path, toPath: toPath, overwrite: overwrite, completionHandler: completionHandler)
}
public func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
self.copyMoveItem(move: false, path: path, toPath: toPath, overwrite: overwrite, completionHandler: completionHandler)
}
fileprivate func copyMoveItem(move:Bool, path: String, toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) {
let url = absoluteURL(path)
var request = URLRequest(url: url)
if move {
request.httpMethod = "MOVE"
} else {
request.httpMethod = "COPY"
@discardableResult
public func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let opType = FileOperationType.move(source: path, destination: toPath)
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
return nil
}
request.setValue(absoluteURL(path).uw_absoluteString, forHTTPHeaderField: "Destination")
if !overwrite {
return self.doOperation(operation: opType, overwrite: overwrite, completionHandler: completionHandler)
}
@discardableResult
public func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let opType = FileOperationType.copy(source: path, destination: toPath)
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
return nil
}
return self.doOperation(operation: opType, overwrite: overwrite, completionHandler: completionHandler)
}
@discardableResult
public func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let opType = FileOperationType.remove(path: path)
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
return nil
}
return self.doOperation(operation: opType, completionHandler: completionHandler)
}
func doOperation(operation opType: FileOperationType, overwrite: Bool? = nil, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let sourceURL = self.url(of:opType.source!)
var request = URLRequest(url: sourceURL)
if let dest = opType.destination {
request.setValue(url(of:dest).absoluteString, forHTTPHeaderField: "Destination")
}
switch opType {
case .copy:
request.httpMethod = "COPY"
case .move:
request.httpMethod = "MOVE"
case .remove:
request.httpMethod = "DELETE"
default:
return nil
}
if let overwrite = overwrite, !overwrite {
request.setValue("F", forHTTPHeaderField: "Overwrite")
}
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var responseError: FileProviderWebDavError?
if let response = response as? HTTPURLResponse, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) {
defer {
let op = move ? FileOperation.move(source: path, destination: toPath) : .copy(source: path, destination: toPath)
self.delegateNotify(op, error: error)
if response.statusCode >= 300 {
responseError = FileProviderWebDavError(code: code, url: sourceURL)
}
if code == .multiStatus, let data = data {
let xresponses = self.parseXMLResponse(data)
for xresponse in xresponses where xresponse.status >= 300 {
completionHandler?(FileProviderWebDavError(code: code, url: url))
let xresponses = DavResponse.parse(xmlResponse: data, baseURL: self.baseURL)
for xresponse in xresponses where (xresponse.status ?? 0) >= 300 {
completionHandler?(FileProviderWebDavError(code: code, url: sourceURL))
}
} else {
completionHandler?(FileProviderWebDavError(code: code, url: url))
}
return
}
completionHandler?(error)
})
if (response as? HTTPURLResponse)?.statusCode ?? 0 != FileProviderHTTPErrorCode.multiStatus.rawValue {
completionHandler?(responseError ?? error)
}
self.delegateNotify(opType, error: responseError ?? error)
})
task.taskDescription = opType.json
task.resume()
return RemoteOperationHandle(operationType: opType, tasks: [task])
}
public func removeItem(path: String, completionHandler: SimpleCompletionHandler) {
let url = absoluteURL(path)
var request = URLRequest(url: url)
request.httpMethod = "DELETE"
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
if let response = response as? HTTPURLResponse, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) {
defer {
self.delegateNotify(.remove(path: path), error: error)
}
if code == .multiStatus, let data = data {
let xresponses = self.parseXMLResponse(data)
for xresponse in xresponses where xresponse.status >= 300 {
completionHandler?(FileProviderWebDavError(code: code, url: url))
}
} else {
completionHandler?(FileProviderWebDavError(code: code, url: url))
}
return
}
completionHandler?(error)
})
task.resume()
}
public func copyItem(localFile: URL, to toPath: String, completionHandler: SimpleCompletionHandler) {
let url = absoluteURL(toPath)
@discardableResult
public func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
// TODO: Make use of overwrite parameter
let opType = FileOperationType.copy(source: localFile.absoluteString, destination: toPath)
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
return nil
}
let url = self.url(of:toPath)
var request = URLRequest(url: url)
request.httpMethod = "PUT"
let task = session.uploadTask(with: request, fromFile: localFile, completionHandler: { (data, response, error) in
@@ -280,14 +279,20 @@ extension WebDAVFileProvider: FileProviderOperations {
responseError = FileProviderWebDavError(code: rCode, url: url)
}
completionHandler?(responseError ?? error)
self.delegateNotify(.move(source: localFile.uw_absoluteString, destination: toPath), error: responseError ?? error)
self.delegateNotify(opType, error: responseError ?? error)
})
task.taskDescription = dictionaryToJSON(["type": "Copy" as NSString, "source": localFile.uw_absoluteString as NSString, "dest": toPath as NSString])
task.taskDescription = opType.json
task.resume()
return RemoteOperationHandle(operationType: opType, tasks: [task])
}
public func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) {
let url = absoluteURL(path)
@discardableResult
public func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let opType = FileOperationType.copy(source: path, destination: toLocalURL.absoluteString)
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
return nil
}
let url = self.url(of:path)
let request = URLRequest(url: url)
let task = session.downloadTask(with: request, completionHandler: { (sourceFileURL, response, error) in
var responseError: FileProviderWebDavError?
@@ -303,48 +308,57 @@ extension WebDAVFileProvider: FileProviderOperations {
}
}
completionHandler?(responseError ?? error)
self.delegateNotify(opType, error: responseError ?? error)
})
task.taskDescription = dictionaryToJSON(["type": "Copy" as NSString, "source": path as NSString, "dest": toLocalURL.uw_absoluteString as NSString])
task.taskDescription = opType.json
task.resume()
return RemoteOperationHandle(operationType: opType, tasks: [task])
}
}
extension WebDAVFileProvider: FileProviderReadWrite {
public func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) {
self.contents(path: path, offset: 0, length: -1, completionHandler: completionHandler)
}
public func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) {
let url = absoluteURL(path)
@discardableResult
public func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
let opType = FileOperationType.fetch(path: path)
let url = self.url(of: path)
var request = URLRequest(url: url)
request.httpMethod = "GET"
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 task = session.dataTask(with: request, completionHandler: { (data, response, error) in
let handle = RemoteOperationHandle(operationType: opType, tasks: [])
runDataTask(with: request, operationHandle: handle, completionHandler: { (data, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: url)
}
completionHandler(data, responseError ?? error)
})
task.resume()
})
return handle
}
public func writeContents(path: String, contents data: Data, atomically: Bool = false, completionHandler: SimpleCompletionHandler) {
@discardableResult
public func writeContents(path: String, contents data: Data, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let opType = FileOperationType.modify(path: path)
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
return nil
}
// FIXME: lock destination before writing process
let url = atomically ? absoluteURL(path).uw_URLByAppendingPathExtension("tmp") : absoluteURL(path)
let url = atomically ? self.url(of: path).appendingPathExtension("tmp") : self.url(of: path)
var request = URLRequest(url: url)
request.httpMethod = "PUT"
if !overwrite {
request.setValue("F", forHTTPHeaderField: "Overwrite")
}
let task = session.uploadTask(with: request, from: data, completionHandler: { (data, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: self.absoluteURL(path))
responseError = FileProviderWebDavError(code: rCode, url: self.url(of: path))
}
defer {
self.delegateNotify(.modify(path: path), error: responseError ?? error)
self.delegateNotify(opType, error: responseError ?? error)
}
if let error = error {
completionHandler?(error)
@@ -354,33 +368,33 @@ extension WebDAVFileProvider: FileProviderReadWrite {
self.moveItem(path: (path as NSString).appendingPathExtension("tmp")!, to: path, completionHandler: completionHandler)
}
})
task.taskDescription = dictionaryToJSON(["type": "Modify" as NSString, "source": path as NSString])
task.taskDescription = opType.json
task.resume()
return RemoteOperationHandle(operationType: opType, tasks: [task])
}
public func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
let url = absoluteURL(path)
let url = self.url(of: path)
var request = URLRequest(url: url)
request.httpMethod = "PROPFIND"
//request.setValue("1", forHTTPHeaderField: "Depth")
request.setValue("text/xml; charset=\"utf-8\"", forHTTPHeaderField: "Content-Type")
request.httpBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/></D:propfind>".data(using: String.Encoding.utf8)
request.setValue(String(request.httpBody!.count), forHTTPHeaderField: "Content-Length")
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
request.httpBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/></D:propfind>".data(using: .utf8)
runDataTask(with: request, completionHandler: { (data, response, error) in
// FIXME: paginating results
var responseError: FileProviderWebDavError?
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, url: url)
}
if let data = data {
let xresponse = self.parseXMLResponse(data)
let xresponse = DavResponse.parse(xmlResponse: data, baseURL: self.baseURL)
var fileObjects = [WebDavFileObject]()
for attr in xresponse {
let path = attr.href.path
if !((path as NSString).lastPathComponent.contains(query)) {
continue
}
let fileObject = self.mapToFileObject(attr)
let fileObject = WebDavFileObject(attr)
fileObjects.append(fileObject)
foundItemHandler?(fileObject)
}
@@ -388,8 +402,7 @@ extension WebDAVFileProvider: FileProviderReadWrite {
return
}
completionHandler([], responseError ?? error)
})
task.resume()
})
}
fileprivate func registerNotifcation(path: String, eventHandler: (() -> Void)) {
@@ -406,42 +419,49 @@ extension WebDAVFileProvider: FileProviderReadWrite {
// TODO: implements methods for lock mechanism
}
extension WebDAVFileProvider: FileProvider {}
extension WebDAVFileProvider: FileProvider {
open func copy(with zone: NSZone? = nil) -> Any {
let copy = WebDAVFileProvider(baseURL: self.baseURL!, credential: self.credential, cache: self.cache)!
copy.currentPath = self.currentPath
copy.delegate = self.delegate
copy.fileOperationDelegate = self.fileOperationDelegate
copy.useCache = self.useCache
copy.validatingCache = self.validatingCache
return copy
}
}
// MARK: WEBDAV XML response implementation
internal extension WebDAVFileProvider {
struct DavResponse {
let href: URL
let hrefString: String
let status: Int?
let prop: [String: String]
fileprivate 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)
}
})
}
}
struct DavResponse {
let href: URL
let hrefString: String
let status: Int?
let prop: [String: String]
fileprivate func parseXMLResponse(_ response: Data) -> [DavResponse] {
var result = [DavResponse]()
do {
let xml = try AEXMLDocument(xml: response)
var rootnode = xml.root
var responsetag = "response"
for node in rootnode.all ?? [] where node.name.lowercased().hasSuffix("multistatus") {
rootnode = node
init? (_ node: AEXMLElement, baseURL: URL?) {
func removeSlash(_ str: String) -> String {
if str.hasPrefix("/") {
return str.substring(from: str.index(after: str.startIndex))
} else {
return str
}
for node in rootnode.children where node.name.lowercased().hasSuffix("response") {
responsetag = node.name
break
}
for responseNode in rootnode[responsetag].all ?? [] {
if let davResponse = mapNodeToDavResponse(responseNode) {
result.append(davResponse)
}
}
} catch _ {
}
return result
}
fileprivate func mapNodeToDavResponse(_ node: AEXMLElement) -> DavResponse? {
// find node names with namespace
var hreftag = "href"
var statustag = "status"
var propstattag = "propstat"
@@ -456,62 +476,106 @@ internal extension WebDAVFileProvider {
propstattag = node.name
}
}
let href = node[hreftag].value
if let href = href, let hrefURL = URL(string: href) {
var status: Int?
let statusDesc = (node[statustag].string).components(separatedBy: " ")
if statusDesc.count > 2 {
status = Int(statusDesc[1])
guard let hrefString = node[hreftag].value else { return nil }
// trying to figure out relative path out of href
let hrefURL: URL?
let newhrefString = URL(string: removeSlash(hrefString), relativeTo: baseURL)?.absoluteString ?? hrefString
let relativePath = newhrefString.replacingOccurrences(of: baseURL?.absoluteString ?? "", with: "", options: .anchored, range: nil)
hrefURL = URL(string: removeSlash(relativePath), relativeTo: baseURL) ?? baseURL
guard let href = hrefURL?.standardized else { return nil }
// reading status and properties
var status: Int?
let statusDesc = (node[statustag].string).components(separatedBy: " ")
if statusDesc.count > 2 {
status = Int(statusDesc[1])
}
var propDic = [String: String]()
let propStatNode = node[propstattag]
for node in propStatNode.children where node.name.lowercased().hasSuffix("status"){
statustag = node.name
break
}
let statusDesc2 = (propStatNode[statustag].string).components(separatedBy: " ")
if statusDesc2.count > 2 {
status = Int(statusDesc2[1])
}
var proptag = "prop"
for tnode in propStatNode.children where tnode.name.lowercased().hasSuffix("prop") {
proptag = tnode.name
break
}
for propItemNode in propStatNode[proptag].children {
propDic[propItemNode.name.components(separatedBy: ":").last!.lowercased()] = propItemNode.value
if propItemNode.name.hasSuffix("resourcetype") && propItemNode.xml.contains("collection") {
propDic["getcontenttype"] = "httpd/unix-directory"
}
var propDic = [String: String]()
let propStatNode = node[propstattag]
for node in propStatNode.children where node.name.lowercased().hasSuffix("status"){
statustag = node.name
}
self.href = href
self.hrefString = hrefString
self.status = status
self.prop = propDic
}
static func parse(xmlResponse: Data, baseURL: URL?) -> [DavResponse] {
var result = [DavResponse]()
do {
let xml = try AEXMLDocument(xml: xmlResponse)
var rootnode = xml.root
var responsetag = "response"
for node in rootnode.all ?? [] where node.name.lowercased().hasSuffix("multistatus") {
rootnode = node
}
for node in rootnode.children where node.name.lowercased().hasSuffix("response") {
responsetag = node.name
break
}
let statusDesc2 = (propStatNode[statustag].string).components(separatedBy: " ")
if statusDesc2.count > 2 {
status = Int(statusDesc2[1])
}
var proptag = "prop"
for tnode in propStatNode.children where tnode.name.lowercased().hasSuffix("prop") {
proptag = tnode.name
break
}
for propItemNode in propStatNode[proptag].children {
propDic[propItemNode.name.components(separatedBy: ":").last!.lowercased()] = propItemNode.value
if propItemNode.name.hasSuffix("resourcetype") && propItemNode.xml.contains("collection") {
propDic["getcontenttype"] = "httpd/unix-directory"
for responseNode in rootnode[responsetag].all ?? [] {
if let davResponse = DavResponse(responseNode, baseURL: baseURL) {
result.append(davResponse)
}
}
return DavResponse(href: hrefURL, hrefString: href, status: status, prop: propDic)
} catch _ {
}
return nil
return result
}
fileprivate func mapToFileObject(_ davResponse: DavResponse) -> WebDavFileObject {
var href = davResponse.href
if href.baseURL == nil {
href = absoluteURL(href.path)
}
}
public final class WebDavFileObject: FileObject {
internal init(_ davResponse: DavResponse) {
let href = davResponse.href
let name = davResponse.prop["displayname"] ?? (davResponse.hrefString.removingPercentEncoding! as NSString).lastPathComponent
let size = Int64(davResponse.prop["getcontentlength"] ?? "-1") ?? NSURLSessionTransferSizeUnknown
let createdDate = self.resolve(dateString: davResponse.prop["creationdate"] ?? "")
let modifiedDate = self.resolve(dateString: davResponse.prop["getlastmodified"] ?? "")
let contentType = davResponse.prop["getcontenttype"] ?? "octet/stream"
let isDirectory = contentType == "httpd/unix-directory"
let entryTag = davResponse.prop["getetag"]
return WebDavFileObject(absoluteURL: href, name: name, path: href.path, size: size, contentType: contentType, createdDate: createdDate, modifiedDate: modifiedDate, fileType: isDirectory ? .directory : .regular, isHidden: false, isReadOnly: false, entryTag: entryTag)
super.init(url: href, name: name, path: href.relativePath)
self.size = Int64(davResponse.prop["getcontentlength"] ?? "-1") ?? NSURLSessionTransferSizeUnknown
self.creationDate = resolve(dateString: davResponse.prop["creationdate"] ?? "")
self.modifiedDate = resolve(dateString: davResponse.prop["getlastmodified"] ?? "")
self.contentType = davResponse.prop["getcontenttype"] ?? "octet/stream"
self.isHidden = (Int(davResponse.prop["ishidden"] ?? "0") ?? 0) > 0
self.type = self.contentType == "httpd/unix-directory" ? .directory : .regular
self.entryTag = davResponse.prop["getetag"]
}
fileprivate func delegateNotify(_ operation: FileOperation, error: Error?) {
DispatchQueue.main.async(execute: {
if error == nil {
self.delegate?.fileproviderSucceed(self, operation: operation)
} else {
self.delegate?.fileproviderFailed(self, operation: operation)
}
})
/// MIME type of the file
open internal(set) var contentType: String {
get {
return allValues["NSURLContentTypeKey"] as? String ?? ""
}
set {
allValues["NSURLContentTypeKey"] = newValue
}
}
/// HTTP E-Tag, can be used to mark changed files
open internal(set) var entryTag: String? {
get {
return allValues["NSURLEntryTagKey"] as? String
}
set {
allValues["NSURLEntryTagKey"] = newValue
}
}
}