Compare commits

...

272 Commits

Author SHA1 Message Date
Amir Abbas 28d01a9c59 Fix StreamTask read data bug
- Single stream upload file
- Removed redundant ftp helper functions
2017-12-29 19:46:46 +03:30
Amir Abbas 5d5e6811fd Fix Swift 3 compile error 2017-12-27 18:39:15 +03:30
Amir Abbas 1f2aa4888c Fixed FTP errors to bypass test cases and build error on macOS 2017-12-27 18:38:55 +03:30
Amir Abbas cf1b226417 Fix FTP provider crashes and errors on uploading and downloading 2017-12-27 17:36:35 +03:30
Amir Abbas 1ddb4b140d Remove testing on swift 3 2017-12-26 11:41:20 +03:30
Amir Abbas 86fe84351f Probable fix #76 , deinit to cleanup providers 2017-12-26 11:23:07 +03:30
Amir Abbas ca248f08dd Fix #72 (WebDAV result contains space) 2017-12-26 11:22:46 +03:30
Amir Abbas b17d92350c Fix. #71, Temp workaround for StreamTask issue 2017-12-26 11:22:05 +03:30
Amir Abbas d38748153d Fixed typo in WebDAV 2017-11-16 12:19:16 +03:30
Amir Abbas 17140f40d7 Fix #70 (WebDAV folder), Better URLRequest header interface 2017-11-14 21:32:41 +03:30
Amir Abbas 2820b4d3fe Fixed test scheme 2017-11-12 18:31:02 +03:30
Amir Abbas 7cd1528d75 Updated readme and travis for tests 2017-11-05 08:26:53 +03:30
Amir Abbas 14b4bcefe4 Fix Dropbox attributesOfItem, fix test cases 2017-11-04 16:23:44 +03:30
Amir Abbas a43640ecfd Fixed Test project settings, test for Dropbox and OneDrive 2017-11-03 01:30:23 +03:30
Amir Abbas f7f2d7f734 Fix error 2017-11-03 01:18:52 +03:30
Amir Abbas 5aaf8f2bf2 Added test 2017-11-03 01:10:05 +03:30
Amir Abbas 026e496512 Fixed macOS swift 4 convertToImage() compile issue 2017-11-03 01:09:26 +03:30
Amir Abbas 435ed37d93 Updated pod version to 0.21 2017-10-27 09:21:17 +03:30
Amir Abbas 9e4a803345 Removed unnecessary properties from SessionDelegate 2017-10-26 14:07:57 +03:30
Amir Abbas 0e53036855 Added progressive downloading for HTTP-based providers
- Added HEIC/HEIF image thumbnail and properties
- Fixed bug: data task recieve data body called only once
2017-10-26 13:56:41 +03:30
Amir Abbas 87317c1d42 Fixed #61, OneDrive progress availibility 2017-10-13 08:55:55 +03:30
Amir Abbas 4c35e446a7 Fix provider base url (as directory), caused Cloud provider misbehaves 2017-10-13 02:31:31 +03:30
Amir Abbas 47516c63ad Refactored contentsOfDirectory to searchFiles where applicable 2017-10-12 18:30:28 +03:30
Amir Abbas 6cd5b7d8c5 Fixed error description in Dropbox (except uploads)
- Refactors in Dropbox request maker method
2017-10-11 01:14:02 +03:30
Amir Abbas 598444c90f Codebeat GPA improvements 2017-10-10 16:48:55 +03:30
Amir Abbas 370866442a Fixed Compilation error for upload size type 2017-10-10 11:40:32 +03:30
Amir Abbas 9cc9a6a96a Improving documentation
- Renaming OneDriveProvider.SubAddress to Route
- Refactors to improve codebeat score
2017-10-10 11:07:28 +03:30
Amir Abbas 2969af28b8 Small refactors 2017-10-09 13:32:46 +03:30
Amir Abbas d41e86b30a Fixed Swift 3 flatMap error 2017-10-09 11:31:02 +03:30
Amir Abbas aaa1def9e7 Refactored paginated listing in Dropbox and OneDrive
- Removed currentPath
2017-10-09 11:16:17 +03:30
Amir Abbas 024a3637b4 Fix compile time error 2017-10-04 16:32:54 +03:30
Amir Abbas b20fc3efe5 Fix compilation error on swift 3 2017-10-04 15:11:26 +03:30
Amir Abbas 682dd40072 File coordination enabled by default for copyItem(locaURL:) methods 2017-10-04 15:06:27 +03:30
Amir Abbas 3f35e600cd Removing deprecated String.characters usage
- Documentation for using file id in Dropbox & OneDrive
2017-10-04 15:04:23 +03:30
Amir Abbas 9cd2fa2e3a Compilation optimizations, refactoring 2017-10-01 02:04:29 +03:30
Amir Abbas 7be6985454 Updated OneDrive API to new version 2017-09-29 21:39:28 +03:30
Amir Abbas 04ac3e22e7 Refactored correctPath to specified providers 2017-09-29 20:51:09 +03:30
Amir Abbas c8de7fdb69 Fixed NSProgress parenting, iCloud KVO, crash in url(of) 2017-09-29 18:11:26 +03:30
Amir Abbas 42b879d4e2 Fix Swift 3 bug 2017-09-24 15:37:00 +03:30
Amir Abbas 5800c9a2ec Improved compile speed, Fixed Swift 4 warnings 2017-09-24 13:12:31 +03:30
Amir Abbas Mousavian 7c9b2bf09a Merge pull request #65 from yuyan7/fix_webdav
Fix HTTP providers not be overwritten
2017-09-16 20:31:59 +04:30
yuya-nakamura ba08cdda1d Fixed WebDAV not be overwritten 2017-09-16 23:01:57 +09:00
Amir Abbas b380685932 Added VolumeObject for storageProperties method
- Refined error handling in HTTP provider
- Added contentType and hash to OneDriveFileObject
2017-09-05 02:26:09 +04:30
Amir Abbas 5a5beb6891 Refined error handling 2017-09-04 01:22:44 +04:30
Amir Abbas c7201a8be7 Fixed currentPath docs, delegateNotify method default error arg 2017-08-29 01:40:15 +04:30
Amir Abbas d2a03369fd Fixed FTP crash on rucursive remove 2017-08-28 00:06:02 +04:30
Amir Abbas d08098008a Removed FoundationErrorEnum, Refactoed delegate progress routine 2017-08-27 13:48:29 +04:30
Amir Abbas 3c5303214e Fixed Array extension error in HashMAC 2017-08-24 15:08:22 +04:30
Amir Abbas ad8f333d16 Fixed swift 3 compile error on Array extenson 2017-08-24 08:36:11 +04:30
Amir Abbas 5e8f653c52 Fixed compile bug in Swift 3.0 for Array.Iterator.Element 2017-08-23 23:21:17 +04:30
Amir Abbas e4cd7ddf22 Fixed Quality<T> percision 2017-08-23 22:18:18 +04:30
Amir Abbas 1efb0e3fe5 Fixed Quality<T> 2017-08-23 22:15:45 +04:30
Amir Abbas 1292856646 Added convenience methods for URLRequest headers 2017-08-23 22:04:47 +04:30
Amir Abbas e39f9c29ec Merge commit '3d44de0b407c3cd604189cc161dc1fc039409763' 2017-08-23 10:54:59 +04:30
Amir Abbas f43199c22a fixed colon in url string problem across provider 2017-08-23 10:52:45 +04:30
Amir Abbas Mousavian 3d44de0b40 Added missed filePathAllowed const to CharacterSet 2017-08-23 01:22:19 +04:30
Amir Abbas 21b5214481 Deprecate currentPath property, returning error in provider delegate
- Using checkResourceIsReachable to check file exists
- fixed bug: downloading/uploading files with colon in name
2017-08-23 01:19:56 +04:30
Amir Abbas f0b4925db2 Consistancy to delegate call in all contents(of:) methods
- renamed opType instances to operation
- moved to v2 in Dropbox
- removed redundant CloudFileProvider methods
2017-08-20 22:41:53 +04:30
Amir Abbas 0da957d25f Making protocol method implementations overridable 2017-08-19 19:47:54 +04:30
Amir Abbas 0de558c160 Recursive search fixed in FTP, OneDrive, WebDAV 2017-08-19 06:49:15 +04:30
Amir Abbas cad68da076 Refactored Dropbox, WebDAV & OneDrive to HTTPFileProvider
- FileOperationType.source is not optional anymore
- critical bugfix: KVO exception crash on task completion
2017-08-19 05:51:25 +04:30
Amir Abbas Mousavian 39da09edd4 Merge pull request #60 from evilutioner/master
Fixed file downloading progress
2017-08-15 18:53:21 +04:30
Amir Abbas 5129aee1b5 Added fileURL to Progress 2017-08-15 18:41:48 +04:30
Oleg Marchik 478f0819b5 Fixed file downloading progress 2017-08-15 16:57:50 +03:00
Amir Abbas b2a7800f7c Fixed typo 2017-08-15 15:08:06 +04:30
Amir Abbas 96c102d156 Updated readme and podspec to ver 0.19.0 2017-08-15 14:57:36 +04:30
Amir Abbas 8aedd8e72a Replaced OperationHandle with (NS)Progress 2017-08-15 13:42:41 +04:30
Amir Abbas 5b55debe8a Fix warning in OneDrive provider 2017-08-12 17:50:00 +04:30
Amir Abbas Mousavian 0fa062a946 Merge pull request #59 from evilutioner/master
OneDrive: list folder issue fixed, removed escape symbols in path
2017-08-11 20:43:28 +04:30
Oleg Marchik 879f86c1f9 Merge branch 'master' into master 2017-08-11 18:07:25 +03:00
Oleg Marchik 80d5f02bbd OneDrive: list folder issue fixed, removed escape symbols in path 2017-08-11 18:04:18 +03:00
Amir Abbas Mousavian b8a7721b2f Merge pull request #58 from evilutioner/master
Dropbox bug fixing: added unicode symbols support in http header path
2017-08-10 22:29:08 +04:30
Oleg Marchik 44b4784cd3 Important dropbox bug fixing: added unicode symbols support in path 2017-08-10 11:36:15 +03:00
Amir Abbas 7d9e2247f2 Fixed Compile error, removed redundant comments 2017-08-04 00:30:46 +04:30
Amir Abbas Mousavian 0ace562442 Merge pull request #57 from evilutioner/master
Fixed outdated OneDrive API
2017-08-04 00:27:36 +04:30
evilutioner 63d831ef90 Merge branch 'master' into master 2017-08-01 16:08:51 +03:00
Oleg Marchik d6b91348a3 Fixed outdated OneDrive API 2017-08-01 12:55:39 +03:00
Amir Abbas fd9d4c1ab4 Probable fix for # 55 (OneDrive url issue) 2017-07-31 20:10:40 +04:30
Amir Abbas 41e266c2a9 Fix #54(FTP login), Refactored ExtendedLocalFileProvider 2017-07-17 05:36:30 +04:30
Amir Abbas 6127f4a7d9 Fixed Readme errors 2017-07-14 19:28:41 +04:30
Amir Abbas faca943beb Updated Readme fot FTP and SMB description 2017-07-14 16:49:55 +04:30
Amir Abbas 9345436fa2 Enhanced PDF Meta-info, Better FTPS handling 2017-07-14 16:49:24 +04:30
Amir Abbas 6228d88d41 Adjusting readme badges 2017-07-05 21:35:15 +04:30
Amir Abbas 5b395915a9 Updated Readme for Pods and SPM file 2017-07-02 18:26:34 +04:30
Amir Abbas e4f12a502b Fixed repo name and Readme 2017-07-01 16:57:23 +04:30
Amir Abbas 8faad0ec65 Fixed project name in schemes 2017-07-01 14:14:45 +04:30
Amir Abbas bd5569d213 Updated scheme names 2017-07-01 14:09:39 +04:30
Amir Abbas 14ed279879 Renamed framework for compatibility with iOS 11 2017-07-01 13:50:22 +04:30
Amir Abbas fd293f7bdb [Amend] Fixed swift 3.0 compile error for type(of:) 2017-07-01 11:08:49 +04:30
Amir Abbas 3d9625e243 Fixed swift 3.0 compile error for type(of:) 2017-07-01 11:03:22 +04:30
Amir Abbas 0d017ebfc4 Fix #52 (Swift 4.0 source compatibility) 2017-07-01 10:47:48 +04:30
Amir Abbas fd36df67f3 FTP passive property encoding
- removed redundant protocol conforming
2017-07-01 09:43:45 +04:30
Amir Abbas 5be8c4ef5e podspec and version update 2017-06-27 14:02:00 +04:30
Amir Abbas 0374fd7688 Making ftp active data task creation async 2017-06-24 12:09:16 +04:30
Amir Abbas 5ddfa43555 Fix #49 (Workaround colon in url path bug in NSURL relative url) 2017-06-24 12:04:50 +04:30
Amir Abbas 21850bb548 Probable fix for #51 2017-06-24 11:44:12 +04:30
Amir Abbas b166e111e0 FTP Active mode, FTPS bugfix, possible fix for #47 2017-05-25 17:33:56 +04:30
Amir Abbas 1dd7561215 Caching ftp server support for RFC3659, Possible fix for #47 2017-05-23 19:10:04 +04:30
Amir Abbas f94719deb0 Fixes #39 (FTP listing), Error domain determination 2017-05-05 13:02:07 +04:30
Amir Abbas b13df0a977 Fixes #38 (Creating folder on WebDAV) 2017-05-05 10:06:17 +04:30
Amir Abbas 24af7aa4c2 Fixed LocalFileProvider init baseURL issue 2017-04-20 01:27:59 +04:30
Amir Abbas 06039ad993 CloudFileProvider operation handle improvements 2017-04-20 01:06:36 +04:30
Amir Abbas Mousavian d8fec3e346 Merge pull request #37 from hansvdam/master
Making `inProgress` of LocalFileProvider working
2017-04-18 14:22:08 +04:30
Hans van Dam 5c93bc8731 making 'inProgress' of LocalFileProvider work more consistently 2017-04-18 11:03:35 +02:00
Hans van Dam 61ba245189 making 'inProgress' of LocalFileProvider work 2017-04-17 18:16:04 +02:00
Amir Abbas 02e6cd37dd WebDAV OAuth 1,2 support 2017-04-16 19:19:17 +04:30
Amir Abbas 55608fb8d0 New FileProviderSharing protocol for publicLinks
- fixed DropboxFileProvider.propertiesOfFile() bug
- minor URLRequest refactors
- Added documentation and scope declaration
2017-04-16 19:07:45 +04:30
Amir Abbas 3e3582f6fa Changed DropboxFileProvider.type constant to “Dropbox” 2017-04-15 21:31:47 +04:30
Amir Abbas dd7a9d20b6 Fixed macOS build error 2017-04-14 23:26:40 +04:30
Amir Abbas d4a9b4a34f Fixed Dropbox thumbnail issue 2017-04-14 22:07:35 +04:30
Amir Abbas 34c663e62c FileObject.url is unwraped. fixed url initializing from path 2017-04-14 18:57:50 +04:30
Amir Abbas 1415dda987 Fixed #36 (FTP Uploading bug) 2017-04-14 18:55:07 +04:30
Amir Abbas cd465c1288 Fixes #35 (Dropbox handlers), Fixed FTP response reading length 2017-04-14 11:15:09 +04:30
Amir Abbas a605b0cd85 Added FTP Recursive listing, Implemented ftp search
- Implemented FTP full directory remove
- Fixed FTP STOR bug
- Implemented relativePath(of:) for FTP
- Initial implementation of FTP active mode
2017-04-11 22:21:13 +04:30
Amir Abbas bf7043de29 Fixed relativePath(of:) bug, made it overridable
- Fixed StreamTask.taskDescription bug
2017-04-11 19:43:34 +04:30
Amir Abbas ff5e13931f Fixed WebDAVProvider.contents bug, refactored FTP Error 2017-04-09 14:09:03 +04:30
Amir Abbas 75af738d2e Made SessionDelegate init public, fixed pod issue 2017-04-05 02:05:28 +04:30
Amir Abbas f54a1253e4 Throwing error when trying to upload a directory 2017-04-05 00:24:24 +04:30
Amir Abbas 1394a92662 Add Documentation 2017-04-03 21:03:48 +04:30
Amir Abbas dab171c755 Setting sessionDelgate credential to updated one. 2017-04-03 18:59:50 +04:30
Amir Abbas ea5de2e2aa Added progress for content(path:) method
- Fixed issue with colliding handlers between sessions.
- Sessions can be set.
- SessionDelegate class is now public.
2017-04-03 18:50:13 +04:30
Amir Abbas 2253cca086 Fixed deprecated URLResourceKey items 2017-04-03 12:52:27 +04:30
Amir Abbas e15a900ade Renamed URLResourceKey additions to have Key prefix 2017-04-03 12:49:35 +04:30
Amir Abbas 5c2c56c44c Fixed: Calling completion handler for upload task
- Added including (file object properties) argument to WebDAV provider (resolves #31)
2017-04-03 12:41:11 +04:30
Amir Abbas Mousavian ff4bbdf0de Updated readme and podspec for FTP, minor fixes. 2017-04-01 14:56:20 +04:30
Amir Abbas Mousavian 163a218ac2 Fixed operation progress (in delegate) for all remote providers
- Now also compatible with background session
- added delegate notify (success, progress, failure) to FTP
- added `FTPFileProvider.useAppleImplementation`, allows developer choose to use apple download task instead of custom implementation
- enabling to download non-text files in FTP
- implementation of `url(of:) in  FTP
- various fixes in error reporting
- disabled closing streams in ftpQuit() due to crash
2017-04-01 01:33:56 +04:30
Amir Abbas Mousavian 81401ee36f Added Documentation, refactors of related Date methods 2017-03-31 10:15:39 +04:30
Amir Abbas Mousavian 759ba3c7bf Added AEXML license file 2017-03-31 00:49:08 +04:30
Amir Abbas Mousavian f5c8f6308b FileProviderStreamTask (URLSessionStreakTask replica) is now public
- closing streams after ftpQuit() executed.
- added ability to add FTP Active Mode.
2017-03-30 23:49:28 +04:30
Amir Abbas Mousavian 4dbb0adb18 FTP better error handling 2017-03-30 13:16:03 +04:30
Amir Abbas Mousavian bf62d585fd implemented FTP copy fallback, FTPS manual auth 2017-03-30 10:41:14 +04:30
Amir Abbas Mousavian f21f658874 Deprecated create(file:) method, replaced by writeContents()
- RemoteOperationHandle now retains task
- FTP provider returns correct operation handle task
2017-03-29 23:03:04 +04:30
Amir Abbas e6eba3d198 Fixed macOS and tvOS build 2017-03-29 10:13:47 +04:30
Amir Abbas 6959a14dc1 Fixed compiler error, closing streams in FTP provider 2017-03-29 09:29:22 +04:30
Amir Abbas 29a9e0fb82 Initial implementation of FTP
- ftp copy and search is not implemented
2017-03-29 04:17:03 +04:30
Amir Abbas Mousavian c7b4e1f124 Ensure baseURL is absolute, fixed warnings for Swift 3.1 2017-03-28 19:25:41 +04:30
Amir Abbas Mousavian 99a433a0fc Credential is open to set anytime 2017-03-26 15:54:55 +04:30
Amir Abbas Mousavian 4023fbc62e Fixed: WebDAV file listing omit files contains space in name 2017-03-26 02:03:43 +04:30
Amir Abbas Mousavian 1045901d7c Added NSCoding support
- Better relative path handling in WebDAV
- obsolete deprecated methods
2017-03-25 19:19:49 +04:30
Amir Abbas 528d5eebc3 Fixed relativePath(of:) crash 2017-03-18 15:58:44 +03:30
Amir Abbas 079f8f4b77 Refactored methods to extensions 2017-03-17 15:52:58 +03:30
Amir Abbas e12f386a9d Refactored DispatchTime, better ExposureTime calculation 2017-03-11 03:02:02 +03:30
Amir Abbas 38e217bc19 Better LocalFileObject initialization with empty path 2017-03-09 17:34:00 +03:30
Amir Abbas 0b41abd4ef Optimized PDF thumbnail/meta handling
- Fixed ISO speed and GPS Area image meta
- Fixed Dropbox `name ! BEGINSWiTH %` search query
2017-03-01 13:28:44 +03:30
Amir Abbas aa781adeb2 Fixed searchFiles() from string, fixing “BEGINSWITH” typo 2017-02-24 16:53:59 +03:30
Amir Abbas d61e51ba1c Fixes #29 (WebDAV authentication), minor lints/optimiziations 2017-02-24 16:24:34 +03:30
Amir Abbas cdff7db32e Fixed OneDriveProvider bugs
- fixed and enhanced searching files in Dropbox
2017-02-21 00:48:59 +03:30
Amir Abbas 9533a0e3c9 Removed redundant isPathRelative property. Now is always true.
- Note: Check documentation to workaround
- Improvement: Disabling `LocalFileProviderMonitor` while handler is running
2017-02-20 00:14:55 +03:30
Amir Abbas 194673b3b6 Added NSPredicate to searchFiles method
- public functions became open, now is overridable
- fixed urlCache documentation
2017-02-19 13:34:55 +03:30
Amir Abbas 194c8a41aa Fixed readme 2017-02-18 09:08:55 +03:30
Amir Abbas Mousavian c290377433 Updated Readme, travis.yml 2017-02-16 20:56:24 +03:30
Amir Abbas Mousavian 330a22c45d Completed Documentation, fixed a small bug. 2017-02-16 13:00:50 +03:30
Amir Abbas e899804e28 Added isReachable method to test connectivity
- Fixed Cloud provider bug when moving/deleting evicted file
2017-02-14 22:58:07 +03:30
Amir Abbas 489a9a16d0 Updated documentation, fixed Cloud provider paths bugs 2017-02-14 12:24:06 +03:30
Amir Abbas 93359d5173 Made FileProviders equatable 2017-02-13 16:45:34 +03:30
Amir Abbas 12ff3a410d Fixed critical bug in LocalFileObject when decoding path 2017-02-13 16:31:29 +03:30
Amir Abbas a7de09362b Equatable FileObject, async completion handler in cloud provider 2017-02-13 12:06:09 +03:30
Amir Abbas b1fe37cf2a Updated Readme, fixed several bugs
- non-locking snapshot of file while uploading to iCloud
- fixed: `CloudFIleProvider.doOperation()` couldn’t handle local urls
- fixed: FileProvider.type didn’t work correctly
2017-02-12 22:45:30 +03:30
Amir Abbas bd59eacee2 Fixed bugs in providers and LocalFileObject initializing 2017-02-12 03:07:40 +03:30
Amir Abbas 42579be371 Added FileProviderHTTPError protocol
- All errors from local provider is now from Cocoa error domain. No URL domain anymore.
- All errors from WebDAV/OneDrive/Dropbox conform to FileProviderHTTPError
2017-02-11 18:05:21 +03:30
Amir Abbas edd4914ca7 Linting, fixed travis wrong device 2017-02-11 06:08:17 +03:30
Amir Abbas bd3e1bd74f Fixed typos in travis.yml 2017-02-10 02:57:15 +03:30
Amir Abbas 371c481482 Fixed project name typo in travis.yml 2017-02-09 15:02:39 +03:30
Amir Abbas dea1d01bf3 Made travis.yml more generic 2017-02-09 14:46:29 +03:30
Amir Abbas ae4cd1dff3 Fixed undo manager bugs
- Fixed contents of file with offset method bugs
- Improved `LocalFileProvider.copy()` implementation
2017-02-09 03:02:21 +03:30
Amir Abbas 72520973e9 Changed FileObject.allValues key type to URLResourceKey
- LocalFileProvider.storageProperties method now uses url resource values
2017-02-08 07:30:41 +03:30
Amir Abbas 63016285af Added UndoManager support to Local provider
- Refactored Local provider operations into one function
2017-02-08 04:54:18 +03:30
Amir Abbas 3245c9df03 Updated travis.yml 2017-02-07 21:59:17 +03:30
Amir Abbas ee1fa89747 Updated travis.yml 2017-02-07 12:07:59 +03:30
Amir Abbas 4a6b25deac Updated travis.yml 2017-02-07 09:48:12 +03:30
Amir Abbas 2423cbd0f6 Updated travis.yml 2017-02-07 02:55:22 +03:30
Amir Abbas 480099ca8a Updated travis.yml 2017-02-06 19:41:57 +03:30
Amir Abbas 755bf6bf84 Updated travis.yaml 2017-02-06 19:38:14 +03:30
Amir Abbas 525825ff5d Fixed Cloud provider listing (RunLoop) bug
- Added ubiquity scope to Cloud provider
- Renamed `temporaryLink` to `publicLink`
- Added deprecated renaming guide
2017-02-06 18:03:58 +03:30
Amir Abbas Mousavian a35384dc31 Refactoring: reducing complexity of properties methods 2017-02-03 23:15:23 +03:30
Amir Abbas Mousavian 0c558fdbb4 Updated version 2017-02-03 18:33:25 +03:30
Amir Abbas Mousavian e30861ad18 Added publicLink method to OneDrive
- Added more Documentation, specially initializer
- Added option to conserve sessions after provider deinit to finish tasks
- Dropbox and OneDrive init are not bailable anymore
- Updated AEXML
2017-02-03 18:29:34 +03:30
Amir Abbas Mousavian 538e9bb42d Updated provider descriptions 2017-02-02 19:32:28 +03:30
Amir Abbas Mousavian ef2d69c54c Updated supported providers order 2017-02-02 19:26:26 +03:30
Amir Abbas Mousavian 9f210ef356 Updated readme link to cocoa pods docs 2017-02-02 13:32:58 +03:30
Amir Abbas Mousavian 5a7dfa1039 Merge commit 'b84e6ea7abbec6f9a83144d2dcd6744b568d682f'
* commit 'b84e6ea7abbec6f9a83144d2dcd6744b568d682f':
  Updated Date
2017-02-02 13:28:08 +03:30
Amir Abbas Mousavian be7a370a42 Added Documentation, get contents handling in Local provider optimized
- Changed default thumbnail dimension to 64x64 in `LocalFileProvider`
2017-02-02 13:25:40 +03:30
Amir Abbas Mousavian b84e6ea7ab Updated Date 2017-02-02 01:03:48 +03:30
Amir Abbas Mousavian 82b1d2c450 Added new badges 2017-02-01 22:18:20 +03:30
Amir Abbas Mousavian c38ee1ccd3 standardized FileObject.path property
- FileObject.path now has heading slash in all scenarios
- fixes #27, WebDAV fileObject.path was not relative
2017-02-01 21:23:11 +03:30
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
Amir Abbas Mousavian 315ead606e AnyObject casts changed to proper one according to data type 2016-09-17 12:12:52 +04:30
Amir Abbas Mousavian d63e9c7f04 Swift 3 travis & podspec update 2016-09-16 05:20:34 +04:30
Amir Abbas Mousavian a5fe28c18d Swift 3 readme update 2016-09-15 07:58:36 +04:30
Amir Abbas Mousavian 542c18bab6 Convert to Swift 2 2016-09-15 02:48:04 +04:30
Amir Abbas Mousavian dca5dddbfd Code beat exceptions added 2016-08-20 19:19:05 +04:30
Amir Abbas Mousavian 6764c23f1b Update README.md and some refactors 2016-08-15 13:50:03 +04:30
Amir Abbas Mousavian 955a86372b Refactored NSURLSession delegate in Dropbox and WebDAV 2016-08-15 13:30:37 +04:30
Amir Abbas Mousavian 32a20fbc49 CodeCov added 2016-08-11 14:26:24 +04:30
Amir Abbas Mousavian 921ba19afa travis yml update 2016-08-11 14:16:04 +04:30
Amir Abbas Mousavian 8625b0464c Travis CI integration 2016-08-11 14:04:37 +04:30
Amir Abbas Mousavian 1bf9d1eadd Swift Package Manager Added 2016-08-11 13:49:28 +04:30
Amir Abbas Mousavian cbf774ba9e update README.md 2016-08-11 12:52:04 +04:30
Amir Abbas Mousavian 5e2f911cda Dropbox provider bug fixes 2016-08-11 01:06:53 +04:30
Amir Abbas Mousavian f423834021 HTTP error codes enum optimization 2016-08-10 12:35:11 +04:30
Amir Abbas Mousavian 0772c87122 FPSStreamTass bugs fixed and now working like a charm! 2016-08-09 23:37:28 +04:30
Amir Abbas Mousavian 011c535760 FPSStreamTask (iOS7,8) on a par with NSURLSessionStreamTask in iOS9 2016-08-09 13:45:35 +04:30
Amir Abbas Mousavian 468a2bc9e9 SMB2.QueryInfo implementation 2016-08-09 02:04:42 +04:30
Amir Abbas Mousavian 33be492499 Implemented SMB.ChangeNotify command, syntax fixed 2016-08-08 15:57:08 +04:30
Amir Abbas Mousavian c4049b961f Dropbox updates, SMB2 QueryDirectory implementation
- ExtendedFileProvider protocol is implemented for Dropbox
-
2016-08-08 12:47:13 +04:30
Amir Abbas Mousavian 940c7c1028 Dropbox implementation completed and getting storage size, used 2016-08-04 23:06:24 +04:30
Amir Abbas Mousavian b4ace7e680 Unified HTTP based services (WebDAV/Dropbox) Error Handling
- DropboxFileProvider.contentsOfDirectory() method implemented
2016-08-03 13:40:12 +04:30
Amir Abbas Mousavian eccbeb7174 Fixed WebDAV connection error for Apache servers 2016-08-02 14:59:32 +04:30
Amir Abbas Mousavian a077d000bc Updated Readme 2016-07-31 01:27:13 +04:30
Amir Abbas Mousavian 6a3ea633bf WebDAV file protocol conformance to FileProvider 2016-07-29 10:57:18 +04:30
61 changed files with 14181 additions and 3813 deletions
+1
View File
@@ -0,0 +1 @@
3.0
+78
View File
@@ -0,0 +1,78 @@
language: objective-c
osx_image: xcode8.2
xcode_project: $PROJECTNAME.xcodeproj
env:
global:
- PROJECT="$PROJECTNAME.xcodeproj"
- FRAMEWORK_NAME="$PROJECTNAME.framework"
- IOS_FRAMEWORK_SCHEME="$PROJECTNAME iOS"
- MACOS_FRAMEWORK_SCHEME="$PROJECTNAME OSX"
- TVOS_FRAMEWORK_SCHEME="$PROJECTNAME tvOS"
- IOS_SDK=iphonesimulator
- MACOS_SDK=macosx
- TVOS_SDK=appletvsimulator
matrix:
- DESTINATION="OS=10.2,name=iPad Air 2" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" CARTHAGEDEPLOY="YES"
- DESTINATION="OS=9.0,name=iPhone 6" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" POD="YES"
- DESTINATION="arch=x86_64" SCHEME="$MACOS_FRAMEWORK_SCHEME" SDK="$MACOS_SDK"
# - DESTINATION="arch=x86_64" SCHEME="FilesProviderTests" SDK="$MACOS_SDK" RUN_TESTS="YES"
- DESTINATION="OS=10.1,name=Apple TV 1080p" SCHEME="$TVOS_FRAMEWORK_SCHEME" SDK="$TVOS_SDK"
before_install:
- gem install xcpretty --no-rdoc --no-ri --no-document --quiet
- gem install cocoapods --no-rdoc --no-ri --no-document --quiet
# - gem install xcpretty-travis-formatter
script:
- set -o pipefail
- xcodebuild -version
# Build Example in Debug if specified
- if [ $BUILD_EXAMPLE == "YES" ]; then
xcodebuild -project "$WORKSPACE" -scheme "$EXAMPLE_SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO build | xcpretty;
fi
# Build Framework in Debug and Run Tests if specified
- if [ $RUN_TESTS == "YES" ]; then
xcodebuild -project "$PROJECT" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO ENABLE_TESTABILITY=YES test | xcpretty;
else
xcodebuild -project "$PROJECT" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO build | xcpretty;
fi
# Build Framework in Release and Run Tests if specified
- if [ $RUN_TESTS == "YES" ]; then
xcodebuild -project "$PROJECT" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Release ONLY_ACTIVE_ARCH=NO ENABLE_TESTABILITY=YES test | xcpretty;
else
xcodebuild -project "$PROJECT" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Release ONLY_ACTIVE_ARCH=NO build | xcpretty;
fi
# Run `pod lib lint` if specified
- if [ $POD == "YES" ]; then
pod lib lint --quick;
fi
after_success:
# Run `pod trunk push` if specified
- if [ $POD == "YES" ] && [ -n "$TRAVIS_TAG" ]; then
pod trunk push --allow-warnings;
fi
# - bash <(curl -s https://codecov.io/bash)
before_deploy:
- if [ $CARTHAGEDEPLOY == "YES" ] && [ -n "$TRAVIS_TAG" ]; then
brew update;
brew outdated carthage || brew upgrade carthage;
carthage version;
carthage build --no-skip-current --verbose;
carthage archive $PROJECTNAME;
fi
deploy:
provider: releases
api_key: "$GITHUBTOKEN"
file: $FRAMEWORK_NAME.zip
skip_cleanup: true
on:
# repo: amosavian/$PROJECTNAME
repo: amosavian/FileProvider
tags: true
condition: "$CARTHAGEDEPLOY = YES"
-801
View File
@@ -1,801 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
799396A71D48C02300086753 /* AEXML.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396921D48C02300086753 /* AEXML.swift */; };
799396A81D48C02300086753 /* AEXML.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396921D48C02300086753 /* AEXML.swift */; };
799396A91D48C02300086753 /* AEXML.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396921D48C02300086753 /* AEXML.swift */; };
799396AA1D48C02300086753 /* DropboxFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396931D48C02300086753 /* DropboxFileProvider.swift */; };
799396AB1D48C02300086753 /* DropboxFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396931D48C02300086753 /* DropboxFileProvider.swift */; };
799396AC1D48C02300086753 /* DropboxFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396931D48C02300086753 /* DropboxFileProvider.swift */; };
799396B01D48C02300086753 /* FileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396951D48C02300086753 /* FileProvider.swift */; };
799396B11D48C02300086753 /* FileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396951D48C02300086753 /* FileProvider.swift */; };
799396B21D48C02300086753 /* FileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396951D48C02300086753 /* FileProvider.swift */; };
799396B31D48C02300086753 /* LocalFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396961D48C02300086753 /* LocalFileProvider.swift */; };
799396B41D48C02300086753 /* LocalFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396961D48C02300086753 /* LocalFileProvider.swift */; };
799396B51D48C02300086753 /* LocalFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396961D48C02300086753 /* LocalFileProvider.swift */; };
799396B61D48C02300086753 /* SMBClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396971D48C02300086753 /* SMBClient.swift */; };
799396B71D48C02300086753 /* SMBClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396971D48C02300086753 /* SMBClient.swift */; };
799396B81D48C02300086753 /* SMBClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396971D48C02300086753 /* SMBClient.swift */; };
799396B91D48C02300086753 /* SMBFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396981D48C02300086753 /* SMBFileProvider.swift */; };
799396BA1D48C02300086753 /* SMBFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396981D48C02300086753 /* SMBFileProvider.swift */; };
799396BB1D48C02300086753 /* SMBFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396981D48C02300086753 /* SMBFileProvider.swift */; };
799396BC1D48C02300086753 /* CIFSTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969A1D48C02300086753 /* CIFSTypes.swift */; };
799396BD1D48C02300086753 /* CIFSTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969A1D48C02300086753 /* CIFSTypes.swift */; };
799396BE1D48C02300086753 /* CIFSTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969A1D48C02300086753 /* CIFSTypes.swift */; };
799396BF1D48C02300086753 /* SMB2DataTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969B1D48C02300086753 /* SMB2DataTypes.swift */; };
799396C01D48C02300086753 /* SMB2DataTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969B1D48C02300086753 /* SMB2DataTypes.swift */; };
799396C11D48C02300086753 /* SMB2DataTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969B1D48C02300086753 /* SMB2DataTypes.swift */; };
799396C21D48C02300086753 /* SMB2FileHandle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969C1D48C02300086753 /* SMB2FileHandle.swift */; };
799396C31D48C02300086753 /* SMB2FileHandle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969C1D48C02300086753 /* SMB2FileHandle.swift */; };
799396C41D48C02300086753 /* SMB2FileHandle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969C1D48C02300086753 /* SMB2FileHandle.swift */; };
799396C51D48C02300086753 /* SMB2FileOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969D1D48C02300086753 /* SMB2FileOperation.swift */; };
799396C61D48C02300086753 /* SMB2FileOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969D1D48C02300086753 /* SMB2FileOperation.swift */; };
799396C71D48C02300086753 /* SMB2FileOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969D1D48C02300086753 /* SMB2FileOperation.swift */; };
799396C81D48C02300086753 /* SMB2IOCtl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969E1D48C02300086753 /* SMB2IOCtl.swift */; };
799396C91D48C02300086753 /* SMB2IOCtl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969E1D48C02300086753 /* SMB2IOCtl.swift */; };
799396CA1D48C02300086753 /* SMB2IOCtl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969E1D48C02300086753 /* SMB2IOCtl.swift */; };
799396CB1D48C02300086753 /* SMB2Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969F1D48C02300086753 /* SMB2Query.swift */; };
799396CC1D48C02300086753 /* SMB2Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969F1D48C02300086753 /* SMB2Query.swift */; };
799396CD1D48C02300086753 /* SMB2Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993969F1D48C02300086753 /* SMB2Query.swift */; };
799396CE1D48C02300086753 /* SMB2Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A01D48C02300086753 /* SMB2Session.swift */; };
799396CF1D48C02300086753 /* SMB2Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A01D48C02300086753 /* SMB2Session.swift */; };
799396D01D48C02300086753 /* SMB2Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A01D48C02300086753 /* SMB2Session.swift */; };
799396D11D48C02300086753 /* SMB2SetInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A11D48C02300086753 /* SMB2SetInfo.swift */; };
799396D21D48C02300086753 /* SMB2SetInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A11D48C02300086753 /* SMB2SetInfo.swift */; };
799396D31D48C02300086753 /* SMB2SetInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A11D48C02300086753 /* SMB2SetInfo.swift */; };
799396D41D48C02300086753 /* SMB2Tree.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A21D48C02300086753 /* SMB2Tree.swift */; };
799396D51D48C02300086753 /* SMB2Tree.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A21D48C02300086753 /* SMB2Tree.swift */; };
799396D61D48C02300086753 /* SMB2Tree.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A21D48C02300086753 /* SMB2Tree.swift */; };
799396D71D48C02300086753 /* SMB2Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A31D48C02300086753 /* SMB2Types.swift */; };
799396D81D48C02300086753 /* SMB2Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A31D48C02300086753 /* SMB2Types.swift */; };
799396D91D48C02300086753 /* SMB2Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A31D48C02300086753 /* SMB2Types.swift */; };
799396DA1D48C02300086753 /* SMBErrorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A41D48C02300086753 /* SMBErrorType.swift */; };
799396DB1D48C02300086753 /* SMBErrorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A41D48C02300086753 /* SMBErrorType.swift */; };
799396DC1D48C02300086753 /* SMBErrorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A41D48C02300086753 /* SMBErrorType.swift */; };
799396DD1D48C02300086753 /* TCPSocketClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A51D48C02300086753 /* TCPSocketClient.swift */; };
799396DE1D48C02300086753 /* TCPSocketClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A51D48C02300086753 /* TCPSocketClient.swift */; };
799396DF1D48C02300086753 /* TCPSocketClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A51D48C02300086753 /* TCPSocketClient.swift */; };
799396E01D48C02300086753 /* WebDAVFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A61D48C02300086753 /* WebDAVFileProvider.swift */; };
799396E11D48C02300086753 /* WebDAVFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A61D48C02300086753 /* WebDAVFileProvider.swift */; };
799396E21D48C02300086753 /* WebDAVFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 799396A61D48C02300086753 /* WebDAVFileProvider.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
799396671D48B7F600086753 /* FileProvider.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FileProvider.framework; sourceTree = BUILT_PRODUCTS_DIR; };
799396751D48B80D00086753 /* FileProvider.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FileProvider.framework; sourceTree = BUILT_PRODUCTS_DIR; };
799396821D48B82700086753 /* FileProvider.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FileProvider.framework; sourceTree = BUILT_PRODUCTS_DIR; };
7993968B1D48B8C700086753 /* Info-iOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-iOS.plist"; sourceTree = "<group>"; };
7993968C1D48B8C700086753 /* Info-MacOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-MacOS.plist"; sourceTree = "<group>"; };
7993968D1D48B8C700086753 /* Info-tvOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-tvOS.plist"; sourceTree = "<group>"; };
799396921D48C02300086753 /* AEXML.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AEXML.swift; sourceTree = "<group>"; };
799396931D48C02300086753 /* DropboxFileProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropboxFileProvider.swift; sourceTree = "<group>"; };
799396941D48C02300086753 /* FileProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FileProvider.h; sourceTree = "<group>"; };
799396951D48C02300086753 /* FileProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileProvider.swift; sourceTree = "<group>"; };
799396961D48C02300086753 /* LocalFileProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalFileProvider.swift; sourceTree = "<group>"; };
799396971D48C02300086753 /* SMBClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMBClient.swift; sourceTree = "<group>"; };
799396981D48C02300086753 /* SMBFileProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMBFileProvider.swift; sourceTree = "<group>"; };
7993969A1D48C02300086753 /* CIFSTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CIFSTypes.swift; sourceTree = "<group>"; };
7993969B1D48C02300086753 /* SMB2DataTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2DataTypes.swift; sourceTree = "<group>"; };
7993969C1D48C02300086753 /* SMB2FileHandle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2FileHandle.swift; sourceTree = "<group>"; };
7993969D1D48C02300086753 /* SMB2FileOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2FileOperation.swift; sourceTree = "<group>"; };
7993969E1D48C02300086753 /* SMB2IOCtl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2IOCtl.swift; sourceTree = "<group>"; };
7993969F1D48C02300086753 /* SMB2Query.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2Query.swift; sourceTree = "<group>"; };
799396A01D48C02300086753 /* SMB2Session.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2Session.swift; sourceTree = "<group>"; };
799396A11D48C02300086753 /* SMB2SetInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2SetInfo.swift; sourceTree = "<group>"; };
799396A21D48C02300086753 /* SMB2Tree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2Tree.swift; sourceTree = "<group>"; };
799396A31D48C02300086753 /* SMB2Types.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2Types.swift; sourceTree = "<group>"; };
799396A41D48C02300086753 /* SMBErrorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMBErrorType.swift; sourceTree = "<group>"; };
799396A51D48C02300086753 /* TCPSocketClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TCPSocketClient.swift; sourceTree = "<group>"; };
799396A61D48C02300086753 /* WebDAVFileProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebDAVFileProvider.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
799396631D48B7F600086753 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
799396711D48B80D00086753 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
7993967E1D48B82700086753 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
7993965B1D48B7BF00086753 = {
isa = PBXGroup;
children = (
799396911D48C02300086753 /* Sources */,
7993968A1D48B8C700086753 /* Pod */,
799396681D48B7F600086753 /* Products */,
);
sourceTree = "<group>";
};
799396681D48B7F600086753 /* Products */ = {
isa = PBXGroup;
children = (
799396671D48B7F600086753 /* FileProvider.framework */,
799396751D48B80D00086753 /* FileProvider.framework */,
799396821D48B82700086753 /* FileProvider.framework */,
);
name = Products;
sourceTree = "<group>";
};
7993968A1D48B8C700086753 /* Pod */ = {
isa = PBXGroup;
children = (
7993968B1D48B8C700086753 /* Info-iOS.plist */,
7993968C1D48B8C700086753 /* Info-MacOS.plist */,
7993968D1D48B8C700086753 /* Info-tvOS.plist */,
);
path = Pod;
sourceTree = "<group>";
};
799396911D48C02300086753 /* Sources */ = {
isa = PBXGroup;
children = (
799396991D48C02300086753 /* SMBTypes */,
799396921D48C02300086753 /* AEXML.swift */,
799396931D48C02300086753 /* DropboxFileProvider.swift */,
799396941D48C02300086753 /* FileProvider.h */,
799396951D48C02300086753 /* FileProvider.swift */,
799396961D48C02300086753 /* LocalFileProvider.swift */,
799396971D48C02300086753 /* SMBClient.swift */,
799396981D48C02300086753 /* SMBFileProvider.swift */,
799396A51D48C02300086753 /* TCPSocketClient.swift */,
799396A61D48C02300086753 /* WebDAVFileProvider.swift */,
);
path = Sources;
sourceTree = "<group>";
};
799396991D48C02300086753 /* SMBTypes */ = {
isa = PBXGroup;
children = (
7993969A1D48C02300086753 /* CIFSTypes.swift */,
7993969B1D48C02300086753 /* SMB2DataTypes.swift */,
7993969C1D48C02300086753 /* SMB2FileHandle.swift */,
7993969D1D48C02300086753 /* SMB2FileOperation.swift */,
7993969E1D48C02300086753 /* SMB2IOCtl.swift */,
7993969F1D48C02300086753 /* SMB2Query.swift */,
799396A01D48C02300086753 /* SMB2Session.swift */,
799396A11D48C02300086753 /* SMB2SetInfo.swift */,
799396A21D48C02300086753 /* SMB2Tree.swift */,
799396A31D48C02300086753 /* SMB2Types.swift */,
799396A41D48C02300086753 /* SMBErrorType.swift */,
);
path = SMBTypes;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
799396641D48B7F600086753 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
799396721D48B80D00086753 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
7993967F1D48B82700086753 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
799396661D48B7F600086753 /* FileProvider iOS */ = {
isa = PBXNativeTarget;
buildConfigurationList = 7993966F1D48B7F600086753 /* Build configuration list for PBXNativeTarget "FileProvider iOS" */;
buildPhases = (
799396621D48B7F600086753 /* Sources */,
799396631D48B7F600086753 /* Frameworks */,
799396641D48B7F600086753 /* Headers */,
799396651D48B7F600086753 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = "FileProvider iOS";
productName = "FileProvider iOS";
productReference = 799396671D48B7F600086753 /* FileProvider.framework */;
productType = "com.apple.product-type.framework";
};
799396741D48B80D00086753 /* FileProvider OSX */ = {
isa = PBXNativeTarget;
buildConfigurationList = 7993967A1D48B80D00086753 /* Build configuration list for PBXNativeTarget "FileProvider OSX" */;
buildPhases = (
799396701D48B80D00086753 /* Sources */,
799396711D48B80D00086753 /* Frameworks */,
799396721D48B80D00086753 /* Headers */,
799396731D48B80D00086753 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = "FileProvider OSX";
productName = "FileProvider OSX";
productReference = 799396751D48B80D00086753 /* FileProvider.framework */;
productType = "com.apple.product-type.framework";
};
799396811D48B82700086753 /* FileProvider tvOS */ = {
isa = PBXNativeTarget;
buildConfigurationList = 799396871D48B82700086753 /* Build configuration list for PBXNativeTarget "FileProvider tvOS" */;
buildPhases = (
7993967D1D48B82700086753 /* Sources */,
7993967E1D48B82700086753 /* Frameworks */,
7993967F1D48B82700086753 /* Headers */,
799396801D48B82700086753 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = "FileProvider tvOS";
productName = "FileProvider tvOS";
productReference = 799396821D48B82700086753 /* FileProvider.framework */;
productType = "com.apple.product-type.framework";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
7993965C1D48B7BF00086753 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0730;
TargetAttributes = {
799396661D48B7F600086753 = {
CreatedOnToolsVersion = 7.3.1;
};
799396741D48B80D00086753 = {
CreatedOnToolsVersion = 7.3.1;
};
799396811D48B82700086753 = {
CreatedOnToolsVersion = 7.3.1;
};
};
};
buildConfigurationList = 7993965F1D48B7BF00086753 /* Build configuration list for PBXProject "FileProvider" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = 7993965B1D48B7BF00086753;
productRefGroup = 799396681D48B7F600086753 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
799396661D48B7F600086753 /* FileProvider iOS */,
799396741D48B80D00086753 /* FileProvider OSX */,
799396811D48B82700086753 /* FileProvider tvOS */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
799396651D48B7F600086753 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
799396731D48B80D00086753 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
799396801D48B82700086753 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
799396621D48B7F600086753 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
799396B31D48C02300086753 /* LocalFileProvider.swift in Sources */,
799396D41D48C02300086753 /* SMB2Tree.swift in Sources */,
799396C81D48C02300086753 /* SMB2IOCtl.swift in Sources */,
799396DD1D48C02300086753 /* TCPSocketClient.swift in Sources */,
799396D71D48C02300086753 /* SMB2Types.swift in Sources */,
799396C51D48C02300086753 /* SMB2FileOperation.swift in Sources */,
799396BF1D48C02300086753 /* SMB2DataTypes.swift in Sources */,
799396B91D48C02300086753 /* SMBFileProvider.swift in Sources */,
799396BC1D48C02300086753 /* CIFSTypes.swift in Sources */,
799396D11D48C02300086753 /* SMB2SetInfo.swift in Sources */,
799396A71D48C02300086753 /* AEXML.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 */,
799396B01D48C02300086753 /* FileProvider.swift in Sources */,
799396B61D48C02300086753 /* SMBClient.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
799396701D48B80D00086753 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
799396B41D48C02300086753 /* LocalFileProvider.swift in Sources */,
799396D51D48C02300086753 /* SMB2Tree.swift in Sources */,
799396C91D48C02300086753 /* SMB2IOCtl.swift in Sources */,
799396DE1D48C02300086753 /* TCPSocketClient.swift in Sources */,
799396D81D48C02300086753 /* SMB2Types.swift in Sources */,
799396C61D48C02300086753 /* SMB2FileOperation.swift in Sources */,
799396C01D48C02300086753 /* SMB2DataTypes.swift in Sources */,
799396BA1D48C02300086753 /* SMBFileProvider.swift in Sources */,
799396BD1D48C02300086753 /* CIFSTypes.swift in Sources */,
799396D21D48C02300086753 /* SMB2SetInfo.swift in Sources */,
799396A81D48C02300086753 /* AEXML.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 */,
799396AB1D48C02300086753 /* DropboxFileProvider.swift in Sources */,
799396B11D48C02300086753 /* FileProvider.swift in Sources */,
799396B71D48C02300086753 /* SMBClient.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
7993967D1D48B82700086753 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
799396B51D48C02300086753 /* LocalFileProvider.swift in Sources */,
799396D61D48C02300086753 /* SMB2Tree.swift in Sources */,
799396CA1D48C02300086753 /* SMB2IOCtl.swift in Sources */,
799396DF1D48C02300086753 /* TCPSocketClient.swift in Sources */,
799396D91D48C02300086753 /* SMB2Types.swift in Sources */,
799396C71D48C02300086753 /* SMB2FileOperation.swift in Sources */,
799396C11D48C02300086753 /* SMB2DataTypes.swift in Sources */,
799396BB1D48C02300086753 /* SMBFileProvider.swift in Sources */,
799396BE1D48C02300086753 /* CIFSTypes.swift in Sources */,
799396D31D48C02300086753 /* SMB2SetInfo.swift in Sources */,
799396A91D48C02300086753 /* AEXML.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 */,
799396AC1D48C02300086753 /* DropboxFileProvider.swift in Sources */,
799396B21D48C02300086753 /* FileProvider.swift in Sources */,
799396B81D48C02300086753 /* SMBClient.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
799396601D48B7BF00086753 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
};
name = Debug;
};
799396611D48B7BF00086753 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
};
name = Release;
};
7993966D1D48B7F600086753 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
APPLICATION_EXTENSION_API_ONLY = YES;
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;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "Pod/Info-iOS.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
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";
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
7993966E1D48B7F600086753 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
APPLICATION_EXTENSION_API_ONLY = YES;
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;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "Pod/Info-iOS.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MTL_ENABLE_DEBUG_INFO = NO;
PRODUCT_BUNDLE_IDENTIFIER = "com.mousavian.FileProvider-iOS";
PRODUCT_NAME = FileProvider;
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
7993967B1D48B80D00086753 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
APPLICATION_EXTENSION_API_ONLY = YES;
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;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
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_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)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "Pod/Info-MacOS.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
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;
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
7993967C1D48B80D00086753 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
APPLICATION_EXTENSION_API_ONLY = YES;
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;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
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_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;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "Pod/Info-MacOS.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.10;
MTL_ENABLE_DEBUG_INFO = NO;
PRODUCT_BUNDLE_IDENTIFIER = "com.mousavian.FileProvider-OSX";
PRODUCT_NAME = FileProvider;
SDKROOT = macosx;
SKIP_INSTALL = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
799396881D48B82700086753 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
APPLICATION_EXTENSION_API_ONLY = YES;
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;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = 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_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)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "Pod/Info-tvOS.plist";
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;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
799396891D48B82700086753 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
APPLICATION_EXTENSION_API_ONLY = YES;
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;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = 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_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;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "Pod/Info-tvOS.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MTL_ENABLE_DEBUG_INFO = NO;
PRODUCT_BUNDLE_IDENTIFIER = "com.mousavian.FileProvider-tvOS";
PRODUCT_NAME = FileProvider;
SDKROOT = appletvos;
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = 3;
TVOS_DEPLOYMENT_TARGET = 9.0;
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
7993965F1D48B7BF00086753 /* Build configuration list for PBXProject "FileProvider" */ = {
isa = XCConfigurationList;
buildConfigurations = (
799396601D48B7BF00086753 /* Debug */,
799396611D48B7BF00086753 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
7993966F1D48B7F600086753 /* Build configuration list for PBXNativeTarget "FileProvider iOS" */ = {
isa = XCConfigurationList;
buildConfigurations = (
7993966D1D48B7F600086753 /* Debug */,
7993966E1D48B7F600086753 /* Release */,
);
defaultConfigurationIsVisible = 0;
};
7993967A1D48B80D00086753 /* Build configuration list for PBXNativeTarget "FileProvider OSX" */ = {
isa = XCConfigurationList;
buildConfigurations = (
7993967B1D48B80D00086753 /* Debug */,
7993967C1D48B80D00086753 /* Release */,
);
defaultConfigurationIsVisible = 0;
};
799396871D48B82700086753 /* Build configuration list for PBXNativeTarget "FileProvider tvOS" */ = {
isa = XCConfigurationList;
buildConfigurations = (
799396881D48B82700086753 /* Debug */,
799396891D48B82700086753 /* Release */,
);
defaultConfigurationIsVisible = 0;
};
/* End XCConfigurationList section */
};
rootObject = 7993965C1D48B7BF00086753 /* Project object */;
}
@@ -1,5 +1,5 @@
#
# Be sure to run `pod spec lint FileProvider.podspec' to ensure this is a
# Be sure to run `pod spec lint FilesProvider.podspec' to ensure this is a
# valid spec and to remove all comments including this before submitting the spec.
#
# To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html
@@ -15,9 +15,9 @@ Pod::Spec.new do |s|
# summary should be tweet-length, and the description more in depth.
#
s.name = "FileProvider"
s.version = "0.3.2"
s.summary = "NSFileManager replacement for Local and Remote (WebDAV/Dropbox/SMB2) files on iOS and MacOS."
s.name = "FilesProvider"
s.version = "0.21.0"
s.summary = "FileManager replacement for Local and Remote (WebDAV/FTP/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"
@@ -58,7 +58,7 @@ Pod::Spec.new do |s|
s.author = { "Amir Abbas Mousavian" => "a.mosavian@gmail.com" }
# Or just: s.author = "Amir Abbas Mousavian"
# s.authors = { "Amir Abbas Mousavian" => "a.mosavian@gmail.com" }
# s.social_media_url = "https://twitter.com/amosavian"
s.social_media_url = "https://twitter.com/amosavian"
# ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
#
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0730"
LastUpgradeVersion = "0820"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -15,9 +15,9 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "799396741D48B80D00086753"
BuildableName = "FileProvider.framework"
BlueprintName = "FileProvider OSX"
ReferencedContainer = "container:FileProvider.xcodeproj">
BuildableName = "FilesProvider.framework"
BlueprintName = "FilesProvider OSX"
ReferencedContainer = "container:FilesProvider.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
@@ -26,6 +26,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
@@ -36,6 +37,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
@@ -46,9 +48,9 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "799396741D48B80D00086753"
BuildableName = "FileProvider.framework"
BlueprintName = "FileProvider OSX"
ReferencedContainer = "container:FileProvider.xcodeproj">
BuildableName = "FilesProvider.framework"
BlueprintName = "FilesProvider OSX"
ReferencedContainer = "container:FilesProvider.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
@@ -64,9 +66,9 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "799396741D48B80D00086753"
BuildableName = "FileProvider.framework"
BlueprintName = "FileProvider OSX"
ReferencedContainer = "container:FileProvider.xcodeproj">
BuildableName = "FilesProvider.framework"
BlueprintName = "FilesProvider OSX"
ReferencedContainer = "container:FilesProvider.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0730"
LastUpgradeVersion = "0820"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -15,9 +15,9 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "799396661D48B7F600086753"
BuildableName = "FileProvider.framework"
BlueprintName = "FileProvider iOS"
ReferencedContainer = "container:FileProvider.xcodeproj">
BuildableName = "FilesProvider.framework"
BlueprintName = "FilesProvider iOS"
ReferencedContainer = "container:FilesProvider.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
@@ -26,6 +26,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
@@ -36,6 +37,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
@@ -46,9 +48,9 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "799396661D48B7F600086753"
BuildableName = "FileProvider.framework"
BlueprintName = "FileProvider iOS"
ReferencedContainer = "container:FileProvider.xcodeproj">
BuildableName = "FilesProvider.framework"
BlueprintName = "FilesProvider iOS"
ReferencedContainer = "container:FilesProvider.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
@@ -64,9 +66,9 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "799396661D48B7F600086753"
BuildableName = "FileProvider.framework"
BlueprintName = "FileProvider iOS"
ReferencedContainer = "container:FileProvider.xcodeproj">
BuildableName = "FilesProvider.framework"
BlueprintName = "FilesProvider iOS"
ReferencedContainer = "container:FilesProvider.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0730"
LastUpgradeVersion = "0810"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -15,9 +15,9 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "799396811D48B82700086753"
BuildableName = "FileProvider.framework"
BlueprintName = "FileProvider tvOS"
ReferencedContainer = "container:FileProvider.xcodeproj">
BuildableName = "FilesProvider.framework"
BlueprintName = "FilesProvider tvOS"
ReferencedContainer = "container:FilesProvider.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
@@ -26,6 +26,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
@@ -36,6 +37,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
@@ -46,9 +48,9 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "799396811D48B82700086753"
BuildableName = "FileProvider.framework"
BlueprintName = "FileProvider tvOS"
ReferencedContainer = "container:FileProvider.xcodeproj">
BuildableName = "FilesProvider.framework"
BlueprintName = "FilesProvider tvOS"
ReferencedContainer = "container:FilesProvider.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
@@ -64,9 +66,9 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "799396811D48B82700086753"
BuildableName = "FileProvider.framework"
BlueprintName = "FileProvider tvOS"
ReferencedContainer = "container:FileProvider.xcodeproj">
BuildableName = "FilesProvider.framework"
BlueprintName = "FilesProvider tvOS"
ReferencedContainer = "container:FilesProvider.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0910"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "79D903501FAB647400D61D31"
BuildableName = "FilesProviderTests.xctest"
BlueprintName = "FilesProviderTests"
ReferencedContainer = "container:FilesProvider.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
+1 -1
View File
@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2016 Amir Abbas Mousavian
Copyright (c) 2016-17 Amir Abbas Mousavian
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+5
View File
@@ -0,0 +1,5 @@
import PackageDescription
let package = Package(
name: "FilesProvider"
)
+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>
+377 -142
View File
@@ -1,64 +1,105 @@
# FileProvider (experimental)
![File Provider](fileprovider.png)
>This Swift library provide a swifty way to deal with local and remote files and directories in a unified way.
<center>
[![Swift Version][swift-image]][swift-url]
[![Platform][platform-image]](#)
[![License][license-image]][license-url]
[![Platform](https://img.shields.io/badge/Platform-iOS%2C%20OSX-lightgray.svg)]()
[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/FileProvider.svg)](https://img.shields.io/cocoapods/v/FileProvider.svg)
[![codebeat badge][codebeat-image]][codebeat-url]
[![Build Status][travis-image]][travis-url]
[![Codebeat Badge][codebeat-image]][codebeat-url]
[![Release version][release-image]][release-url]
[![CocoaPods version](https://img.shields.io/cocoapods/v/FilesProvider.svg)][cocoapods]
[![Carthage compatible][carthage-image]](https://github.com/Carthage/Carthage)
[![Cocoapods Downloads][cocoapods-downloads]][cocoapods]
[![Cocoapods Apps][cocoapods-apps]][cocoapods]
</center>
<!---
[![Build Status][travis-image]][travis-url]
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
[![Cocoapods Doc][docs-image]][docs-url]
[![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, FTP, Dropbox, OneDrive and SMB2 (incomplete) and local files.
All functions are async calls and it wont block your main thread.
Local and WebDAV providers are fully tested and can be used in production environment.
All functions do async calls and it wont block your main thread.
## Features
- [x] **LocalFileProvider** a wrapper around `NSFileManager` with some additions like searching and reading a portion of file.
- [x] **WebDAVFileProvider** WebDAV protocol is usual file transmission system on Macs.
- [ ] **SMBFileProvider** SMB/CIFS and SMB2/3 are file and printer sharing protocol which is originated from IBM & Microsoft and SMB2/3 is now replacing AFP protocol on MacOS. I implemented data types and some basic functions but *main interface is not implemented yet!*
- [ ] **DropboxFileProvider** *partially implemented*
- [ ] **FTPFileProvider**
- [ ] **AmazonS3FileProvider**
- [x] **LocalFileProvider** a wrapper around `FileManager` with some additions like builtin coordinating, searching and reading a portion of file.
- [x] **CloudFileProvider** A wrapper around app's ubiquitous container API of iCloud Drive.
- [x] **WebDAVFileProvider** WebDAV protocol is defacto file transmission standard, supported by some cloud services like `ownCloud`, `Box.com` and `Yandex.disk`.
- [x] **FTPFileProvider** While deprecated in 1990s due to serious security concerns, it's still in use on some Web hosts.
- [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 REST API, works with `onedrive.com` and compatible (business) servers.
* For now it has limitation in uploading files up to 100MB.
- [ ] **AmazonS3FileProvider** Amazon storage backend. Used by many sites.
- [ ] **GoogleFileProvider** A wrapper around Goodle Drive REST 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!*.
* SMBv1/CIFS is insecure, deprecated and kinda tricky to be implemented due to strict memory allignment in Swift.
## Requirements
- **Swift 2.2**
- **Swift 3.0 or higher**
- iOS 8.0 , OSX 10.10
- XCode 7.3
- XCode 8.0
Legacy version is available in swift-2 branch.
## Installation
### Cocoapods / Carthage / Swift Package Manager
### Important: this library has been renamed to avoid conflict in iOS 11, macOS 10.13 and Xcode 9.0. Please read issue [#53](https://github.com/amosavian/FileProvider/issues/53) to find more.
FileProvider supports both CocoaPods.
### Cocoapods / Carthage / Swift Package Manager
Add this line to your pods file:
pod "FileProvider"
```ruby
pod "FilesProvider"
```
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)
```
### 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 `FilesProvider.xcodeproj` to you Xcode workspace and add the framework to your Embeded Binaries in target.
## Usage
@@ -68,71 +109,102 @@ 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
import FilesProvider
is equal to:
let documentPath = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true);
let documentsURL = NSURL(fileURLWithPath: documentPath);
let documentsProvider = LocalFileProvider(baseURL: documentsURL)
let documentsProvider = LocalFileProvider()
// Equals with:
let documentsProvider = LocalFileProvider(for: .documentDirectory, in: .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
import FilesProvider
let documentsProvider = LocalFileProvider(sharedContainerId: "group.yourcompany.appContainer")
// Replace your group identifier
```
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 look at [here](https://medium.com/ios-os-x-development/icloud-drive-documents-1a46b5706fe1) to see how to update project settings then use below code, This will automatically manager creating Documents folder in container:
```swift
import FilesProvider
let documentsProvider = CloudFileProvider(containerId: nil)
```
For remote file providers authentication may be necessary:
let credential = NSURLCredential(user: "user", password: "pass", persistence: NSURLCredentialPersistence.Permanent)
let webdavProvider = WebDAVFileProvider(baseURL: "http://www.example.com/dav", credential: credential)
``` swift
import FilesProvider
* 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.
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) or FTP 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 & 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 `url(of:)` 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).
There's simply three method which indicated whether the operation failed, succeed and how much of operation has been done (suitable for uploading and downloading operations).
Your class should conforms `FileProviderDelegate` class:
override func viewDidLoad() {
documentsProvider.delegate = self
}
```swift
override func viewDidLoad() {
documentsProvider.delegate = self as FileProviderDelegate
}
func fileproviderSucceed(fileProvider: FileProvider, operation: FileOperation) {
switch operation {
case .Copy(source: let source, destination: let dest):
NSLog("\(source) copied to \(dest).")
case .Remove(path: let path):
NSLog("\(path) has been deleted.")
default:
break
}
}
func fileproviderFailed(fileProvider: FileProvider, operation: FileOperation) {
switch operation {
case .Copy(source: let source, destination: let dest):
NSLog("copy of \(source) failed.")
case .Remove(path: let path):
NSLog("\(path) can't be deleted.")
default:
break
}
}
func fileproviderProgress(fileProvider: FileProvider, operation: FileOperation, progress: Float) {
switch operation {
case .Copy(source: let source, destination: let dest):
NSLog("Copy\(source) to \(dest): \(progress * 100) completed.")
default:
break
}
func fileproviderSucceed(_ fileProvider: FileProviderOperations, operation: FileOperationType) {
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: FileOperationType) {
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: FileOperationType, 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.
@@ -140,11 +212,11 @@ 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.**
**Note: In `LocalFileProvider`, these methods will be called for files in a directory and its subfolders recursively.**
### Directory contents and file attributes
@@ -152,123 +224,286 @@ There is a `FileObject` class which holds file attributes like size and creation
For a single file:
documentsProvider.attributesOfItemAtPath(path: "/file.txt", completionHandler: {
(attributes: LocalFileObject?, error: ErrorType?) -> Void} in
if let attributes = attributes {
print("File Size: \(attributes.size)")
print("Creation Date: \(attributes.createdDate)")
print("Modification Date: \(modifiedDate)")
print("Is Read Only: \(isReadOnly)")
}
)
```swift
documentsProvider.attributesOfItem(path: "/file.txt", completionHandler: {
attributes, error in
if let attributes = attributes {
print("File Size: \(attributes.size)")
print("Creation Date: \(attributes.creationDate)")
print("Modification Date: \(attributes.modifiedDate)")
print("Is Read Only: \(attributes.isReadOnly)")
}
})
```
To get list of files in a directory:
documentsProvider.contentsOfDirectoryAtPath(path: "/", completionHandler: {
(contents: [LocalFileObject], error: ErrorType?) -> Void} in
for file in contents {
print("Name: \(attributes.name)")
print("Size: \(attributes.size)")
print("Creation Date: \(attributes.createdDate)")
print("Modification Date: \(modifiedDate)")
}
)
```swift
documentsProvider.contentsOfDirectory(path: "/", completionHandler: {
contents, error in
for file in contents {
print("Name: \(file.name)")
print("Size: \(file.size)")
print("Creation Date: \(file.creationDate)")
print("Modification Date: \(file.modifiedDate)")
}
})
```
### Change current directory
To get size of strage and used/free space:
documentsProvider.currentPath = "/New Folder"
// now path is ~/Documents/New Folder
```swift
func storageProperties(completionHandler: { total, used in
print("Total Storage Space: \(total)")
print("Used Space: \(used)")
print("Free Space: \(total - used)")
})
```
You can then pass "" (empty string) to contentsOfDirectoryAtPath method to list files in current directory.
* if this function is unavailable on provider or an error has been occurred, total space will be reported `-1` and used space `0`
### Creating File and Folders
Creating new directory:
documentsProvider.createFolder(folderName: "new folder", atPath: "/", completionHandler: nil)
```swift
documentsProvider.create(folder: "new folder", at: "/", completionHandler: { error in
if let error = error {
// Error handling here
} else {
// The operation succeed
}
})
```
Creating new file from data stream:
let data = "hello world!".dataUsingEncoding(NSUTF8StringEncoding)
let file = FileObject(name: "old.txt", createdDate: NSDate(), modifiedDate: NSDate(), isHidden: false, isReadOnly: true)
documentsProvider.createFile(fileAttribs: file, atPath: "/", contents: data, completionHandler: nil)
To create a file, use `writeContents(path:, content:, atomically:, completionHandler:)` method.
### Copy and Move/Rename Files
Copy file old.txt to new.txt in current path:
documentsProvider.copyItemAtPath(path: "new folder/old.txt", toPath: "new.txt", overwrite: false, completionHandler: nil)
```swift
documentsProvider.copyItem(path: "new folder/old.txt", to: "new.txt", overwrite: false, completionHandler: nil)
```
Move file old.txt to new.txt in current path:
documentsProvider.moveItemAtPath(path: "new folder/old.txt", toPath: "new.txt", overwrite: false, completionHandler: nil)
```swift
documentsProvider.moveItem(path: "new folder/old.txt", to: "new.txt", overwrite: false, completionHandler: nil)
```
**Note:** To have a consistent behavior, create intermediate directories first if necessary.
### Delete Files
documentsProvider.removeItemAtPath(path: "new.txt", completionHandler: nil)
```swift
documentsProvider.removeItem(path: "new.txt", completionHandler: nil)
```
***Caution:*** This method will delete directories with all it's content recursively.
### Fetching Contents of File
### Retrieve Content of File
There is two method for this purpose, one of them loads entire file into `Data` and another can load a portion of file.
THere is two method for this purpose, one of them loads entire file into NSData and another can load a portion of file.
documentsProvider.contentsAtPath(path: "old.txt", completionHandler: {
(contents: NSData?, error: ErrorType?) -> Void
if let contents = contents {
print(String(data: contents, encoding: NSUTF8StringEncoding)) // "hello world!"
}
})
```swift
documentsProvider.contents(path: "old.txt", completionHandler: {
contents, error in
if let contents = contents {
print(String(data: contents, encoding: .utf8)) // "hello world!"
}
})
```
If you want to retrieve a portion of file you should can `contentsAtPath` method with offset and length arguments. Please note first byte of file has offset: 0.
If you want to retrieve a portion of file you can use `contents` method with offset and length arguments. Please note first byte of file has offset: 0.
documentsProvider.contentsAtPath(path: "old.txt", offset: 2, length: 5, completionHandler: {
(contents: NSData?, error: ErrorType?) -> Void
if let contents = contents {
print(String(data: contents, encoding: NSUTF8StringEncoding)) // "llo w"
}
})
```swift
documentsProvider.contents(path: "old.txt", offset: 2, length: 5, completionHandler: {
contents, error in
if let contents = contents {
print(String(data: contents, encoding: .utf8)) // "llo w"
}
})
```
### Write Data To Files
let data = "What's up Newyork!".dataUsingEncoding(NSUTF8StringEncoding)
documentsProvider.writeContentsAtPath(path: "old.txt", contents data: data, atomically: true, completionHandler: nil)
```swift
let data = "What's up Newyork!".data(encoding: .utf8)
documentsProvider.writeContents(path: "old.txt", content: data, atomically: true, completionHandler: nil)
```
### Monitoring FIle Changes
### Copying Files to and From Local Storage
There are two methods to download and upload files between provider's and local storage. These methods use `URLSessionDownloadTask` and `URLSessionUploadTask` classes and allows to use background session and provide progress via delegate.
To upload a file:
```swift
let fileURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("image.jpg")
documentsProvider.copyItem(localFile: fileURL, to: "/upload/image.jpg", overwrite: true, completionHandler: nil)
```
To download a file:
```swift
let fileURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("image.jpg")
documentsProvider.copyItem(path: "/download/image.jpg", toLocalURL: fileURL, overwrite: true, completionHandler: nil)
```
* It's safe only to assume these methods **won't** handle directories to upload/download recursively. If you need, you can list directories, create directories on target and copy files using these methods.
* FTP provider allows developer to either use apple implemented `URLSessionDownloadTask` or custom implemented method based on stream task via `useAppleImplementation` property. FTP protocol is not supported by background session.
### Operation Progress
Creating/Copying/Deleting/Searching functions return a `(NS)Progress`. It provides operation type, progress and a `.cancel()` method which allows you to cancel operation in midst. You can check `cancellable` property to check either you can cancel operation via this object or not.
- **Note:** Progress reporting is not supported by native `(NS)FileManager` so `LocalFileProvider`.
### Undo Operations
Providers conform to `FileProviderUndoable` can perform undo for **some** operations like moving/renaming, copying and creating (file or folder). **Now, only `LocalFileProvider` supports this feature.** To implement:
```swift
// To setup a new UndoManager:
documentsProvider.setupUndoManager()
// or if you have an UndoManager object already:
documentsProvider.undoManager = self.undoManager
// e.g.: To undo last operation manually:
documentsProvider.undoManager?.undo()
```
You can also bind `UndoManager` object with view controller to use shake gesture and builtin undo support in iOS/macOS, add these code to your ViewController class like this sample code:
```swift
class ViewController: UIViewController
override var canBecomeFirstResponder: Bool {
return true
}
override var undoManager: UndoManager? {
return (provider as? FileProvideUndoable)?.undoManager
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Your code here
UIApplication.shared.applicationSupportsShakeToEdit = true
self.becomeFirstResponder()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Your code here
UIApplication.shared.applicationSupportsShakeToEdit = false
self.resignFirstResponder()
}
// The rest of your implementation
}
```
### File Coordination
`LocalFileProvider` and its descendents has a `isCoordinating` property. By setting this, provider will use `NSFileCoordinating` class to do all file operations. It's mandatory for iCloud, while recommended when using shared container or anywhere that simultaneous operations on a file/folder is common.
### Monitoring File Changes
You can monitor updates in some file system (Local and SMB2), there is three methods in supporting provider you can use to register a handler, to unregister and to check whether it's being monitored or not. It's useful to find out when new files added or removed from directory and update user interface. The handler will be dispatched to main threads to avoid UI bugs with a 0.25 sec delay.
documentsProvider.registerNotifcation(provider.currentPath)
{
// calling functions to update UI
}
```swift
// to register a new notification handler
documentsProvider.registerNotifcation(path: "/") {
// calling functions to update UI
}
// To discontinue monitoring folders:
documentsProvider.unregisterNotifcation(provider.currentPath)
// To discontinue monitoring folders:
documentsProvider.unregisterNotifcation(path: "/")
```
* **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) // or nil which renders to default dimension of provider
if documentsProvider.thumbnailOfFileSupported(path: path {
documentsProvider.thumbnailOfFile(path: file.path, dimension: thumbSize, completionHandler: { (image, error) in
// Interacting with UI must be placed in main thread
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, location, 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.
Things you may consider to help us:
- [x] Implement Test-case (`XCTest`)
- [ ] Implement request/response stack for `SMBClient`
- [ ] Add Sample project for iOS
- [ ] Add Sample project for macOS
## Projects in use
* [EDM - Browse and Receive Files](https://itunes.apple.com/us/app/edm-browse-and-receive-files/id948397575?ls=1&mt=8)
* [File Manager - PDF Reader & Music Player](https://itunes.apple.com/us/app/file-manager-pdf-reader-music/id1017809685?ls=1&mt=8)
If you used this library in your project, you can open an issue to inform us.
## Meta
Amir-Abbas Mousavian [@amosavian](https://twitter.com/amosavian)
Amir-Abbas Mousavian [@amosavian](https://twitter.com/amosavian)
Thanks to [Hootan Moradi](https://github.com/hoootan) for designing logo.
Distributed under the MIT license. See `LICENSE` for more information.
[https://github.com/yourname/github-link](https://github.com/dbader/)
[https://github.com/amosavian/](https://github.com/amosavian/)
[swift-image]:https://img.shields.io/badge/swift-2.2-green.svg
[cocoapods]: https://cocoapods.org/pods/FilesProvider
[cocoapods-old]: https://cocoapods.org/pods/FileProvider
[swift-image]: https://img.shields.io/badge/swift-3.0,%203.1-orange.svg
[swift-url]: https://swift.org/
[license-image]: https://img.shields.io/badge/License-MIT-blue.svg
[platform-image]: https://img.shields.io/cocoapods/p/FilesProvider.svg
[license-image]: https://img.shields.io/github/license/amosavian/FileProvider.svg
[license-url]: LICENSE
[codebeat-image]: https://codebeat.co/badges/7b359f48-78eb-4647-ab22-56262a827517
[codebeat-url]: https://codebeat.co/projects/github-com-amosavian-fileprovider
<!---
[travis-image]: https://img.shields.io/travis/dbader/node-datadog-metrics/master.svg?style=flat-square
[travis-url]: https://travis-ci.org/dbader/node-datadog-metrics
--->
[travis-image]: https://api.travis-ci.org/amosavian/FileProvider.svg?branch=swift-3
[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
[carthage-image]: https://img.shields.io/badge/Carthage-compatible-4BC51D.svg
[cocoapods-downloads-old]: https://img.shields.io/cocoapods/dt/FileProvider.svg
[cocoapods-apps-old]: https://img.shields.io/cocoapods/at/FileProvider.svg
[cocoapods-downloads]: https://img.shields.io/cocoapods/dt/FilesProvider.svg
[cocoapods-apps]: https://img.shields.io/cocoapods/at/FilesProvider.svg
[docs-image]: https://img.shields.io/cocoapods/metrics/doc-percent/FilesProvider.svg
[docs-url]: http://cocoadocs.org/docsets/FilesProvider/
-492
View File
@@ -1,492 +0,0 @@
//
// AEXML.swift
//
// Copyright (c) 2014 Marko Tadić <tadija@me.com> http://tadija.net
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
// MARK: - AEXMLElement
/**
This is base class for holding XML structure.
You can access its structure by using subscript like this: `element["foo"]["bar"]` which would
return `<bar></bar>` element from `<element><foo><bar></bar></foo></element>` XML as an `AEXMLElement` object.
*/
public class AEXMLElement {
/// A type representing an error value that can be inside `error` property.
public enum Error: ErrorType {
case ElementNotFound
case RootElementMissing
}
private struct Defaults {
static let name = String()
static let attributes = [String : String]()
}
// MARK: Properties
/// Every `AEXMLElement` should have its parent element instead of `AEXMLDocument` which parent is `nil`.
public private(set) weak var parent: AEXMLElement?
/// Child XML elements.
public private(set) var children: [AEXMLElement] = [AEXMLElement]()
/// XML Element name (defaults to empty string).
public var name: String
/// XML Element value.
public var value: String?
/// XML Element attributes (defaults to empty dictionary).
public var attributes: [String : String]
/// Error value (`nil` if there is no error).
public var error: Error?
/// String representation of `value` property (if `value` is `nil` this is empty String).
public var stringValue: String { return value ?? String() }
/// Boolean representation of `value` property (if `value` is "true" or 1 this is `True`, otherwise `False`).
public var boolValue: Bool { return stringValue.lowercaseString == "true" || Int(stringValue) == 1 ? true : false }
/// Integer representation of `value` property (this is **0** if `value` can't be represented as Integer).
public var intValue: Int { return Int(stringValue) ?? 0 }
/// Double representation of `value` property (this is **0.00** if `value` can't be represented as Double).
public var doubleValue: Double { return (stringValue as NSString).doubleValue }
// MARK: Lifecycle
/**
Designated initializer - all parameters are optional.
- parameter name: XML element name.
- parameter value: XML element value
- parameter attributes: XML element attributes
- returns: An initialized `AEXMLElement` object.
*/
public init(_ name: String? = nil, value: String? = nil, attributes: [String : String]? = nil) {
self.name = name ?? Defaults.name
self.value = value
self.attributes = attributes ?? Defaults.attributes
}
// MARK: XML Read
/// The first element with given name **(Empty element with error if not exists)**.
public subscript(key: String) -> AEXMLElement {
guard let
first = children.filter({ $0.name == key }).first
else {
let errorElement = AEXMLElement(key)
errorElement.error = Error.ElementNotFound
return errorElement
}
return first
}
/// Returns all of the elements with equal name as `self` **(nil if not exists)**.
public var all: [AEXMLElement]? { return parent?.children.filter { $0.name == self.name } }
/// Returns the first element with equal name as `self` **(nil if not exists)**.
public var first: AEXMLElement? { return all?.first }
/// Returns the last element with equal name as `self` **(nil if not exists)**.
public var last: AEXMLElement? { return all?.last }
/// Returns number of all elements with equal name as `self`.
public var count: Int { return all?.count ?? 0 }
private func allWithCondition(fulfillCondition: (element: AEXMLElement) -> Bool) -> [AEXMLElement]? {
var found = [AEXMLElement]()
if let elements = all {
for element in elements {
if fulfillCondition(element: element) {
found.append(element)
}
}
return found.count > 0 ? found : nil
} else {
return nil
}
}
/**
Returns all elements with given value.
- parameter value: XML element value.
- returns: Optional Array of found XML elements.
*/
public func allWithValue(value: String) -> [AEXMLElement]? {
let found = allWithCondition { (element) -> Bool in
return element.value == value
}
return found
}
/**
Returns all elements with given attributes.
- parameter attributes: Dictionary of Keys and Values of attributes.
- returns: Optional Array of found XML elements.
*/
public func allWithAttributes(attributes: [String : String]) -> [AEXMLElement]? {
let found = allWithCondition { (element) -> Bool in
var countAttributes = 0
for (key, value) in attributes {
if element.attributes[key] == value {
countAttributes += 1
}
}
return countAttributes == attributes.count
}
return found
}
// MARK: XML Write
/**
Adds child XML element to `self`.
- parameter child: Child XML element to add.
- returns: Child XML element with `self` as `parent`.
*/
public func addChild(child: AEXMLElement) -> AEXMLElement {
child.parent = self
children.append(child)
return child
}
/**
Adds child XML element to `self`.
- parameter name: Child XML element name.
- parameter value: Child XML element value.
- parameter attributes: Child XML element attributes.
- returns: Child XML element with `self` as `parent`.
*/
public func addChild(name name: String, value: String? = nil, attributes: [String : String]? = nil) -> AEXMLElement {
let child = AEXMLElement(name, value: value, attributes: attributes)
return addChild(child)
}
/// Removes `self` from `parent` XML element.
public func removeFromParent() {
parent?.removeChild(self)
}
private func removeChild(child: AEXMLElement) {
if let childIndex = children.indexOf({ $0 === child }) {
children.removeAtIndex(childIndex)
}
}
private var parentsCount: Int {
var count = 0
var element = self
while let parent = element.parent {
count += 1
element = parent
}
return count
}
private func indentation(depth: Int) -> String {
var count = depth
var indent = String()
while count > 0 {
indent += "\t"
count -= 1
}
return indent
}
/// Complete hierarchy of `self` and `children` in **XML** escaped and formatted String
public var xmlString: String {
var xml = String()
// open element
xml += indentation(parentsCount - 1)
xml += "<\(name)"
if attributes.count > 0 {
// insert attributes
for (key, value) in attributes {
xml += " \(key)=\"\(value.xmlEscaped)\""
}
}
if value == nil && children.count == 0 {
// close element
xml += " />"
} else {
if children.count > 0 {
// add children
xml += ">\n"
for child in children {
xml += "\(child.xmlString)\n"
}
// add indentation
xml += indentation(parentsCount - 1)
xml += "</\(name)>"
} else {
// insert string value and close element
xml += ">\(stringValue.xmlEscaped)</\(name)>"
}
}
return xml
}
/// Same as `xmlString` but without `\n` and `\t` characters
public var xmlStringCompact: String {
let chars = NSCharacterSet(charactersInString: "\n\t")
return xmlString.componentsSeparatedByCharactersInSet(chars).joinWithSeparator("")
}
}
public extension String {
/// String representation of self with XML special characters escaped.
public var xmlEscaped: String {
// we need to make sure "&" is escaped first. Not doing this may break escaping the other characters
var escaped = stringByReplacingOccurrencesOfString("&", withString: "&amp;", options: .LiteralSearch)
// replace the other four special characters
let escapeChars = ["<" : "&lt;", ">" : "&gt;", "'" : "&apos;", "\"" : "&quot;"]
for (char, echar) in escapeChars {
escaped = escaped.stringByReplacingOccurrencesOfString(char, withString: echar, options: .LiteralSearch)
}
return escaped
}
}
// MARK: - AEXMLDocument
/**
This class is inherited from `AEXMLElement` and has a few addons to represent **XML Document**.
XML Parsing is also done with this object.
*/
public class AEXMLDocument: AEXMLElement {
private struct Defaults {
static let version = 1.0
static let encoding = "utf-8"
static let standalone = "no"
static let documentName = "AEXMLDocument"
}
/// Default options used by NSXMLParser
public struct NSXMLParserOptions {
public var shouldProcessNamespaces = false
public var shouldReportNamespacePrefixes = false
public var shouldResolveExternalEntities = false
public init() {}
}
// MARK: Properties
/// This is only used for XML Document header (default value is 1.0).
public let version: Double
/// This is only used for XML Document header (default value is "utf-8").
public let encoding: String
/// This is only used for XML Document header (default value is "no").
public let standalone: String
/// Options for NSXMLParser (default values are `false`)
public let xmlParserOptions: NSXMLParserOptions
/// Root (the first child element) element of XML Document **(Empty element with error if not exists)**.
public var root: AEXMLElement {
guard let rootElement = children.first else {
let errorElement = AEXMLElement()
errorElement.error = Error.RootElementMissing
return errorElement
}
return rootElement
}
// MARK: Lifecycle
/**
Designated initializer - Creates and returns XML Document object.
- parameter version: Version value for XML Document header (defaults to 1.0).
- parameter encoding: Encoding value for XML Document header (defaults to "utf-8").
- parameter standalone: Standalone value for XML Document header (defaults to "no").
- parameter root: Root XML element for XML Document (defaults to `nil`).
- parameter xmlParserOptions: Options for NSXMLParser (defaults to `false` for all).
- returns: An initialized XML Document object.
*/
public init(version: Double = Defaults.version,
encoding: String = Defaults.encoding,
standalone: String = Defaults.standalone,
root: AEXMLElement? = nil,
xmlParserOptions: NSXMLParserOptions = NSXMLParserOptions())
{
// set document properties
self.version = version
self.encoding = encoding
self.standalone = standalone
self.xmlParserOptions = xmlParserOptions
// init super with default name
super.init(Defaults.documentName)
// document has no parent element
parent = nil
// add root element to document (if any)
if let rootElement = root {
addChild(rootElement)
}
}
/**
Convenience initializer - used for parsing XML data (by calling `loadXMLData:` internally).
- parameter version: Version value for XML Document header (defaults to 1.0).
- parameter encoding: Encoding value for XML Document header (defaults to "utf-8").
- parameter standalone: Standalone value for XML Document header (defaults to "no").
- parameter xmlData: XML data to parse.
- parameter xmlParserOptions: Options for NSXMLParser (defaults to `false` for all).
- returns: An initialized XML Document object containing parsed data. Throws error if data could not be parsed.
*/
public convenience init(version: Double = Defaults.version,
encoding: String = Defaults.encoding,
standalone: String = Defaults.standalone,
xmlData: NSData,
xmlParserOptions: NSXMLParserOptions = NSXMLParserOptions()) throws
{
self.init(version: version, encoding: encoding, standalone: standalone, xmlParserOptions: xmlParserOptions)
try loadXMLData(xmlData)
}
// MARK: Read XML
/**
Creates instance of `AEXMLParser` (private class which is simple wrapper around `NSXMLParser`)
and starts parsing the given XML data. Throws error if data could not be parsed.
- parameter data: XML which should be parsed.
*/
public func loadXMLData(data: NSData) throws {
children.removeAll(keepCapacity: false)
let xmlParser = AEXMLParser(xmlDocument: self, xmlData: data)
try xmlParser.parse()
}
// MARK: Override
/// Override of `xmlString` property of `AEXMLElement` - it just inserts XML Document header at the beginning.
public override var xmlString: String {
var xml = "<?xml version=\"\(version)\" encoding=\"\(encoding)\" standalone=\"\(standalone)\"?>\n"
for child in children {
xml += child.xmlString
}
return xml
}
}
// MARK: - AEXMLParser
private class AEXMLParser: NSObject, NSXMLParserDelegate {
// MARK: Properties
let xmlDocument: AEXMLDocument
let xmlData: NSData
var currentParent: AEXMLElement?
var currentElement: AEXMLElement?
var currentValue = String()
var parseError: NSError?
// MARK: Lifecycle
init(xmlDocument: AEXMLDocument, xmlData: NSData) {
self.xmlDocument = xmlDocument
self.xmlData = xmlData
currentParent = xmlDocument
super.init()
}
// MARK: XML Parse
func parse() throws {
let parser = NSXMLParser(data: xmlData)
parser.delegate = self
parser.shouldProcessNamespaces = xmlDocument.xmlParserOptions.shouldProcessNamespaces
parser.shouldReportNamespacePrefixes = xmlDocument.xmlParserOptions.shouldReportNamespacePrefixes
parser.shouldResolveExternalEntities = xmlDocument.xmlParserOptions.shouldResolveExternalEntities
let success = parser.parse()
if !success {
throw parseError ?? NSError(domain: "net.tadija.AEXML", code: 1, userInfo: nil)
}
}
// MARK: NSXMLParserDelegate
@objc func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
currentValue = String()
currentElement = currentParent?.addChild(name: elementName, attributes: attributeDict)
currentParent = currentElement
}
@objc func parser(parser: NSXMLParser, foundCharacters string: String) {
currentValue += string
let newValue = currentValue.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
currentElement?.value = newValue == String() ? nil : newValue
}
@objc func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
currentParent = currentParent?.parent
currentElement = nil
}
@objc func parser(parser: NSXMLParser, parseErrorOccurred parseError: NSError) {
self.parseError = parseError
}
}
+128
View File
@@ -0,0 +1,128 @@
//
// Document.swift
//
// Copyright (c) 2014-2016 Marko Tadić <tadija@me.com> http://tadija.net
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
This class is inherited from `AEXMLElement` and has a few addons to represent **XML Document**.
XML Parsing is also done with this object.
*/
internal class AEXMLDocument: AEXMLElement {
// MARK: - Properties
/// Root (the first child element) element of XML Document **(Empty element with error if not exists)**.
open var root: AEXMLElement {
guard let rootElement = children.first else {
let errorElement = AEXMLElement(name: "Error")
errorElement.error = AEXMLError.rootElementMissing
return errorElement
}
return rootElement
}
open let options: AEXMLOptions
// MARK: - Lifecycle
/**
Designated initializer - Creates and returns new XML Document object.
- parameter root: Root XML element for XML Document (defaults to `nil`).
- parameter options: Options for XML Document header and parser settings (defaults to `AEXMLOptions()`).
- returns: Initialized XML Document object.
*/
public init(root: AEXMLElement? = nil, options: AEXMLOptions = AEXMLOptions()) {
self.options = options
let documentName = String(describing: AEXMLDocument.self)
super.init(name: documentName)
// document has no parent element
parent = nil
// add root element to document (if any)
if let rootElement = root {
_ = addChild(rootElement)
}
}
/**
Convenience initializer - used for parsing XML data (by calling `loadXMLData:` internally).
- parameter xmlData: XML data to parse.
- parameter options: Options for XML Document header and parser settings (defaults to `AEXMLOptions()`).
- returns: Initialized XML Document object containing parsed data. Throws error if data could not be parsed.
*/
public convenience init(xml: Data, options: AEXMLOptions = AEXMLOptions()) throws {
self.init(options: options)
try loadXML(xml)
}
/**
Convenience initializer - used for parsing XML string (by calling `init(xmlData:options:)` internally).
- parameter xmlString: XML string to parse.
- parameter encoding: String encoding for creating `Data` from `xmlString` (defaults to `String.Encoding.utf8`)
- parameter options: Options for XML Document header and parser settings (defaults to `AEXMLOptions()`).
- returns: Initialized XML Document object containing parsed data. Throws error if data could not be parsed.
*/
public convenience init(xml: String,
encoding: String.Encoding = String.Encoding.utf8,
options: AEXMLOptions = AEXMLOptions()) throws
{
guard let data = xml.data(using: encoding) else { throw AEXMLError.parsingFailed }
try self.init(xml: data, options: options)
}
// MARK: - Parse XML
/**
Creates instance of `AEXMLParser` (private class which is simple wrapper around `XMLParser`)
and starts parsing the given XML data. Throws error if data could not be parsed.
- parameter data: XML which should be parsed.
*/
open func loadXML(_ data: Data) throws {
children.removeAll(keepingCapacity: false)
let xmlParser = AEXMLParser(document: self, data: data)
try xmlParser.parse()
}
// MARK: - Override
/// Override of `xml` property of `AEXMLElement` - it just inserts XML Document header at the beginning.
open override var xml: String {
var xml = "\(options.documentHeader.xmlString)\n"
for child in children {
xml += child.xml
}
return xml
}
}
+285
View File
@@ -0,0 +1,285 @@
//
// Element.swift
//
// Copyright (c) 2014-2016 Marko Tadić <tadija@me.com> http://tadija.net
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/**
This is base class for holding XML structure.
You can access its structure by using subscript like this: `element["foo"]["bar"]` which would
return `<bar></bar>` element from `<element><foo><bar></bar></foo></element>` XML as an `AEXMLElement` object.
*/
internal class AEXMLElement {
// MARK: - Properties
/// Every `AEXMLElement` should have its parent element instead of `AEXMLDocument` which parent is `nil`.
open internal(set) weak var parent: AEXMLElement?
/// Child XML elements.
open internal(set) var children = [AEXMLElement]()
/// XML Element name.
open var name: String
/// XML Element value.
open var value: String?
/// XML Element attributes.
open var attributes: [String : String]
/// Error value (`nil` if there is no error).
open var error: AEXMLError?
/// String representation of `value` property (if `value` is `nil` this is empty String).
open var string: String { return value ?? String() }
/// Boolean representation of `value` property (if `value` is "true" or 1 this is `True`, otherwise `False`).
open var bool: Bool { return string.lowercased() == "true" || Int(string) == 1 ? true : false }
/// Integer representation of `value` property (this is **0** if `value` can't be represented as Integer).
open var int: Int { return Int(string) ?? 0 }
/// Double representation of `value` property (this is **0.00** if `value` can't be represented as Double).
open var double: Double { return Double(string) ?? 0.00 }
// MARK: - Lifecycle
/**
Designated initializer - all parameters are optional.
- parameter name: XML element name.
- parameter value: XML element value (defaults to `nil`).
- parameter attributes: XML element attributes (defaults to empty dictionary).
- returns: An initialized `AEXMLElement` object.
*/
public init(name: String, value: String? = nil, attributes: [String : String] = [String : String]()) {
self.name = name
self.value = value
self.attributes = attributes
}
// MARK: - XML Read
/// The first element with given name **(Empty element with error if not exists)**.
open subscript(key: String) -> AEXMLElement {
guard let
first = children.first(where: { $0.name == key })
else {
let errorElement = AEXMLElement(name: key)
errorElement.error = AEXMLError.elementNotFound
return errorElement
}
return first
}
/// Returns all of the elements with equal name as `self` **(nil if not exists)**.
open var all: [AEXMLElement]? { return parent?.children.filter { $0.name == self.name } }
/// Returns the first element with equal name as `self` **(nil if not exists)**.
open var first: AEXMLElement? { return all?.first }
/// Returns the last element with equal name as `self` **(nil if not exists)**.
open var last: AEXMLElement? { return all?.last }
/// Returns number of all elements with equal name as `self`.
open var count: Int { return all?.count ?? 0 }
fileprivate func filter(withCondition condition: (AEXMLElement) -> Bool) -> [AEXMLElement]? {
guard let elements = all else { return nil }
var found = [AEXMLElement]()
for element in elements {
if condition(element) {
found.append(element)
}
}
return found.count > 0 ? found : nil
}
/**
Returns all elements with given value.
- parameter value: XML element value.
- returns: Optional Array of found XML elements.
*/
open func all(withValue value: String) -> [AEXMLElement]? {
let found = filter { (element) -> Bool in
return element.value == value
}
return found
}
/**
Returns all elements with given attributes.
- parameter attributes: Dictionary of Keys and Values of attributes.
- returns: Optional Array of found XML elements.
*/
open func all(withAttributes attributes: [String : String]) -> [AEXMLElement]? {
let found = filter { (element) -> Bool in
var countAttributes = 0
for (key, value) in attributes {
if element.attributes[key] == value {
countAttributes += 1
}
}
return countAttributes == attributes.count
}
return found
}
// MARK: - XML Write
/**
Adds child XML element to `self`.
- parameter child: Child XML element to add.
- returns: Child XML element with `self` as `parent`.
*/
@discardableResult open func addChild(_ child: AEXMLElement) -> AEXMLElement {
child.parent = self
children.append(child)
return child
}
/**
Adds child XML element to `self`.
- parameter name: Child XML element name.
- parameter value: Child XML element value (defaults to `nil`).
- parameter attributes: Child XML element attributes (defaults to empty dictionary).
- returns: Child XML element with `self` as `parent`.
*/
@discardableResult open func addChild(name: String,
value: String? = nil,
attributes: [String : String] = [String : String]()) -> AEXMLElement
{
let child = AEXMLElement(name: name, value: value, attributes: attributes)
return addChild(child)
}
/// Removes `self` from `parent` XML element.
open func removeFromParent() {
parent?.removeChild(self)
}
fileprivate func removeChild(_ child: AEXMLElement) {
if let childIndex = children.index(where: { $0 === child }) {
children.remove(at: childIndex)
}
}
fileprivate var parentsCount: Int {
var count = 0
var element = self
while let parent = element.parent {
count += 1
element = parent
}
return count
}
fileprivate func indent(withDepth depth: Int) -> String {
var count = depth
var indent = String()
while count > 0 {
indent += "\t"
count -= 1
}
return indent
}
/// Complete hierarchy of `self` and `children` in **XML** escaped and formatted String
open var xml: String {
var xml = String()
// open element
xml += indent(withDepth: parentsCount - 1)
xml += "<\(name)"
if attributes.count > 0 {
// insert attributes
for (key, value) in attributes {
xml += " \(key)=\"\(value.xmlEscaped)\""
}
}
if value == nil && children.count == 0 {
// close element
xml += " />"
} else {
if children.count > 0 {
// add children
xml += ">\n"
for child in children {
xml += "\(child.xml)\n"
}
// add indentation
xml += indent(withDepth: parentsCount - 1)
xml += "</\(name)>"
} else {
// insert string value and close element
xml += ">\(string.xmlEscaped)</\(name)>"
}
}
return xml
}
/// Same as `xmlString` but without `\n` and `\t` characters
open var xmlCompact: String {
let chars = CharacterSet(charactersIn: "\n\t")
return xml.components(separatedBy: chars).joined(separator: "")
}
}
public extension String {
/// String representation of self with XML special characters escaped.
public var xmlEscaped: String {
// we need to make sure "&" is escaped first. Not doing this may break escaping the other characters
var escaped = replacingOccurrences(of: "&", with: "&amp;", options: .literal)
// replace the other four special characters
let escapeChars = ["<" : "&lt;", ">" : "&gt;", "'" : "&apos;", "\"" : "&quot;"]
for (char, echar) in escapeChars {
escaped = escaped.replacingOccurrences(of: char, with: echar, options: .literal)
}
return escaped
}
}
+37
View File
@@ -0,0 +1,37 @@
//
// Error.swift
//
// Copyright (c) 2014-2016 Marko Tadić <tadija@me.com> http://tadija.net
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/// A type representing error value that can be thrown or inside `error` property of `AEXMLElement`.
internal enum AEXMLError: Error {
/// This will be inside `error` property of `AEXMLElement` when subscript is used for not-existing element.
case elementNotFound
/// This will be inside `error` property of `AEXMLDocument` when there is no root element.
case rootElementMissing
/// `AEXMLDocument` can throw this error on `init` or `loadXMLData` if parsing with `XMLParser` was not successful.
case parsingFailed
}
+19
View File
@@ -0,0 +1,19 @@
Copyright (c) 2014-2017 Marko Tadić <tadija@me.com> http://tadija.net
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+52
View File
@@ -0,0 +1,52 @@
//
// Options.swift
// AEXML
//
// Created by Marko Tadic on 9/10/16.
// Copyright © 2016 AE. All rights reserved.
//
import Foundation
/// Options used in `AEXMLDocument`
internal struct AEXMLOptions {
/// Values used in XML Document header
public struct DocumentHeader {
/// Version value for XML Document header (defaults to 1.0).
public var version = 1.0
/// Encoding value for XML Document header (defaults to "utf-8").
public var encoding = "utf-8"
/// Standalone value for XML Document header (defaults to "no").
public var standalone = "no"
/// XML Document header
public var xmlString: String {
return "<?xml version=\"\(version)\" encoding=\"\(encoding)\" standalone=\"\(standalone)\"?>"
}
}
/// Settings used by `Foundation.XMLParser`
public struct ParserSettings {
/// Parser reports the namespaces and qualified names of elements. (defaults to `false`)
public var shouldProcessNamespaces = false
/// Parser reports the prefixes indicating the scope of namespace declarations. (defaults to `false`)
public var shouldReportNamespacePrefixes = false
/// Parser reports declarations of external entities. (defaults to `false`)
public var shouldResolveExternalEntities = false
}
/// Values used in XML Document header (defaults to `DocumentHeader()`)
public var documentHeader = DocumentHeader()
/// Settings used by `Foundation.XMLParser` (defaults to `ParserSettings()`)
public var parserSettings = ParserSettings()
/// Designated initializer - Creates and returns default `AEXMLOptions`.
public init() {}
}
+101
View File
@@ -0,0 +1,101 @@
//
// Parser.swift
//
// Copyright (c) 2014-2016 Marko Tadić <tadija@me.com> http://tadija.net
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
/// Simple wrapper around `Foundation.XMLParser`.
internal class AEXMLParser: NSObject, XMLParserDelegate {
// MARK: - Properties
let document: AEXMLDocument
let data: Data
var currentParent: AEXMLElement?
var currentElement: AEXMLElement?
var currentValue = String()
var parseError: Error?
// MARK: - Lifecycle
init(document: AEXMLDocument, data: Data) {
self.document = document
self.data = data
currentParent = document
super.init()
}
// MARK: - API
func parse() throws {
let parser = XMLParser(data: data)
parser.delegate = self
parser.shouldProcessNamespaces = document.options.parserSettings.shouldProcessNamespaces
parser.shouldReportNamespacePrefixes = document.options.parserSettings.shouldReportNamespacePrefixes
parser.shouldResolveExternalEntities = document.options.parserSettings.shouldResolveExternalEntities
let success = parser.parse()
if !success {
guard let error = parseError else { throw AEXMLError.parsingFailed }
throw error
}
}
// MARK: - XMLParserDelegate
@objc func parser(_ parser: XMLParser,
didStartElement elementName: String,
namespaceURI: String?,
qualifiedName qName: String?,
attributes attributeDict: [String : String])
{
currentValue = String()
currentElement = currentParent?.addChild(name: elementName, attributes: attributeDict)
currentParent = currentElement
}
@objc func parser(_ parser: XMLParser, foundCharacters string: String) {
currentValue += string
let newValue = currentValue.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
currentElement?.value = newValue == String() ? nil : newValue
}
@objc func parser(_ parser: XMLParser,
didEndElement elementName: String,
namespaceURI: String?,
qualifiedName qName: String?)
{
currentParent = currentParent?.parent
currentElement = nil
}
@objc func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) {
self.parseError = parseError
}
}
+785
View File
@@ -0,0 +1,785 @@
//
// CloudFileProvider.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2017 Mousavian. Distributed under MIT license.
//
import Foundation
/**
Allows accessing to iCloud Drive stored files. Determine scope when initializing, to either access
to public documents folder or files stored as data.
To setup a functional iCloud container, please
[read this page](https://medium.com/ios-os-x-development/icloud-drive-documents-1a46b5706fe1).
*/
open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
/// An string to identify type of provider.
open override class var type: String { return "iCloudDrive" }
/// Forces file operations to use `NSFileCoordinating`,
/// Actually this is readonly, and value is always true.
override open var isCoorinating: Bool {
get {
return true
}
set {
assert(newValue, "CloudFileProvider.isCoorinating can't be set to false")
}
}
/// The fully-qualified container identifier for an iCloud container directory.
open fileprivate(set) var containerId: String?
/// Scope of container, indicates user can manipulate data/files or not.
open fileprivate(set) var scope: UbiquitousScope
/// Set this property to ignore initiations asserting to be on secondary thread
static open var asserting: Bool = true
/**
Initializes the provider for the iCloud container associated with the specified identifier and
establishes access to that container.
- Important: Do not call this method from your apps main thread. Because this method might take a nontrivial amount of time to set up iCloud and return the requested URL, you should always call it from a secondary thread.
- Parameter containerId: The fully-qualified container identifier for an iCloud container directory. The string you specify must not contain wildcards and must be of the form `<TEAMID>.<CONTAINER>`, where `<TEAMID>` is your development team ID and `<CONTAINER>` is the bundle identifier of the container you want to access.\
The container identifiers for your app must be declared in the `com.apple.developer.ubiquity-container-identifiers` array of the `.entitlements` property list file in your Xcode project.\
If you specify nil for this parameter, this method uses the first container listed in the `com.apple.developer.ubiquity-container-identifiers` entitlement array.
- Parameter scope: Use `.documents` (default) to put documents that the user is allowed to access inside a Documents subdirectory. Otherwise use `.data` to store user-related data files that your app needs to share but that are not files you want the user to manipulate directly.
*/
public convenience init? (containerId: String?, scope: UbiquitousScope = .documents) {
assert(!(CloudFileProvider.asserting && Thread.isMainThread), "CloudFileProvider.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
}
let baseURL: URL
if scope == .documents {
baseURL = ubiquityURL.appendingPathComponent("Documents/")
} else {
baseURL = ubiquityURL
}
self.init(baseURL: baseURL)
self.containerId = containerId
self.scope = scope
// To prepare FileManager objects?!
fileManager.url(forUbiquityContainerIdentifier: containerId)
opFileManager.url(forUbiquityContainerIdentifier: containerId)
try? fileManager.createDirectory(at: baseURL, withIntermediateDirectories: true)
}
public override init(baseURL: URL) {
self.scope = .data
super.init(baseURL: baseURL)
self.isCoorinating = true
#if swift(>=3.1)
let queueLabel = "FileProvider.\(Swift.type(of: self).type)"
#else
let queueLabel = "FileProvider.\(type(of: self).type)"
#endif
dispatch_queue = DispatchQueue(label: queueLabel, attributes: .concurrent)
operation_queue = OperationQueue()
operation_queue.name = "\(queueLabel).Operation"
}
public required convenience init?(coder aDecoder: NSCoder) {
if let containerId = aDecoder.decodeObject(forKey: "containerId") as? String,
let scopeString = aDecoder.decodeObject(forKey: "scope") as? String,
let scope = UbiquitousScope(rawValue: scopeString) {
self.init(containerId: containerId, scope: scope)
} else if let baseURL = aDecoder.decodeObject(forKey: "baseURL") as? URL {
self.init(baseURL: baseURL)
} else {
return nil
}
self.isCoorinating = aDecoder.decodeBool(forKey: "isCoorinating")
}
deinit {
let monitors = self.monitors
self.monitors = [:]
for monitor in monitors {
self.unregisterNotifcation(path: monitor.key)
}
}
open override func encode(with aCoder: NSCoder) {
super.encode(with: aCoder)
aCoder.encode(self.containerId, forKey: "containerId")
aCoder.encode(self.scope.rawValue, forKey: "scope")
}
open override func copy(with zone: NSZone? = nil) -> Any {
let copy = CloudFileProvider(containerId: self.containerId, scope: self.scope)
copy?.delegate = self.delegate
copy?.fileOperationDelegate = self.fileOperationDelegate
return copy as Any
}
/**
Returns an Array of `FileObject`s identifying the the directory entries via asynchronous completion handler.
If the directory contains no entries or an error is occured, this method will return the empty array.
- Parameters:
- path: path to target directory. If empty, root will be iterated.
- completionHandler: a closure with result of directory entries or error.
- contents: An array of `FileObject` identifying the the directory entries.
- error: Error returned by system.
*/
open override func contentsOfDirectory(path: String, completionHandler: @escaping (_ contents: [FileObject], _ error: Error?) -> Void) {
// FIXME: create runloop for dispatch_queue, start query on it
let query = NSPredicate(format: "TRUEPREDICATE")
_ = searchFiles(path: path, recursive: false, query: query, foundItemHandler: nil, completionHandler: completionHandler)
}
/// Please don't rely this function to get iCloud drive total and remaining capacity
/// - Important: iCloud Storage size and free space is unavailable, it returns local space
open override func storageProperties(completionHandler: @escaping (VolumeObject?) -> Void) {
super.storageProperties(completionHandler: completionHandler)
}
/**
Returns a `FileObject` containing the attributes of the item (file, directory, symlink, etc.) at the path in question via asynchronous completion handler.
If the directory contains no entries or an error is occured, this method will return the empty `FileObject`.
- Parameters:
- path: path to target directory. If empty, attributes of root will be returned.
- completionHandler: a closure with result of directory entries or error.
- attributes: A `FileObject` containing the attributes of the item.
- error: Error returned by system.
*/
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[CD] %@", NSMetadataItemPathKey, pathURL.path)
query.valueListAttributes = [NSMetadataItemURLKey, NSMetadataItemFSNameKey, NSMetadataItemPathKey, NSMetadataItemFSSizeKey, NSMetadataItemContentTypeTreeKey, NSMetadataItemFSCreationDateKey, NSMetadataItemFSContentChangeDateKey]
query.searchScopes = [self.scope.rawValue]
var finishObserver: NSObjectProtocol?
finishObserver = NotificationCenter.default.addObserver(forName: .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.cocoaError(path, code: .fileNoSuchFile)
self.dispatch_queue.async {
completionHandler(nil, error)
}
return
}
if let file = self.mapFileObject(attributes: attribs) {
self.dispatch_queue.async {
completionHandler(file, nil)
}
} else {
let noFileError = self.cocoaError(path, code: .fileNoSuchFile)
self.dispatch_queue.async {
completionHandler(nil, noFileError)
}
}
})
DispatchQueue.main.async {
if !query.start() {
self.dispatch_queue.async {
completionHandler(nil, self.cocoaError(path, code: .fileReadNoPermission))
}
}
}
}
}
/**
Search files inside directory using query asynchronously.
Sample predicates:
```
NSPredicate(format: "(name CONTAINS[c] 'hello') && (filesize >= 10000)")
NSPredicate(format: "(modifiedDate >= %@)", Date())
NSPredicate(format: "(path BEGINSWITH %@)", "folder/child folder")
```
- Note: Don't pass Spotlight predicates to this method directly, use `FileProvider.convertSpotlightPredicateTo()` method to get usable predicate.
- Important: A file name criteria should be provided for Dropbox.
- Parameters:
- path: location of directory to start search
- recursive: Searching subdirectories of path
- query: An `NSPredicate` object with keys like `FileObject` members, except `size` which becomes `filesize`.
- foundItemHandler: Closure which is called when a file is found
- completionHandler: Closure which will be called after finishing search. Returns an arry of `FileObject` or error if occured.
- files: all files meat the `query` criteria.
- error: `Error` returned by server if occured.
- Returns: An `Progress` to get progress or cancel progress. Use `completedUnitCount` to iterate count of found items.
*/
open override func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping (_ files: [FileObject], _ error: Error?) -> Void) -> Progress? {
let progress = Progress(totalUnitCount: -1)
let pathURL = self.url(of: path)
progress.setUserInfoObject(pathURL, forKey: .fileURLKey)
let mdquery = NSMetadataQuery()
mdquery.predicate = NSPredicate(format: "(%K BEGINSWITH[CD] %@) && (\(updateQueryTypeKeys(query).predicateFormat))", NSMetadataItemPathKey, pathURL.path)
mdquery.valueListAttributes = [NSMetadataItemURLKey, NSMetadataItemFSNameKey, NSMetadataItemPathKey, NSMetadataItemFSSizeKey, NSMetadataItemContentTypeTreeKey, NSMetadataItemFSCreationDateKey, NSMetadataItemFSContentChangeDateKey]
mdquery.searchScopes = [self.scope.rawValue]
var lastReportedCount = 0
progress.cancellationHandler = { [weak mdquery] in
mdquery?.stop()
}
var updateObserver: NSObjectProtocol?
if let foundItemHandler = foundItemHandler {
// FIXME: Remove this section as it won't work as expected on iCloud
updateObserver = NotificationCenter.default.addObserver(forName: .NSMetadataQueryGatheringProgress, object: mdquery, queue: nil, using: { (notification) in
mdquery.disableUpdates()
for index in lastReportedCount..<mdquery.resultCount {
guard let attribs = (mdquery.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), query.evaluate(with: file.mapPredicate()) {
foundItemHandler(file)
}
}
lastReportedCount = mdquery.resultCount
progress.totalUnitCount = Int64(lastReportedCount)
mdquery.enableUpdates()
})
}
var finishObserver: NSObjectProtocol?
finishObserver = NotificationCenter.default.addObserver(forName: .NSMetadataQueryDidFinishGathering, object: mdquery, queue: nil, using: { (notification) in
defer {
mdquery.stop()
finishObserver.flatMap(NotificationCenter.default.removeObserver)
finishObserver = nil
updateObserver.flatMap(NotificationCenter.default.removeObserver)
updateObserver = nil
}
guard let results = mdquery.results as? [NSMetadataItem] else {
return
}
mdquery.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), query.evaluate(with: file.mapPredicate()) {
contents.append(file)
}
}
progress.completedUnitCount = Int64(contents.count)
self.dispatch_queue.async {
completionHandler(contents, nil)
}
})
DispatchQueue.main.async {
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
if !mdquery.start() {
self.dispatch_queue.async {
completionHandler([], self.cocoaError(path, code: .fileReadNoPermission))
}
}
}
return progress
}
open override func isReachable(completionHandler: @escaping (Bool) -> Void) {
dispatch_queue.async {
completionHandler(self.fileManager.ubiquityIdentityToken != nil)
}
}
/**
Removes the file or directory at the specified path.
- Important: Due to a bug (race condition?) in Apple API, it takes about 3-5 seconds to update containing folder
list and triggering notification registered for directory while completion handler will run almost immediately.
It's your responsibility to workaourd this bug/feature and mark file as deleted in your software.
- Parameters:
- path: file or directory path.
- completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
- Returns: A `Progress` object to get progress or cancel progress. Doesn't work on `CloudFileProvider`.
*/
@discardableResult
open override func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> Progress? {
return super.removeItem(path: path, completionHandler: completionHandler)
}
/**
Uploads a file from local file url to designated path asynchronously.
Method will fail if source is not a local url with `file://` scheme.
- Parameters:
- localFile: a file url to file.
- to: destination path of file, including file/directory name.
- overwrite: Destination file should be overwritten if file is already exists. **Default** is `false`.
- completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
- Returns: A `Progress` object to get progress or cancel progress.
*/
@discardableResult
open override func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
// TODO: Make use of overwrite parameter
let operation = FileOperationType.copy(source: localFile.absoluteString, destination: toPath)
let progress = Progress(totalUnitCount: -1)
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
progress.kind = .file
progress.isCancellable = false
progress.setUserInfoObject(localFile, forKey: .fileURLKey)
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
monitorFile(path: toPath, operation: operation, progress: progress)
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 {
progress.totalUnitCount = localFile.fileSize
try self.opFileManager.copyItem(at: localFile, to: tmpFile)
let toUrl = self.url(of: toPath)
try self.opFileManager.setUbiquitous(true, itemAt: tmpFile, destinationURL: toUrl)
self.monitorFile(path: toPath, operation: operation, progress: progress)
completionHandler?(nil)
self.delegateNotify(operation)
} catch {
if self.opFileManager.fileExists(atPath: tmpFile.path) {
try? self.opFileManager.removeItem(at: tmpFile)
}
completionHandler?(error)
self.delegateNotify(operation, error: error)
}
}
return progress
}
/**
Download a file from `path` to designated local file url asynchronously.
Method will fail if destination is not a local url with `file://` scheme.
- Parameters:
- path: original file or directory path.
- toLocalURL: destination local url of file, including file/directory name.
- completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
- Returns: A `Progress` object to get progress or cancel progress.
*/
@discardableResult
open override func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) -> Progress? {
let operation = FileOperationType.copy(source: path, destination: toLocalURL.absoluteString)
let progress = super.copyItem(path: path, toLocalURL: toLocalURL, completionHandler: completionHandler)
monitorFile(path: path, operation: operation, progress: progress)
do {
try self.opFileManager.startDownloadingUbiquitousItem(at: self.url(of: path))
} catch {
completionHandler?(error)
self.delegateNotify(operation, error: error)
return nil
}
return progress
}
/**
Retreives a `Data` object with the contents of the file asynchronously vis contents argument of completion handler.
If path specifies a directory, or if some other error occurs, data will be nil.
- Parameters:
- path: Path of file.
- completionHandler: a closure with result of file contents or error.
`contents`: contents of file in a `Data` object.
`error`: Error returned by system.
- Returns: A `Progress` object to get progress or cancel progress.
*/
@discardableResult
open override func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> Progress? {
let operation = FileOperationType.fetch(path: path)
let progress = super.contents(path: path, completionHandler: completionHandler)
monitorFile(path: path, operation: operation, progress: progress)
return progress
}
/**
Retreives a `Data` object with a portion contents of the file asynchronously vis contents argument of completion handler.
If path specifies a directory, or if some other error occurs, data will be nil.
- Parameters:
- path: Path of file.
- offset: First byte index which should be read. **Starts from 0.**
- length: Bytes count of data. Pass `-1` to read until the end of file.
- completionHandler: a closure with result of file contents or error.
`contents`: contents of file in a `Data` object.
`error`: Error returned by system.
- Returns: A `Progress` object to get progress or cancel progress.
*/
@discardableResult
open override func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> Progress? {
let operation = FileOperationType.fetch(path: path)
let progress = super.contents(path: path, offset: offset, length: length, completionHandler: completionHandler)
monitorFile(path: path, operation: operation, progress: progress)
return progress
}
/**
Write the contents of the `Data` to a location asynchronously.
- Parameters:
- path: Path of target file.
- contents: Data to be written into file.
- overwrite: Destination file should be overwritten if file is already exists. Default is `false`.
- atomically: data will be written to a temporary file before writing to final location. Default is `false`.
- completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
- Returns: A `Progress` object to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
*/
@discardableResult
open override func writeContents(path: String, contents data: Data?, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
let operation = FileOperationType.fetch(path: path)
let progress = Progress(totalUnitCount: -1)
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
progress.kind = .file
progress.setUserInfoObject(self.url(of: path), forKey: .fileURLKey)
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
monitorFile(path: path, operation: operation, progress: progress)
_ = super.writeContents(path: path, contents: data, atomically: atomically, overwrite: overwrite, completionHandler: completionHandler)
return progress
}
fileprivate var monitors = [String: (NSMetadataQuery, NSObjectProtocol)]()
/**
Starts monitoring a path and its subpaths, including files and folders, for any change,
including copy, move/rename, content changes, etc.
To avoid thread congestion, `evetHandler` will be triggered with 0.2 seconds interval,
and has a 0.25 second delay, to ensure it's called after updates.
- Note: this functionality is available only in `LocalFileProvider` and `CloudFileProvider`.
- Note: `eventHandler` is not called on main thread, for updating UI. dispatch routine to main thread.
- Important: `eventHandler` may be called if file is changed in recursive subpaths of registered path.
This may cause negative impact on performance if a root path is being monitored.
- Parameters:
- path: path of directory.
- eventHandler: Closure executed after change, on a secondary thread.
*/
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.valueListAttributes = []
query.searchScopes = [self.scope.rawValue]
let updateObserver = NotificationCenter.default.addObserver(forName: .NSMetadataQueryDidUpdate, object: query, queue: nil, using: { (notification) in
query.disableUpdates()
eventHandler()
query.enableUpdates()
})
DispatchQueue.main.async {
if query.start() {
self.monitors[path] = (query, updateObserver)
}
}
}
/// Stops monitoring the path.
///
/// - Parameter path: path of directory.
open override func unregisterNotifcation(path: String) {
guard let (query, observer) = monitors[path] else {
return
}
query.disableUpdates()
query.stop()
NotificationCenter.default.removeObserver(observer)
monitors.removeValue(forKey: path)
}
/// Investigate either the path is registered for change notification or not.
///
/// - Parameter path: path of directory.
/// - Returns: Directory is being monitored or not.
open override func isRegisteredForNotification(path: String) -> Bool {
return monitors[path] != nil
}
fileprivate func updateQueryTypeKeys(_ queryComponent: NSPredicate) -> NSPredicate {
let mapDict: [String: String] = ["url": NSMetadataItemURLKey, "name": NSMetadataItemFSNameKey, "path": NSMetadataItemPathKey, "filesize": NSMetadataItemFSSizeKey, "modifiedDate": NSMetadataItemFSContentChangeDateKey, "creationDate": NSMetadataItemFSCreationDateKey, "contentType": NSMetadataItemContentTypeKey]
if let cQuery = queryComponent as? NSCompoundPredicate {
let newSub = cQuery.subpredicates.map { updateQueryTypeKeys($0 as! NSPredicate) }
switch cQuery.compoundPredicateType {
case .and: return NSCompoundPredicate(andPredicateWithSubpredicates: newSub)
case .not: return NSCompoundPredicate(notPredicateWithSubpredicate: newSub.first!)
case .or: return NSCompoundPredicate(orPredicateWithSubpredicates: newSub)
}
} else if let cQuery = queryComponent as? NSComparisonPredicate {
var newLeft = cQuery.leftExpression
var newRight = cQuery.rightExpression
if newLeft.expressionType == .keyPath, let newKey = mapDict[newLeft.keyPath] {
newLeft = NSExpression(forKeyPath: newKey)
}
if newRight.expressionType == .keyPath, let newKey = mapDict[newRight.keyPath] {
newRight = NSExpression(forKeyPath: newKey)
}
if newLeft.expressionType == .keyPath, newLeft.keyPath == "type" {
newRight = NSExpression(forConstantValue: newRight.constantValue as? String == "directory" ? "public.directory": "public.data")
}
if newRight.expressionType == .keyPath, newRight.keyPath == "type" {
newLeft = NSExpression(forConstantValue: newLeft.constantValue as? String == "directory" ? "public.directory": "public.data")
}
return NSComparisonPredicate(leftExpression: newLeft, rightExpression: newRight, modifier: cQuery.comparisonPredicateModifier, type: cQuery.predicateOperatorType, options: cQuery.options)
} else {
return queryComponent
}
}
fileprivate func mapFileObject(attributes attribs: [String: Any]) -> FileObject? {
guard let url = (attribs[NSMetadataItemURLKey] as? URL)?.standardizedFileURL, let name = attribs[NSMetadataItemFSNameKey] as? String else {
return nil
}
let path = self.relativePathOf(url: url)
#if swift(>=4.0)
let rpath = path.hasPrefix("/") ? String(path[path.index(after: path.startIndex)...]) : path
#else
let rpath = path.hasPrefix("/") ? path.substring(from: path.index(after: path.startIndex)) : path
#endif
let relativeUrl = URL(string: rpath.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? 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
}
lazy fileprivate var observer: KVOObserver = KVOObserver()
fileprivate func monitorFile(path: String, operation: FileOperationType, progress: Progress?) {
let pathURL = self.url(of: path).standardizedFileURL
let query = NSMetadataQuery()
query.predicate = NSPredicate(format: "%K LIKE[CD] %@", NSMetadataItemPathKey, pathURL.path)
query.valueListAttributes = [NSMetadataItemURLKey, NSMetadataItemFSNameKey, NSMetadataItemPathKey, NSMetadataUbiquitousItemPercentDownloadedKey, NSMetadataUbiquitousItemPercentUploadedKey, NSMetadataUbiquitousItemDownloadingStatusKey, NSMetadataItemFSSizeKey]
query.searchScopes = [self.scope.rawValue]
var context = QueryProgressWrapper(provider: self, progress: progress, operation: operation)
query.addObserver(self.observer, forKeyPath: "results", options: [.initial, .new, .old], context: &context)
DispatchQueue.main.async {
query.start()
progress?.setUserInfoObject(Date(), forKey: .startingTimeKey)
}
}
open func publicLink(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)
self.dispatch_queue.async {
completionHandler(url, nil, expiration as Date?, nil)
}
} catch {
self.dispatch_queue.async {
completionHandler(nil, nil, nil, error)
}
}
}
}
/// Removes local copy of file, but spares cloud copy.
/// - Parameter path: Path of file or directory to be removed from local
/// - Parameter completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
open func evictItem(path: String, completionHandler: SimpleCompletionHandler) {
operation_queue.addOperation {
do {
try self.opFileManager.evictUbiquitousItem(at: self.url(of: path))
completionHandler?(nil)
} catch {
completionHandler?(error)
}
}
}
/// Returns current version of file on this device and all versions of files in user devices.
/// - Parameter path: Path of file or directory.
/// - Parameter completionHandler: Retrieve current version on this device and all versions available. `currentVersion` will be nil if file doesn't exist. If an error parameter was provided, a presentable `Error` will be returned.
func versionsOfItem(path: String, completionHandler: @escaping ((_ currentVersion: NSFileVersion?, _ versions: [NSFileVersion], _ error: Error?) -> Void)) {
NotImplemented()
}
/// Resolves conflicts by selecting a version.
/// - Parameter path: Path of file or directory.
/// - Parameter version: Version than will be choose as main version. `nil` value indicates current version on this device will be selected.
/// - Parameter completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
func selectVersionOfItem(path: String, version: NSFileVersion? = nil, completionHandler: SimpleCompletionHandler) {
NotImplemented()
}
}
/// Scope of iCloud, wrapper for NSMetadataQueryUbiquitous...Scope constants
public enum UbiquitousScope: RawRepresentable {
/// Search all files not in the Documents directories of the apps iCloud container directories.
/// Use this scope to store user-related data files that your app needs to share
/// but that are not files you want the user to manipulate directly.
///
/// Raw value is equivalent to `NSMetadataQueryUbiquitousDataScope`
case data
/// Search all files in the Documents directories of the apps iCloud container directories.
/// Put documents that the user is allowed to access inside a Documents subdirectory.
///
/// Raw value is equivalent to `NSMetadataQueryUbiquitousDocumentsScope`
case documents
public typealias RawValue = String
public init? (rawValue: String) {
switch rawValue {
case NSMetadataQueryUbiquitousDataScope:
self = .data
case NSMetadataQueryUbiquitousDocumentsScope:
self = .documents
default:
return nil
}
}
public var rawValue: String {
switch self {
case .data:
return NSMetadataQueryUbiquitousDataScope
case .documents:
return NSMetadataQueryUbiquitousDocumentsScope
}
}
}
struct QueryProgressWrapper {
weak var provider: CloudFileProvider?
weak var progress: Progress?
let operation: FileOperationType
}
fileprivate class KVOObserver: NSObject {
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard let query = object as? NSMetadataQuery else {
return
}
guard let wrapper = context?.load(as: QueryProgressWrapper.self) else {
query.stop()
query.removeObserver(self, forKeyPath: "results")
return
}
let provider = wrapper.provider
let progress = wrapper.progress
let operation = wrapper.operation
guard let results = change?[.newKey], let item = (results as? [NSMetadataItem])?.first else {
return
}
query.disableUpdates()
var size = progress?.totalUnitCount ?? -1
if size < 0, let size_d = item.value(forAttribute: NSMetadataItemFSSizeKey) as? Int64 {
size = size_d
progress?.totalUnitCount = size
}
let downloadStatus = item.value(forAttribute: NSMetadataUbiquitousItemPercentDownloadedKey) as? String ?? ""
let downloaded = item.value(forAttribute: NSMetadataUbiquitousItemPercentDownloadedKey) as? Double ?? 0
let uploaded = item.value(forAttribute: NSMetadataUbiquitousItemPercentUploadedKey) as? Double ?? 0
if (downloaded == 0 || downloaded == 100) && (uploaded > 0 && uploaded < 100) {
progress?.completedUnitCount = Int64(uploaded / 100 * Double(size))
provider?.delegateNotify(operation, progress: uploaded / 100)
} else if (uploaded == 0 || uploaded == 100) && downloadStatus != NSMetadataUbiquitousItemDownloadingStatusCurrent {
progress?.completedUnitCount = Int64(downloaded / 100 * Double(size))
provider?.delegateNotify(operation, progress: downloaded / 100)
} else if uploaded == 100 || downloadStatus == NSMetadataUbiquitousItemDownloadingStatusCurrent {
progress?.completedUnitCount = size
query.stop()
query.removeObserver(self, forKeyPath: "results")
provider?.delegateNotify(operation)
}
query.enableUpdates()
}
}
/*
func getMetadataItem(url: URL) -> NSMetadataItem? {
let query = NSMetadataQuery()
query.predicate = NSPredicate(format: "(%K LIKE %@)", NSMetadataItemPathKey, url.path)
query.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope, NSMetadataQueryUbiquitousDataScope]
var item: NSMetadataItem?
let group = DispatchGroup()
group.enter()
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()
})
DispatchQueue.main.async {
query.start()
}
_ = group.wait(timeout: .now() + 30)
return item
}
*/
+445 -323
View File
@@ -1,3 +1,4 @@
//
// DropboxFileProvider.swift
// FileProvider
@@ -7,291 +8,276 @@
//
import Foundation
import CoreGraphics
public enum FileProviderDropboxErrorCode: Int {
case BadInputParameter = 400
case ExpiredToken = 401
case Forbidden = 403
case Endpoint = 409
case TooManyRequests = 429
case InternalServer = 500
case BadGateway = 502
}
public struct FileProviderDropboxError: ErrorType, CustomStringConvertible {
public let code: FileProviderDropboxErrorCode
public let path: String
/**
Allows accessing to Dropbox stored files. This provider doesn't cache or save files internally, however you can
set `useCache` and `cache` properties to use Foundation `NSURLCache` system.
- Note: You can pass file id or rev instead of file path, e.g `"id:1234abcd"` or `"rev:1234abcd"`, to point to a file or folder by ID.
- Note: Uploading files and data are limited to 150MB, for now.
*/
open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
override open class var type: String { return "Dropbox" }
public var description: String {
switch code {
case .BadInputParameter: return "Bad input parameter."
case .ExpiredToken: return "Bad or expired token. To fix this, you should re-authenticate the user."
case .Forbidden: return "Forbidden."
case .Endpoint: return "Endpoint-specific error."
case .TooManyRequests: return "Your app is making too many requests"
case .InternalServer: return "An error occurred on the Dropbox servers."
case .BadGateway: return "An error occurred on the Dropbox servers."
}
}
}
public final class DropboxFileObject: FileObject {
public let serverTime: NSDate?
public let id: String?
public let rev: String?
/// Dropbox RPC API URL, which is equal with [https://api.dropboxapi.com/2/](https://api.dropboxapi.com/2/)
open let apiURL: URL
/// Dropbox contents download/upload API URL, which is equal with [https://content.dropboxapi.com/2/](https://content.dropboxapi.com/2/)
open let contentURL: URL
public init(absoluteURL: NSURL, name: String, path: String, size: Int64, serverTime: NSDate?, createdDate: NSDate?, modifiedDate: NSDate?, fileType: FileType, isHidden: Bool, isReadOnly: Bool, id: String?, rev: String?) {
self.serverTime = serverTime
self.id = id
self.rev = rev
super.init(absoluteURL: absoluteURL, name: name, path: path, size: size, createdDate: createdDate, modifiedDate: modifiedDate, fileType: fileType, isHidden: isHidden, isReadOnly: isReadOnly)
}
}
// Because this class uses NSURLSession, it's necessary to disable App Transport Security
// in case of using this class with unencrypted HTTP connection.
public class DropboxFileProvider: NSObject, FileProviderBasic {
public let type: String = "WebDAV"
public let isPathRelative: Bool = true
public let baseURL: NSURL?
public var currentPath: String = ""
public var dispatch_queue: dispatch_queue_t {
willSet {
assert(_session == nil, "It's not effective to change dispatch_queue property after session is initialized.")
}
}
public weak var delegate: FileProviderDelegate?
public let credential: NSURLCredential?
private var _session: NSURLSession?
private var session: NSURLSession {
if _session == nil {
let queue = NSOperationQueue()
//queue.underlyingQueue = dispatch_queue
_session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), delegate: self, delegateQueue: queue)
}
return _session!
/**
Initializer for Dropbox provider with given client ID and Token.
These parameters 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.
- Parameter credential: a `URLCredential` object with Client ID set as `user` and Token set as `password`.
- Parameter cache: A URLCache to cache downloaded files and contents.
*/
public init(credential: URLCredential?, cache: URLCache? = nil) {
self.apiURL = URL(string: "https://api.dropboxapi.com/2/")!
self.contentURL = URL(string: "https://content.dropboxapi.com/2/")!
super.init(baseURL: nil, credential: credential, cache: cache)
}
public init? (baseURL: NSURL, credential: NSURLCredential?) {
if !["http", "https"].contains(baseURL.uw_scheme.lowercaseString) {
return nil
}
self.baseURL = baseURL
dispatch_queue = dispatch_queue_create("FileProvider.\(type)", DISPATCH_QUEUE_CONCURRENT)
//let url = baseURL.uw_absoluteString
self.credential = credential
public required convenience init?(coder aDecoder: NSCoder) {
self.init(credential: aDecoder.decodeObject(forKey: "credential") as? URLCredential)
self.useCache = aDecoder.decodeBool(forKey: "useCache")
self.validatingCache = aDecoder.decodeBool(forKey: "validatingCache")
}
deinit {
_session?.invalidateAndCancel()
override open func copy(with zone: NSZone? = nil) -> Any {
let copy = DropboxFileProvider(credential: self.credential, cache: self.cache)
copy.delegate = self.delegate
copy.fileOperationDelegate = self.fileOperationDelegate
copy.useCache = self.useCache
copy.validatingCache = self.validatingCache
return copy
}
public func contentsOfDirectoryAtPath(path: String, completionHandler: ((contents: [FileObject], error: ErrorType?) -> Void)) {
NotImplemented()
/**
Returns an Array of `FileObject`s identifying the the directory entries via asynchronous completion handler.
If the directory contains no entries or an error is occured, this method will return the empty array.
- Parameters:
- path: path to target directory. If empty, root will be iterated.
- completionHandler: a closure with result of directory entries or error.
- contents: An array of `FileObject` identifying the the directory entries.
- error: Error returned by system.
*/
open override func contentsOfDirectory(path: String, completionHandler: @escaping (_ contents: [FileObject], _ error: Error?) -> Void) {
let query = NSPredicate(format: "TRUEPREDICATE")
_ = searchFiles(path: path, recursive: false, query: query, foundItemHandler: nil, completionHandler: completionHandler)
}
public func attributesOfItemAtPath(path: String, completionHandler: ((attributes: FileObject?, error: ErrorType?) -> Void)) {
let url = NSURL(string: "https://api.dropboxapi.com/2/files/list_revisions")!
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let requestDictionary = ["path": correctPath(path)!]
request.HTTPBody = dictionaryToJSON(requestDictionary)?.dataUsingEncoding(NSUTF8StringEncoding)
let task = session.dataTaskWithRequest(request) { (data, response, error) in
if let response = response as? NSHTTPURLResponse {
defer {
self.delegateNotify(FileOperation.Create(path: path), error: error)
}
let code = FileProviderDropboxErrorCode(rawValue: response.statusCode)
let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path) : nil
if let data = data, let jsonStr = String(data: data, encoding: NSUTF8StringEncoding) {
let json = self.jsonToDictionary(jsonStr)
if (json?["is_deleted"] as? NSNumber)?.boolValue ?? false, let entries = json?["entries"] as? [AnyObject] where entries.count > 0 , let entry = entries[0] as? [String: AnyObject], let file = self.mapToFileObject(entry) {
completionHandler(attributes: file, error: dbError)
return
}
}
completionHandler(attributes: nil, error: dbError)
/**
Returns a `FileObject` containing the attributes of the item (file, directory, symlink, etc.) at the path in question via asynchronous completion handler.
If the directory contains no entries or an error is occured, this method will return the empty `FileObject`.
- Parameters:
- path: path to target directory. If empty, attributes of root will be returned.
- completionHandler: a closure with result of directory entries or error.
- attributes: A `FileObject` containing the attributes of the item.
- error: Error returned by system.
*/
open override func attributesOfItem(path: String, completionHandler: @escaping (_ attributes: FileObject?, _ error: Error?) -> Void) {
let url = URL(string: "files/get_metadata", relativeTo: apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue(authentication: credential, with: .oAuth2)
request.setValue(contentType: .json)
let requestDictionary: [String: AnyObject] = ["path": correctPath(path)! as NSString]
request.httpBody = Data(jsonDictionary: requestDictionary)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var serverError: FileProviderHTTPError?
var fileObject: DropboxFileObject?
if let response = response as? HTTPURLResponse, response.statusCode >= 400 {
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
serverError = code.flatMap { self.serverError(with: $0, path: path, data: data) }
}
if let json = data?.deserializeJSON(), let file = DropboxFileObject(json: json) {
fileObject = file
}
completionHandler(fileObject, serverError ?? error)
})
task.resume()
}
/// Returns volume/provider information asynchronously.
/// - Parameter volumeInfo: Information of filesystem/Provider returned by system/server.
open override func storageProperties(completionHandler: @escaping (_ volumeInfo: VolumeObject?) -> Void) {
let url = URL(string: "users/get_space_usage", relativeTo: apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue(authentication: credential, with: .oAuth2)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
guard let json = data?.deserializeJSON() else {
completionHandler(nil)
return
}
completionHandler(attributes: nil, error: error)
}
let volume = VolumeObject(allValues: [:])
volume.totalCapacity = ((json["allocation"] as? NSDictionary)?["allocated"] as? NSNumber)?.int64Value ?? -1
volume.usage = (json["used"] as? NSNumber)?.int64Value ?? 0
completionHandler(volume)
})
task.resume()
}
public weak var fileOperationDelegate: FileOperationDelegate?
}
extension DropboxFileProvider: FileProviderOperations {
public func createFolder(folderName: String, atPath: String, completionHandler: SimpleCompletionHandler) {
let path = (atPath as NSString).stringByAppendingPathComponent(folderName) + "/"
doOperation(.Create(path: path), completionHandler: completionHandler)
}
public func createFile(fileAttribs: FileObject, atPath path: String, contents data: NSData?, completionHandler: SimpleCompletionHandler) {
NotImplemented()
}
public func moveItemAtPath(path: String, toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
doOperation(.Move(source: path, destination: toPath), completionHandler: completionHandler)
}
public func copyItemAtPath(path: String, toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
doOperation(.Copy(source: path, destination: toPath), completionHandler: completionHandler)
}
public func removeItemAtPath(path: String, completionHandler: SimpleCompletionHandler) {
doOperation(.Remove(path: path), completionHandler: completionHandler)
}
private func doOperation(operation: FileOperation, completionHandler: SimpleCompletionHandler) {
let url: String
var path: String?, fromPath: String?, toPath: String?
switch operation {
case .Create(path: let p):
url = "https://api.dropboxapi.com/2/files/create_folder"
path = p
case .Copy(source: let fp, destination: let tp):
url = "https://api.dropboxapi.com/2/files/copy"
fromPath = fp
toPath = tp
case .Move(source: let fp, destination: let tp):
url = "https://api.dropboxapi.com/2/files/move"
fromPath = fp
toPath = tp
case .Modify(path: let p):
return
case .Remove(path: let p):
url = "https://api.dropboxapi.com/2/files/delete"
path = p
case .Link(link: _, target: _):
return
/**
Search files inside directory using query asynchronously.
Sample predicates:
```
NSPredicate(format: "(name CONTAINS[c] 'hello') && (filesize >= 10000)")
NSPredicate(format: "(modifiedDate >= %@)", Date())
NSPredicate(format: "(path BEGINSWITH %@)", "folder/child folder")
```
- Note: Don't pass Spotlight predicates to this method directly, use `FileProvider.convertSpotlightPredicateTo()` method to get usable predicate.
- Important: A file name criteria should be provided for Dropbox.
- Parameters:
- path: location of directory to start search
- recursive: Searching subdirectories of path
- query: An `NSPredicate` object with keys like `FileObject` members, except `size` which becomes `filesize`.
- foundItemHandler: Closure which is called when a file is found
- completionHandler: Closure which will be called after finishing search. Returns an arry of `FileObject` or error if occured.
- files: all files meat the `query` criteria.
- error: `Error` returned by server if occured.
- Returns: An `Progress` to get progress or cancel progress. Use `completedUnitCount` to iterate count of found items.
*/
open override func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping (_ files: [FileObject], _ error: Error?) -> Void) -> Progress? {
let queryStr: String?
if query.predicateFormat == "TRUEPREDICATE" {
queryStr = nil
} else {
queryStr = query.findValue(forKey: "name", operator: .beginsWith) as? String
}
let request = NSMutableURLRequest(URL: NSURL(string: url)!)
request.HTTPMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
var requestDictionary = [String: AnyObject]()
requestDictionary["path"] = correctPath(path)
requestDictionary["from_path"] = correctPath(fromPath)
requestDictionary["to_path"] = correctPath(toPath)
request.HTTPBody = dictionaryToJSON(requestDictionary)?.dataUsingEncoding(NSUTF8StringEncoding)
let task = session.dataTaskWithRequest(request) { (data, response, error) in
if let response = response as? NSHTTPURLResponse {
let code = FileProviderDropboxErrorCode(rawValue: response.statusCode)
let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path ?? fromPath ?? "") : nil
defer {
self.delegateNotify(operation, error: error ?? dbError)
}
/*if let data = data, let jsonStr = String(data: data, encoding: NSUTF8StringEncoding) {
let json = self.jsonToDictionary(jsonStr)
}*/
completionHandler?(error: dbError)
return
let requestHandler = self.listRequest(path: path, queryStr: queryStr, recursive: recursive)
let queryIsTruePredicate = query.predicateFormat == "TRUEPREDICATE"
return paginated(path, requestHandler: requestHandler,
pageHandler: { [weak self] (data, progress) -> (files: [FileObject], error: Error?, newToken: String?) in
guard let json = data?.deserializeJSON(), let entries = (json["entries"] ?? json["matches"]) as? [AnyObject] else {
let err = self?.urlError(path, code: .badServerResponse)
return ([], err, nil)
}
completionHandler?(error: error)
}
task.resume()
}
public func copyLocalFileToPath(localFile: NSURL, toPath: String, completionHandler: SimpleCompletionHandler) {
NotImplemented()
let request = NSMutableURLRequest(URL: absoluteURL(toPath))
request.HTTPMethod = "PUT"
let task = session.uploadTaskWithRequest(request, fromFile: localFile) { (data, response, error) in
completionHandler?(error: error)
self.delegateNotify(.Move(source: localFile.uw_absoluteString, destination: toPath), error: error)
}
task.taskDescription = self.dictionaryToJSON(["type": "Copy", "source": localFile.uw_absoluteString, "dest": toPath])
task.resume()
}
public func copyPathToLocalFile(path: String, toLocalURL: NSURL, completionHandler: SimpleCompletionHandler) {
NotImplemented()
let request = NSMutableURLRequest(URL: absoluteURL(path))
let task = session.downloadTaskWithRequest(request) { (sourceFileURL, response, error) in
if let sourceFileURL = sourceFileURL {
do {
try NSFileManager.defaultManager().copyItemAtURL(sourceFileURL, toURL: toLocalURL)
} catch let e {
completionHandler?(error: e)
return
var files = [FileObject]()
for entry in entries {
if let entry = entry as? [String: AnyObject], let file = DropboxFileObject(json: entry), queryIsTruePredicate || query.evaluate(with: file.mapPredicate()) {
files.append(file)
progress.completedUnitCount += 1
foundItemHandler?(file)
}
}
completionHandler?(error: error)
}
task.taskDescription = self.dictionaryToJSON(["type": "Copy", "source": path, "dest": toLocalURL.uw_absoluteString])
task.resume()
}
}
extension DropboxFileProvider: FileProviderReadWrite {
public func contentsAtPath(path: String, completionHandler: ((contents: NSData?, error: ErrorType?) -> Void)) {
self.contentsAtPath(path, offset: 0, length: -1, completionHandler: completionHandler)
let ncursor: String?
if let hasmore = (json["has_more"] as? NSNumber)?.boolValue, hasmore {
ncursor = json["cursor"] as? String
} else if let hasmore = (json["more"] as? NSNumber)?.boolValue, hasmore {
ncursor = (json["start"] as? Int).flatMap(String.init)
} else {
ncursor = nil
}
return (files, nil, ncursor)
}, completionHandler: completionHandler)
}
public func contentsAtPath(path: String, offset: Int64, length: Int, completionHandler: ((contents: NSData?, error: ErrorType?) -> Void)) {
override func request(for operation: FileOperationType, overwrite: Bool = false, attributes: [URLResourceKey : Any] = [:]) -> URLRequest {
let url = NSURL(string: "https://api.dropboxapi.com/2/files/download")!
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "GET"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
if length > 0 {
request.setValue("bytes=\(offset)-\(offset + length)", forHTTPHeaderField: "Range")
} else if offset > 0 && length < 0 {
request.setValue("bytes=\(offset)-", forHTTPHeaderField: "Range")
func uploadRequest(to path: String) -> URLRequest {
var requestDictionary = [String: AnyObject]()
let url: URL = URL(string: "files/upload", relativeTo: contentURL)!
requestDictionary["path"] = correctPath(path) as NSString?
requestDictionary["mode"] = (overwrite ? "overwrite" : "add") as NSString
//requestDictionary["client_modified"] = (attributes[.contentModificationDateKey] as? Date)?.format(with: .rfc3339) as NSString?
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue(authentication: credential, with: .oAuth2)
request.setValue(contentType: .stream)
request.setValue(dropboxArgKey: requestDictionary)
return request
}
let requestDictionary = ["path": path]
request.setValue(dictionaryToJSON(requestDictionary), forHTTPHeaderField: "Dropbox-API-Arg")
let task = session.downloadTaskWithRequest(request, completionHandler: { (cacheURL, response, error) in
guard let cacheURL = cacheURL, let httpResponse = response as? NSHTTPURLResponse where httpResponse.statusCode < 300 else {
let code = FileProviderDropboxErrorCode(rawValue: (response as? NSHTTPURLResponse)?.statusCode ?? -1)
let dbError: FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path) : nil
completionHandler(contents: nil, error: dbError ?? error)
return
}
let destURL = NSURL(fileURLWithPath: NSTemporaryDirectory()).uw_URLByAppendingPathComponent(cacheURL.lastPathComponent ?? "tmpfile")
do {
try NSFileManager.defaultManager().moveItemAtURL(cacheURL, toURL: destURL)
completionHandler(contents: NSData(contentsOfURL: destURL), error: error)
} catch let e {
completionHandler(contents: nil, error: e)
}
})
task.resume()
}
public func writeContentsAtPath(path: String, contents data: NSData, atomically: Bool = false, completionHandler: SimpleCompletionHandler) {
NotImplemented()
let url = atomically ? absoluteURL(path).uw_URLByAppendingPathExtension("tmp") : absoluteURL(path)
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "PUT"
let task = session.uploadTaskWithRequest(request, fromData: data) { (data, response, error) in
defer {
self.delegateNotify(.Modify(path: path), error: error)
}
if atomically {
self.moveItemAtPath((path as NSString).stringByAppendingPathExtension("tmp")!, toPath: path, completionHandler: completionHandler)
}
if let error = error {
// If there is no error, completionHandler has been executed by move command
completionHandler?(error: error)
}
func downloadRequest(from path: String) -> URLRequest {
let url = URL(string: "files/download", relativeTo: contentURL)!
var request = URLRequest(url: url)
request = URLRequest(url: url)
request.setValue(authentication: credential, with: .oAuth2)
request.setValue(dropboxArgKey: ["path": correctPath(path)! as NSString])
return request
}
// content operations
switch operation {
case .copy(source: let source, destination: let dest) where dest.lowercased().hasPrefix("file://"):
return downloadRequest(from: source)
case .fetch(let path):
return downloadRequest(from: path)
case .copy(source: let source, destination: let dest) where source.lowercased().hasPrefix("file://"):
return uploadRequest(to: dest)
case .modify(let path):
return uploadRequest(to: path)
default:
return self.apiRequest(for: operation, overwrite: overwrite)
}
task.taskDescription = self.dictionaryToJSON(["type": "Modify", "source": path])
task.resume()
}
public func searchFilesAtPath(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: ((files: [FileObject], error: ErrorType?) -> Void)) {
NotImplemented()
func apiRequest(for operation: FileOperationType, overwrite: Bool = false) -> URLRequest {
let url: String
let sourcePath = operation.source
let destPath = operation.destination
var requestDictionary = [String: AnyObject]()
switch operation {
case .create:
url = "files/create_folder_v2"
case .copy:
url = "files/copy_v2"
requestDictionary["allow_shared_folder"] = NSNumber(value: true)
case .move:
url = "files/move_v2"
requestDictionary["allow_shared_folder"] = NSNumber(value: true)
case .remove:
url = "files/delete_v2"
default: // modify, link, fetch
fatalError("Unimplemented operation \(operation.description) in \(#file)")
}
var request = URLRequest(url: URL(string: url, relativeTo: apiURL)!)
request.httpMethod = "POST"
request.setValue(authentication: credential, with: .oAuth2)
request.setValue(contentType: .json)
if let dest = correctPath(destPath) as NSString? {
requestDictionary["from_path"] = correctPath(sourcePath) as NSString?
requestDictionary["to_path"] = dest
} else {
requestDictionary["path"] = correctPath(sourcePath) as NSString?
}
request.httpBody = Data(jsonDictionary: requestDictionary)
return request
}
private func registerNotifcation(path: String, eventHandler: (() -> Void)) {
/* There is two ways to monitor folders chaging in Dropbox. Either using webooks
override func serverError(with code: FileProviderHTTPErrorCode, path: String?, data: Data?) -> FileProviderHTTPError {
let errorDesc: String?
if let response = data?.deserializeJSON() {
errorDesc = (response["user_message"] as? String) ?? (response["error"]?["tag"] as? String)
} else {
errorDesc = data.flatMap({ String(data: $0, encoding: .utf8) })
}
return FileProviderDropboxError(code: code, path: path ?? "", errorDescription: errorDesc)
}
override var maxUploadSimpleSupported: Int64 {
return 157_286_400 // 150MB
}
/*
fileprivate func registerNotifcation(path: String, eventHandler: (() -> Void)) {
/* There is two ways to monitor folders changing in Dropbox. Either using webooks
* which means you have to implement a server to translate it to push notifications
* or using apiv2 list_folder/longpoll method. The second one is implemeted here.
* Tough webhooks are much more efficient, longpoll is much simpler to implement!
@@ -299,87 +285,223 @@ extension DropboxFileProvider: FileProviderReadWrite {
*/
NotImplemented()
}
private func unregisterNotifcation(path: String) {
fileprivate func unregisterNotifcation(path: String) {
NotImplemented()
}
}
internal extension DropboxFileProvider {
private func mapToFileObject(jsonStr: String) -> DropboxFileObject? {
guard let json = self.jsonToDictionary(jsonStr) else { return nil }
return self.mapToFileObject(json)
}
*/
// TODO: Implement /get_account & /get_current_account
private func mapToFileObject(json: [String: AnyObject]) -> DropboxFileObject? {
guard let name = json["name"] as? String else { return nil }
guard let path = json["path_display"] as? String else { return nil }
let href = NSURL(string: path)!
let size = (json["size"] as? NSNumber)?.longLongValue ?? -1
let serverTime = resolveDate(json["server_modified"] as? String ?? "")
let modifiedDate = resolveDate(json["client_modified"] as? String ?? "")
let isDirectory = (json[".tag"] as? String) == "folder"
let isReadonly = (json["sharing_info"]?["read_only"] as? NSNumber)?.boolValue ?? false
let id = json["id"] as? String
let rev = json["id"] as? String
return DropboxFileObject(absoluteURL: href, name: name, path: path, size: size, serverTime: serverTime, createdDate: nil, modifiedDate: modifiedDate, fileType: isDirectory ? .Directory : .Regular, isHidden: false, isReadOnly: isReadonly, id: id, rev: rev)
}
private func delegateNotify(operation: FileOperation, error: ErrorType?) {
dispatch_async(dispatch_get_main_queue(), {
if error == nil {
self.delegate?.fileproviderSucceed(self, operation: operation)
} else {
self.delegate?.fileproviderFailed(self, operation: operation)
open func publicLink(to path: String, completionHandler: @escaping ((_ link: URL?, _ attribute: FileObject?, _ 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(authentication: credential, with: .oAuth2)
request.setValue(contentType: .json)
let requestDictionary: [String: AnyObject] = ["path": correctPath(path)! as NSString]
request.httpBody = Data(jsonDictionary: requestDictionary)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var serverError: FileProviderHTTPError?
var link: URL?
var fileObject: DropboxFileObject?
if let response = response as? HTTPURLResponse {
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
serverError = code.flatMap { self.serverError(with: $0, path: path, data: data) }
if let json = data?.deserializeJSON() {
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, serverError ?? error)
})
task.resume()
}
/**
Downloads a file from remote url to designated path asynchronously.
- Parameters:
- remoteURL: a valid remote url to file.
- to: Destination path of file, including file/directory name.
- completionHandler: a closure with result of directory entries or error.
- `jobId`: Job ID returned by Dropbox to monitor the copy/download progress.
- `attribute`: A `FileObject` containing the attributes of the item.
- `error`: Error returned by Dropbox.
*/
open func copyItem(remoteURL: URL, to toPath: String, completionHandler: @escaping ((_ jobId: String?, _ attribute: DropboxFileObject?, _ error: Error?) -> Void)) {
if remoteURL.isFileURL {
completionHandler(nil, nil, self.urlError(remoteURL.path, code: .badURL))
return
}
let url = URL(string: "files/save_url", relativeTo: apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue(authentication: credential, with: .oAuth2)
request.setValue(contentType: .json)
let requestDictionary: [String: AnyObject] = ["path": correctPath(toPath)! as NSString, "url" : remoteURL.absoluteString as NSString]
request.httpBody = Data(jsonDictionary: requestDictionary)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var serverError: FileProviderHTTPError?
var jobId: String?
var fileObject: DropboxFileObject?
if let response = response as? HTTPURLResponse {
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
serverError = code.flatMap { self.serverError(with: $0, path: toPath, data: data) }
if let json = data?.deserializeJSON() {
jobId = json["async_job_id"] as? String
if let attribDic = json["metadata"] as? [String: AnyObject] {
fileObject = DropboxFileObject(json: attribDic)
}
}
}
completionHandler(jobId, fileObject, serverError ?? error)
})
task.resume()
}
/**
Copys a file from another user Dropbox storage to designated path asynchronously.
- Parameters:
- reference: a valid reference string from another user via `copy_reference/get` REST method.
- to: Destination path of file, including file/directory name.
- completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
*/
open func copyItem(reference: String, to toPath: String, completionHandler: SimpleCompletionHandler) {
let url = URL(string: "files/copy_reference/save", relativeTo: apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue(authentication: credential, with: .oAuth2)
request.setValue(contentType: .json)
let requestDictionary: [String: AnyObject] = ["path": correctPath(toPath)! as NSString, "copy_reference" : reference as NSString]
request.httpBody = Data(jsonDictionary: requestDictionary)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var serverError: FileProviderHTTPError?
if let response = response as? HTTPURLResponse {
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
serverError = code.flatMap { self.serverError(with: $0, path: toPath, data: data) }
}
completionHandler?(serverError ?? error)
})
task.resume()
}
}
// MARK: URLSession delegate
extension DropboxFileProvider: NSURLSessionDataDelegate, NSURLSessionDownloadDelegate {
public func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {
return
extension DropboxFileProvider: ExtendedFileProvider {
open func thumbnailOfFileSupported(path: String) -> Bool {
switch (path as NSString).pathExtension.lowercased() {
case "jpg", "jpeg", "gif", "bmp", "png", "tif", "tiff":
return true
case "doc", "docx", "docm", "xls", "xlsx", "xlsm":
return true
case "ppt", "pps", "ppsx", "ppsm", "pptx", "pptm":
return true
case "rtf":
return true
default:
return false
}
}
public func URLSession(session: NSURLSession, task: NSURLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
guard let desc = task.taskDescription, let json = jsonToDictionary(desc) else {
return
open func propertiesOfFileSupported(path: String) -> Bool {
let fileExt = (path as NSString).pathExtension.lowercased()
switch fileExt {
case "jpg", "jpeg", "bmp", "gif", "png", "tif", "tiff":
return true
/*case "mp3", "aac", "m4a":
return true*/
case "mp4", "mpg", "3gp", "mov", "avi":
return true
default:
return false
}
guard let type = json["type"] as? String, let source = json["source"] as? String else {
return
}
let dest = json["dest"] as? String
let op : FileOperation
switch type {
case "Create":
op = .Create(path: source)
case "Copy":
guard let dest = dest else { return }
op = .Copy(source: source, destination: dest)
case "Move":
guard let dest = dest else { return }
op = .Move(source: source, destination: dest)
case "Modify":
op = .Modify(path: source)
case "Remove":
op = .Remove(path: source)
case "Link":
guard let dest = dest else { return }
op = .Link(link: source, target: dest)
}
/// Default value for dimension is 64x64, according to Dropbox documentation
open func thumbnailOfFile(path: String, dimension: CGSize?, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void)) {
let url: URL
let thumbAPI: Bool
switch (path as NSString).pathExtension.lowercased() {
case "jpg", "jpeg", "gif", "bmp", "png", "tif", "tiff":
url = URL(string: "files/get_thumbnail", relativeTo: contentURL)!
thumbAPI = true
case "doc", "docx", "docm", "xls", "xlsx", "xlsm":
fallthrough
case "ppt", "pps", "ppsx", "ppsm", "pptx", "pptm":
fallthrough
case "rtf":
url = URL(string: "files/get_preview", relativeTo: contentURL)!
thumbAPI = false
default:
return
}
let progress = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
self.delegate?.fileproviderProgress(self, operation: op, progress: progress)
var request = URLRequest(url: url)
request.setValue(authentication: credential, with: .oAuth2)
var requestDictionary: [String: AnyObject] = ["path": path as NSString]
if thumbAPI {
requestDictionary["format"] = "jpeg" as NSString
let size: String
switch dimension?.height ?? 64 {
case 0...32: size = "w32h32"
case 33...64: size = "w64h64"
case 65...128: size = "w128h128"
case 129...480: size = "w640h480"
default: size = "w1024h768"
}
requestDictionary["size"] = size as NSString
}
request.setValue(dropboxArgKey: requestDictionary)
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 = result.deserializeJSON() {
if jsonResult["error"] != nil {
completionHandler(nil, self.urlError(path, code: .cannotDecodeRawData))
}
}
if let data = data {
if data.isPDF, let pageImage = DropboxFileProvider.convertToImage(pdfData: data) {
image = pageImage
} 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 if let fetchedimage = ImageClass(data: data){
if let dimension = dimension {
image = DropboxFileProvider.scaleDown(image: fetchedimage, toSize: dimension)
} else {
image = fetchedimage
}
}
}
completionHandler(image, error)
})
task.resume()
}
public func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
guard let desc = downloadTask.taskDescription, let json = jsonToDictionary(desc), let source = json["source"] as? String, dest = json["dest"] as? String else {
return
}
self.delegate?.fileproviderProgress(self, operation: .Copy(source: source, destination: dest), progress: Float(totalBytesWritten) / Float(totalBytesExpectedToWrite))
open func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String : Any], _ keys: [String], _ error: Error?) -> Void)) {
let url = URL(string: "files/get_metadata", relativeTo: apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue(authentication: credential, with: .oAuth2)
request.setValue(contentType: .json)
let requestDictionary: [String: AnyObject] = ["path": correctPath(path)! as NSString, "include_media_info": NSNumber(value: true)]
request.httpBody = Data(jsonDictionary: requestDictionary)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var serverError: FileProviderHTTPError?
var dic = [String: Any]()
var keys = [String]()
if let response = response as? HTTPURLResponse {
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
serverError = code.flatMap { self.serverError(with: $0, path: path, data: data) }
if let json = data?.deserializeJSON(), let properties = (json["media_info"] as? [String: Any])?["metadata"] as? [String: Any] {
(dic, keys) = self.mapMediaInfo(properties)
}
}
completionHandler(dic, keys, serverError ?? error)
})
task.resume()
}
}
}
+158
View File
@@ -0,0 +1,158 @@
//
// DropboxHelper.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2016 Mousavian. Distributed under MIT license.
//
import Foundation
/// Error returned by Dropbox server when trying to access or do operations on a file or folder.
public struct FileProviderDropboxError: FileProviderHTTPError {
public let code: FileProviderHTTPErrorCode
public let path: String
public let errorDescription: String?
}
/// Containts path, url and attributes of a Dropbox file or resource.
public final class DropboxFileObject: FileObject {
internal convenience init? (jsonStr: String) {
guard let json = jsonStr.deserializeJSON() else { return nil }
self.init(json: json)
}
internal init? (json: [String: AnyObject]) {
var json = json
if json["name"] == nil, let metadata = json["metadata"] as? [String: AnyObject] {
json = metadata
}
guard let name = json["name"] as? String else { return nil }
guard let path = json["path_display"] as? String else { return nil }
super.init(url: nil, name: name, path: path)
self.size = (json["size"] as? NSNumber)?.int64Value ?? -1
self.serverTime = (json["server_modified"] as? String).flatMap(Date.init(rfcString:))
self.modifiedDate = (json["client_modified"] as? String).flatMap(Date.init(rfcString:))
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
}
/// The time contents of file has been modified on server, returns nil if not set
open internal(set) var serverTime: Date? {
get {
return allValues[.serverDateKey] as? Date
}
set {
allValues[.serverDateKey] = newValue
}
}
/// The document identifier is a value assigned by the Dropbox to a file.
/// This value is used to identify the document regardless of where it is moved on a volume.
open internal(set) var id: String? {
get {
return allValues[.fileResourceIdentifierKey] as? String
}
set {
allValues[.fileResourceIdentifierKey] = newValue
}
}
/// The revision of file, which changes when a file contents are modified.
/// Changes to attributes or other file metadata do not change the identifier.
open internal(set) var rev: String? {
get {
return allValues[.generationIdentifierKey] as? String
}
set {
allValues[.generationIdentifierKey] = newValue
}
}
}
internal extension DropboxFileProvider {
internal func correctPath(_ path: String?) -> String? {
guard let path = path else { return nil }
if path.hasPrefix("id:") || path.hasPrefix("rev:") {
return path
}
var p = path.hasPrefix("/") ? path : "/" + path
if p.hasSuffix("/") {
p.remove(at: p.index(before:p.endIndex))
}
return p
}
internal func listRequest(path: String, queryStr: String? = nil, recursive: Bool = false) -> ((_ token: String?) -> URLRequest?) {
if let queryStr = queryStr {
return { [weak self] (token) -> URLRequest? in
guard let `self` = self else { return nil }
let url = URL(string: "files/search", relativeTo: self.apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue(authentication: self.credential, with: .oAuth2)
request.setValue(contentType: .json)
var requestDictionary: [String: AnyObject] = ["path": self.correctPath(path) as NSString!]
requestDictionary["query"] = queryStr as NSString
requestDictionary["start"] = NSNumber(value: (token.flatMap( { Int($0) } ) ?? 0))
request.httpBody = Data(jsonDictionary: requestDictionary)
return request
}
} else {
return { [weak self] (token) -> URLRequest? in
guard let `self` = self else { return nil }
var requestDictionary = [String: AnyObject]()
let url: URL
if let token = token {
url = URL(string: "files/list_folder/continue", relativeTo: self.apiURL)!
requestDictionary["cursor"] = token as NSString?
} else {
url = URL(string: "files/list_folder", relativeTo: self.apiURL)!
requestDictionary["path"] = self.correctPath(path) as NSString?
requestDictionary["recursive"] = NSNumber(value: recursive)
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue(authentication: self.credential, with: .oAuth2)
request.setValue(contentType: .json)
request.httpBody = Data(jsonDictionary: requestDictionary)
return request
}
}
}
}
internal extension DropboxFileProvider {
static let dateFormatter = DateFormatter()
static let decimalFormatter = NumberFormatter()
func mapMediaInfo(_ json: [String: Any]) -> (dictionary: [String: Any], keys: [String]) {
var dic = [String: Any]()
var keys = [String]()
if let dimensions = json["dimensions"] as? [String: Any], let height = dimensions["height"] as? UInt64, let width = dimensions["width"] as? UInt64 {
keys.append("Dimensions")
dic["Dimensions"] = "\(width)x\(height)"
}
if let location = json["location"] as? [String: Any], let latitude = location["latitude"] as? Double, let longitude = location["longitude"] as? Double {
DropboxFileProvider.decimalFormatter.numberStyle = .decimal
DropboxFileProvider.decimalFormatter.maximumFractionDigits = 5
keys.append("Location")
let latStr = DropboxFileProvider.decimalFormatter.string(from: NSNumber(value: latitude))!
let longStr = DropboxFileProvider.decimalFormatter.string(from: NSNumber(value: longitude))!
dic["Location"] = "\(latStr), \(longStr)"
}
if let timeTakenStr = json["time_taken"] as? String, let timeTaken = Date(rfcString: 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"] = TimeInterval(duration).formatshort
}
return (dic, keys)
}
}
+506
View File
@@ -0,0 +1,506 @@
//
// ExtendedLocalFileProvider.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2017 Mousavian. Distributed under MIT license.
//
import Foundation
import ImageIO
import CoreGraphics
import AVFoundation
extension LocalFileProvider: ExtendedFileProvider {
open func thumbnailOfFileSupported(path: String) -> Bool {
switch (path as NSString).pathExtension.lowercased() {
case LocalFileInformationGenerator.imageThumbnailExtensions.contains:
return true
case LocalFileInformationGenerator.audioThumbnailExtensions.contains:
return true
case LocalFileInformationGenerator.videoThumbnailExtensions.contains:
return true
case LocalFileInformationGenerator.pdfThumbnailExtensions.contains:
return true
case LocalFileInformationGenerator.officeThumbnailExtensions.contains:
return true
case LocalFileInformationGenerator.customThumbnailExtensions.contains:
return true
default:
return false
}
}
open func propertiesOfFileSupported(path: String) -> Bool {
let fileExt = (path as NSString).pathExtension.lowercased()
switch fileExt {
case LocalFileInformationGenerator.imagePropertiesExtensions.contains:
return LocalFileInformationGenerator.imageProperties != nil
case LocalFileInformationGenerator.audioPropertiesExtensions.contains:
return LocalFileInformationGenerator.audioProperties != nil
case LocalFileInformationGenerator.videoPropertiesExtensions.contains:
return LocalFileInformationGenerator.videoProperties != nil
case LocalFileInformationGenerator.pdfPropertiesExtensions.contains:
return LocalFileInformationGenerator.pdfProperties != nil
case LocalFileInformationGenerator.archivePropertiesExtensions.contains:
return LocalFileInformationGenerator.archiveProperties != nil
case LocalFileInformationGenerator.officePropertiesExtensions.contains:
return LocalFileInformationGenerator.officeProperties != nil
case LocalFileInformationGenerator.customPropertiesExtensions.contains:
return LocalFileInformationGenerator.customProperties != nil
default:
return false
}
}
open func thumbnailOfFile(path: String, dimension: CGSize? = nil, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void)) {
let dimension = dimension ?? CGSize(width: 64, height: 64)
(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.contains:
thumbnailImage = LocalFileInformationGenerator.videoThumbnail(fileURL)
case LocalFileInformationGenerator.audioThumbnailExtensions.contains:
thumbnailImage = LocalFileInformationGenerator.audioThumbnail(fileURL)
case LocalFileInformationGenerator.imageThumbnailExtensions.contains:
thumbnailImage = LocalFileInformationGenerator.imageThumbnail(fileURL)
case LocalFileInformationGenerator.pdfThumbnailExtensions.contains:
thumbnailImage = LocalFileInformationGenerator.pdfThumbnail(fileURL)
case LocalFileInformationGenerator.officeThumbnailExtensions.contains:
thumbnailImage = LocalFileInformationGenerator.officeThumbnail(fileURL)
case LocalFileInformationGenerator.customThumbnailExtensions.contains:
thumbnailImage = LocalFileInformationGenerator.customThumbnail(fileURL)
default:
completionHandler(nil, nil)
return
}
if let image = thumbnailImage {
let scaledImage = LocalFileProvider.scaleDown(image: image, toSize: dimension)
completionHandler(scaledImage, nil)
}
}
}
open 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.contains:
getter = LocalFileInformationGenerator.imageProperties
case LocalFileInformationGenerator.audioPropertiesExtensions.contains:
getter = LocalFileInformationGenerator.audioProperties
case LocalFileInformationGenerator.videoPropertiesExtensions.contains:
getter = LocalFileInformationGenerator.videoProperties
case LocalFileInformationGenerator.pdfPropertiesExtensions.contains:
getter = LocalFileInformationGenerator.pdfProperties
case LocalFileInformationGenerator.archivePropertiesExtensions.contains:
getter = LocalFileInformationGenerator.archiveProperties
case LocalFileInformationGenerator.officePropertiesExtensions.contains:
getter = LocalFileInformationGenerator.officeProperties
case LocalFileInformationGenerator.customPropertiesExtensions.contains:
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)
}
}
}
/// Holds supported file types and thumbnail/properties generator for specefied type of file
public struct LocalFileInformationGenerator {
/// Image extensions supportes for thumbnail.
///
/// Default: `["jpg", "jpeg", "gif", "bmp", "png", "tif", "tiff", "ico"]`
static public var imageThumbnailExtensions: [String] = ["heic", "jpg", "jpeg", "gif", "bmp", "png", "tif", "tiff", "ico"]
/// Audio and music extensions supportes for thumbnail.
///
/// Default: `["mp1", "mp2", "mp3", "mpa", "mpga", "m1a", "m2a", "m4a", "m4b", "m4p", "m4r", "aac", "snd", "caf", "aa", "aax", "adts", "aif", "aifc", "aiff", "au", "flac", "amr", "wav", "wave", "bwf", "ac3", "eac3", "ec3", "cdda"]`
static public var audioThumbnailExtensions: [String] = ["mp1", "mp2", "mp3", "mpa", "mpga", "m1a", "m2a", "m4a", "m4b", "m4p", "m4r", "aac", "snd", "caf", "aa", "aax", "adts", "aif", "aifc", "aiff", "au", "flac", "amr", "wav", "wave", "bwf", "ac3", "eac3", "ec3", "cdda"]
/// Video extensions supportes for thumbnail.
///
/// Default: `["mov", "mp4", "mpg4", "m4v", "mqv", "mpg", "mpeg", "avi", "vfw", "3g2", "3gp", "3gp2", "3gpp", "qt"]`
static public var videoThumbnailExtensions: [String] = ["mov", "mp4", "mpg4", "m4v", "mqv", "mpg", "mpeg", "avi", "vfw", "3g2", "3gp", "3gp2", "3gpp", "qt"]
/// Portable document file extensions supportes for thumbnail.
///
/// Default: `["pdf"]`
static public var pdfThumbnailExtensions: [String] = ["pdf"]
/// Office document extensions supportes for thumbnail.
///
/// Default: `empty`
static public var officeThumbnailExtensions: [String] = []
/// Custom document extensions supportes for thumbnail.
///
/// Default: `empty`
static public var customThumbnailExtensions: [String] = []
/// Image extensions supportes for properties.
///
/// Default: `["jpg", "jpeg", "gif", "bmp", "png", "tif", "tiff"]`
static public var imagePropertiesExtensions: [String] = ["heic", "jpg", "jpeg", "bmp", "gif", "png", "tif", "tiff"]
/// Audio and music extensions supportes for properties.
///
/// Default: `["mp1", "mp2", "mp3", "mpa", "mpga", "m1a", "m2a", "m4a", "m4b", "m4p", "m4r", "aac", "snd", "caf", "aa", "aax", "adts", "aif", "aifc", "aiff", "au", "flac", "amr", "wav", "wave", "bwf", "ac3", "eac3", "ec3", "cdda"]`
static public var audioPropertiesExtensions: [String] = ["mp1", "mp2", "mp3", "mpa", "mpga", "m1a", "m2a", "m4a", "m4b", "m4p", "m4r", "aac", "snd", "caf", "aa", "aax", "adts", "aif", "aifc", "aiff", "au", "flac", "amr", "wav", "wave", "bwf", "ac3", "eac3", "ec3", "cdda"]
/// Video extensions supportes for properties.
///
/// Default: `["mov", "mp4", "mpg4", "m4v", "mqv", "mpg", "mpeg", "avi", "vfw", "3g2", "3gp", "3gp2", "3gpp", "qt"]`
static public var videoPropertiesExtensions: [String] = ["mov", "mp4", "mpg4", "m4v", "mqv", "mpg", "mpeg", "avi", "vfw", "3g2", "3gp", "3gp2", "3gpp", "qt"]
/// Portable document file extensions supportes for properties.
///
/// Default: `["pdf"]`
static public var pdfPropertiesExtensions: [String] = ["pdf"]
/// Archive extensions (like zip) supportes for properties.
///
/// Default: `empty`
static public var archivePropertiesExtensions: [String] = []
/// Office document extensions supportes for properties.
///
/// Default: `empty`
static public var officePropertiesExtensions: [String] = []
/// Custom document extensions supportes for properties.
///
/// Default: `empty`
static public var customPropertiesExtensions: [String] = []
/// Thumbnail generator closure for image files.
static public var imageThumbnail: (_ fileURL: URL) -> ImageClass? = { fileURL in
return ImageClass(contentsOfFile: fileURL.path)
}
/// Thumbnail generator closure for audio and music files.
static public var audioThumbnail: (_ fileURL: URL) -> ImageClass? = { fileURL in
let playerItem = AVPlayerItem(url: fileURL)
let metadataList = playerItem.asset.commonMetadata
#if swift(>=4.0)
let commonKeyArtwork = AVMetadataKey.commonKeyArtwork
#else
let commonKeyArtwork = AVMetadataCommonKeyArtwork
#endif
for item in metadataList {
if item.commonKey == commonKeyArtwork {
if let data = item.dataValue {
return ImageClass(data: data)
}
}
}
return nil
}
/// Thumbnail generator closure for video files.
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: .zero)
#else
return ImageClass(cgImage: cgImage)
#endif
}
return nil
}
/// Thumbnail generator closure for portable document files files.
static public var pdfThumbnail: (_ fileURL: URL) -> ImageClass? = { fileURL in
return LocalFileProvider.convertToImage(pdfURL: fileURL)
}
/// Thumbnail generator closure for office document files.
/// - Note: No default implementation is avaiable
static public var officeThumbnail: (_ fileURL: URL) -> ImageClass? = { fileURL in
return nil
}
/// Thumbnail generator closure for custom type of files.
/// - Note: No default implementation is avaiable
static public var customThumbnail: (_ fileURL: URL) -> ImageClass? = { fileURL in
return nil
}
/// Properties generator closure for image files.
static public var imageProperties: ((_ fileURL: URL) -> (prop: [String: Any], keys: [String]))? = { fileURL in
var dic = [String: Any]()
var keys = [String]()
func add(key: String, value: Any?) {
if let value = value, !((value as? String)?.isEmpty ?? false) {
keys.append(key)
dic[key] = value
}
}
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))
}
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? NSDictionary ?? [:]
let exifDict = imageDict[kCGImagePropertyExifDictionary as String] as? NSDictionary ?? [:]
if let pixelWidth = imageDict.object(forKey: kCGImagePropertyPixelWidth) as? NSNumber, let pixelHeight = imageDict.object(forKey: kCGImagePropertyPixelHeight) as? NSNumber {
add(key: "Dimensions", value: "\(pixelWidth)x\(pixelHeight)")
}
add(key: "DPI", value: imageDict[kCGImagePropertyDPIWidth as String])
add(key: "Device make", value: tiffDict[kCGImagePropertyTIFFMake as String])
add(key: "Device model", value: tiffDict[kCGImagePropertyTIFFModel as String])
add(key: "Lens model", value: exifDict[kCGImagePropertyExifLensModel as String])
add(key: "Artist", value: tiffDict[kCGImagePropertyTIFFArtist as String] as? String)
add(key: "Copyright", value: tiffDict[kCGImagePropertyTIFFCopyright as String] as? String)
add(key: "Date taken", value: tiffDict[kCGImagePropertyTIFFDateTime as String] as? String)
if let latitude = tiffDict[kCGImagePropertyGPSLatitude as String] as? NSNumber, let longitude = tiffDict[kCGImagePropertyGPSLongitude as String] as? NSNumber {
add(key: "Location", value: "\(latitude), \(longitude)")
}
add(key: "Altitude", value: tiffDict[kCGImagePropertyGPSAltitude as String] as? NSNumber)
add(key: "Area", value: tiffDict[kCGImagePropertyGPSAreaInformation as String])
add(key: "Color space", value: imageDict[kCGImagePropertyColorModel as String])
add(key: "Focal length", value: exifDict[kCGImagePropertyExifFocalLength as String])
add(key: "F number", value: exifDict[kCGImagePropertyExifFNumber as String])
add(key: "Exposure program", value: exifDict[kCGImagePropertyExifExposureProgram as String])
if let exp = exifDict[kCGImagePropertyExifExposureTime as String] as? NSNumber {
let expfrac = simplify(Int64(exp.doubleValue * 1_163_962_800_000), 1_163_962_800_000)
add(key: "Exposure time", value: "\(expfrac.newTop)/\(expfrac.newBottom)")
}
add(key: "ISO speed", value: (exifDict[kCGImagePropertyExifISOSpeedRatings as String] as? [NSNumber])?.first)
return (dic, keys)
}
/// Properties generator closure for audio and music files.
static var audioProperties: ((_ fileURL: URL) -> (prop: [String: Any], keys: [String]))? = { fileURL in
var dic = [String: Any]()
var keys = [String]()
func add(key: String, value: Any?) {
if let value = value {
keys.append(key)
dic[key] = value
}
}
func makeDescription(_ key: String?) -> String? {
guard let key = key else {
return nil
}
guard let regex = try? NSRegularExpression(pattern: "([a-z])([A-Z])" , options: []) else {
return nil
}
let newKey = regex.stringByReplacingMatches(in: key, options: [], range: NSRange(location: 0, length: (key as NSString).length) , withTemplate: "$1 $2")
return newKey.capitalized
}
guard FileManager.default.fileExists(atPath: fileURL.path) else {
return (dic, keys)
}
let playerItem = AVPlayerItem(url: fileURL)
let metadataList = playerItem.asset.commonMetadata
for item in metadataList {
#if swift(>=4.0)
let commonKey = item.commonKey?.rawValue
#else
let commonKey = item.commonKey
#endif
if let description = makeDescription(commonKey) {
if let value = item.stringValue {
keys.append(description)
dic[description] = value
}
}
}
if let ap = try? AVAudioPlayer(contentsOf: fileURL) {
add(key: "Duration", value: ap.duration.formatshort)
add(key: "Bitrate", value: ap.settings[AVSampleRateKey] as? Int)
}
return (dic, keys)
}
/// Properties generator closure for video files.
static public var videoProperties: ((_ fileURL: URL) -> (prop: [String: Any], keys: [String]))? = { fileURL in
var dic = [String: Any]()
var keys = [String]()
func add(key: String, value: Any?) {
if let value = value {
keys.append(key)
dic[key] = value
}
}
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)
#if swift(>=4.0)
let videoTracks = asset.tracks(withMediaType: AVMediaType.video)
#else
let videoTracks = asset.tracks(withMediaType: AVMediaTypeVideo)
#endif
if let videoTrack = videoTracks.first {
var bitrate: Float = 0
let width = Int(videoTrack.naturalSize.width)
let height = Int(videoTrack.naturalSize.height)
add(key: "Dimensions", value: "\(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
}
add(key: "Duration", value: TimeInterval(duration).formatshort)
add(key: "Video Bitrate", value: "\(Int(ceil(bitrate / 1000))) kbps")
}
#if swift(>=4.0)
let audioTracks = asset.tracks(withMediaType: AVMediaType.audio)
#else
let audioTracks = asset.tracks(withMediaType: AVMediaTypeAudio)
#endif
// dic["Audio channels"] = audioTracks.count
var bitrate: Float = 0
for track in audioTracks {
bitrate += track.estimatedDataRate
}
add(key: "Audio Bitrate", value: "\(Int(ceil(bitrate / 1000))) kbps")
return (dic, keys)
}
/// Properties generator closure for protable documents files.
static public var pdfProperties: ((_ fileURL: URL) -> (prop: [String: Any], keys: [String]))? = { fileURL in
var dic = [String: Any]()
var keys = [String]()
func add(key: String, value: Any?) {
if let value = value, !((value as? String)?.isEmpty ?? false) {
keys.append(key)
dic[key] = value
}
}
func getKey(_ key: String, from dict: CGPDFDictionaryRef) -> String? {
var cfStrValue: CGPDFStringRef?
if (CGPDFDictionaryGetString(dict, key, &cfStrValue)), let value = cfStrValue.flatMap({ CGPDFStringCopyTextString($0) }) {
return value as String
}
var cfArrayValue: CGPDFArrayRef?
if (CGPDFDictionaryGetArray(dict, key, &cfArrayValue)), let cfArray = cfArrayValue {
var array = [String]()
for i in 0..<CGPDFArrayGetCount(cfArray) {
var cfItemValue: CGPDFStringRef?
if CGPDFArrayGetString(cfArray, i, &cfItemValue), let item = cfItemValue.flatMap({ CGPDFStringCopyTextString($0) }) {
array.append(item as String)
}
}
return array.joined(separator: ", ")
}
return nil
}
func convertDate(_ date: String?) -> Date? {
guard let date = date else { return nil }
let dateStr = date.replacingOccurrences(of: "'", with: "").replacingOccurrences(of: "D:", with: "", options: .anchored)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyyMMddHHmmssTZ"
if let result = dateFormatter.date(from: dateStr) {
return result
}
dateFormatter.dateFormat = "yyyyMMddHHmmssZZZZZ"
if let result = dateFormatter.date(from: dateStr) {
return result
}
dateFormatter.dateFormat = "yyyyMMddHHmmssZ"
if let result = dateFormatter.date(from: dateStr) {
return result
}
dateFormatter.dateFormat = "yyyyMMddHHmmss"
if let result = dateFormatter.date(from: dateStr) {
return result
}
return nil
}
guard let provider = CGDataProvider(url: fileURL as CFURL), let reference = CGPDFDocument(provider), let dict = reference.info else {
return (dic, keys)
}
add(key: "Title", value: getKey("Title", from: dict))
add(key: "Author", value: getKey("Author", from: dict))
add(key: "Subject", value: getKey("Subject", from: dict))
add(key: "Producer", value: getKey("Producer", from: dict))
add(key: "Keywords", value: getKey("Keywords", from: dict))
var majorVersion: Int32 = 0
var minorVersion: Int32 = 0
reference.getVersion(majorVersion: &majorVersion, minorVersion: &minorVersion)
if majorVersion > 0 {
add(key: "Version", value: String(majorVersion) + "." + String(minorVersion))
}
add(key: "Pages", value: reference.numberOfPages)
if reference.numberOfPages > 0, let pageRef = reference.page(at: 1) {
let size = pageRef.getBoxRect(CGPDFBox.mediaBox).size
add(key: "Resolution", value: "\(Int(size.width))x\(Int(size.height))")
}
add(key: "Content creator", value: getKey("Creator", from: dict))
add(key: "Creation date", value: convertDate(getKey("CreationDate", from: dict)))
add(key: "Modified date", value: convertDate(getKey("ModDate", from: dict)))
add(key: "Security", value: reference.isEncrypted)
add(key: "Allows printing", value: reference.allowsPrinting)
add(key: "Allows copying", value: reference.allowsCopying)
return (dic, keys)
}
/// Properties generator closure for video files.
/// - Note: No default implementation is avaiable
static public var archiveProperties: ((_ fileURL: URL) -> (prop: [String: Any], keys: [String]))? = nil
/// Properties generator closure for office doument files.
/// - Note: No default implementation is avaiable
static public var officeProperties: ((_ fileURL: URL) -> (prop: [String: Any], keys: [String]))? = nil
/// Properties generator closure for custom type of files.
/// - Note: No default implementation is avaiable
static public var customProperties: ((_ fileURL: URL) -> (prop: [String: Any], keys: [String]))? = nil
}
+559
View File
@@ -0,0 +1,559 @@
//
// FileProviderExtensions.swift
// FileProvider
//
// Created by Amir Abbas on 12/27/1395 AP.
//
//
import Foundation
public extension Array where Element: FileObject {
/// Returns a sorted array of `FileObject`s by criterias set in attributes.
public func sort(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]
}
/// Sorts array of `FileObject`s by criterias set in attributes.
public mutating func sorted(by type: FileObjectSorting.SortType, ascending: Bool = true, isDirectoriesFirst: Bool = false) {
self = self.sort(by: type, ascending: ascending, isDirectoriesFirst: isDirectoriesFirst)
}
}
public extension Sequence where Iterator.Element == UInt8 {
/// Converts a byte array into hexadecimal string representation
func hexString() -> String {
return self.map{String(format: "%02X", $0)}.joined()
}
}
public extension URLFileResourceType {
/// **FileProvider** returns corresponding `URLFileResourceType` of a `FileAttributeType` value
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 extension URLResourceKey {
/// **FileProvider** returns url of file object.
public static let fileURLKey = URLResourceKey(rawValue: "NSURLFileURLKey")
/// **FileProvider** returns modification date of file in server
public static let serverDateKey = URLResourceKey(rawValue: "NSURLServerDateKey")
/// **FileProvider** returns HTTP ETag string of remote resource
public static let entryTagKey = URLResourceKey(rawValue: "NSURLEntryTagKey")
/// **FileProvider** returns MIME type of file, if returned by server
public static let mimeTypeKey = URLResourceKey(rawValue: "NSURLMIMETypeIdentifierKey")
/// **FileProvider** returns either file is encrypted or not
public static let isEncryptedKey = URLResourceKey(rawValue: "NSURLIsEncryptedKey")
/// **FileProvider** count of items in directory
public static let childrensCount = URLResourceKey(rawValue: "MFPURLChildrensCount")
}
public extension ProgressUserInfoKey {
/// **FileProvider** returns associated `FileProviderOperationType`
public static let fileProvderOperationTypeKey = ProgressUserInfoKey("MFPOperationTypeKey")
/// **FileProvider** returns start date/time of operation
public static let startingTimeKey = ProgressUserInfoKey("NSProgressStartingTimeKey")
}
internal extension URL {
var uw_scheme: String {
return self.scheme ?? ""
}
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)
}
}
public extension URLRequest {
/// Defines HTTP Authentication method required to access
public enum AuthenticationType {
/// Basic method for authentication
case basic
/// Digest method for authentication
case digest
/// OAuth 1.0 method for authentication (OAuth)
case oAuth1
/// OAuth 2.0 method for authentication (Bearer)
case oAuth2
}
}
/// Holds file MIME, and introduces selected type MIME as constants
public struct ContentMIMEType: RawRepresentable, Hashable, Equatable {
public var rawValue: String
public typealias RawValue = String
public init(rawValue: String) {
self.rawValue = rawValue
}
public var hashValue: Int { return rawValue.hashValue }
public static func == (lhs: ContentMIMEType, rhs: ContentMIMEType) -> Bool {
return lhs.rawValue == rhs.rawValue
}
/// Directory
static public let directory = ContentMIMEType(rawValue: "httpd/unix-directory")
// Archive and Binary
/// Binary stream and unknown types
static public let stream = ContentMIMEType(rawValue: "application/octet-stream")
/// Protable document format
static public let pdf = ContentMIMEType(rawValue: "application/pdf")
/// Zip archive
static public let zip = ContentMIMEType(rawValue: "application/zip")
/// Rar archive
static public let rarArchive = ContentMIMEType(rawValue: "application/x-rar-compressed")
/// 7-zip archive
static public let lzma = ContentMIMEType(rawValue: "application/x-7z-compressed")
/// Adobe Flash
static public let flash = ContentMIMEType(rawValue: "application/x-shockwave-flash")
/// ePub book
static public let epub = ContentMIMEType(rawValue: "application/epub+zip")
/// Java archive (jar)
static public let javaArchive = ContentMIMEType(rawValue: "application/java-archive")
// Texts
/// Text file
static public let plainText = ContentMIMEType(rawValue: "text/plain")
/// Coma-separated values
static public let csv = ContentMIMEType(rawValue: "text/csv")
/// Hyper-text markup language
static public let html = ContentMIMEType(rawValue: "text/html")
/// Common style sheet
static public let css = ContentMIMEType(rawValue: "text/css")
/// eXtended Markup language
static public let xml = ContentMIMEType(rawValue: "text/xml")
/// Javascript code file
static public let javascript = ContentMIMEType(rawValue: "application/javascript")
/// Javascript notation
static public let json = ContentMIMEType(rawValue: "application/json")
// Documents
/// Rich text file (RTF)
static public let richText = ContentMIMEType(rawValue: "application/rtf")
/// Excel 2013 (OOXML) document
static public let excel = ContentMIMEType(rawValue: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
/// Powerpoint 2013 (OOXML) document
static public let powerpoint = ContentMIMEType(rawValue: "application/vnd.openxmlformats-officedocument.presentationml.slideshow")
/// Word 2013 (OOXML) document
static public let word = ContentMIMEType(rawValue: "application/vnd.openxmlformats-officedocument.wordprocessingml.document")
// Images
/// Bitmap
static public let bmp = ContentMIMEType(rawValue: "image/bmp")
/// Graphics Interchange Format photo
static public let gif = ContentMIMEType(rawValue: "image/gif")
/// JPEG photo
static public let jpeg = ContentMIMEType(rawValue: "image/jpeg")
/// Portable network graphics
static public let png = ContentMIMEType(rawValue: "image/png")
// Audio & Video
/// MPEG Audio
static public let mpegAudio = ContentMIMEType(rawValue: "audio/mpeg")
/// MPEG Video
static public let mpeg = ContentMIMEType(rawValue: "video/mpeg")
/// MPEG4 Audio
static public let mpeg4Audio = ContentMIMEType(rawValue: "audio/mp4")
/// MPEG4 Video
static public let mpeg4 = ContentMIMEType(rawValue: "video/mp4")
/// OGG Audio
static public let ogg = ContentMIMEType(rawValue: "audio/ogg")
/// Advanced Audio Coding
static public let aac = ContentMIMEType(rawValue: "audio/x-aac")
/// Microsoft Audio Video Interleaved
static public let avi = ContentMIMEType(rawValue: "video/x-msvideo")
/// Microsoft Wave audio
static public let wav = ContentMIMEType(rawValue: "audio/x-wav")
/// Apple QuickTime format
static public let quicktime = ContentMIMEType(rawValue: "video/quicktime")
/// 3GPP
static public let threegp = ContentMIMEType(rawValue: "video/3gpp")
/// Adobe Flash video
static public let flashVideo = ContentMIMEType(rawValue: "video/x-flv")
/// Adobe Flash video
static public let flv = ContentMIMEType.flashVideo
// Google Drive
/// Google Drive: Folder
static public let googleFolder = ContentMIMEType(rawValue: "application/vnd.google-apps.folder")
/// Google Drive: Document (word processor)
static public let googleDocument = ContentMIMEType(rawValue: "application/vnd.google-apps.document")
/// Google Drive: Sheets (spreadsheet)
static public let googleSheets = ContentMIMEType(rawValue: "application/vnd.google-apps.spreadsheet")
/// Google Drive: Slides (presentation)
static public let googleSlides = ContentMIMEType(rawValue: "application/vnd.google-apps.presentation")
/// Google Drive: Drawing (vector draw)
static public let googleDrawing = ContentMIMEType(rawValue: "application/vnd.google-apps.drawing")
/// Google Drive: Audio
static public let googleAudio = ContentMIMEType(rawValue: "application/vnd.google-apps.audio")
/// Google Drive: Video
static public let googleVideo = ContentMIMEType(rawValue: "application/vnd.google-apps.video")
}
internal extension URLRequest {
mutating func setValue(authentication credential: URLCredential?, with type: AuthenticationType) {
func base64(_ str: String) -> String {
let plainData = str.data(using: .utf8)
let base64String = plainData!.base64EncodedString(options: [])
return base64String
}
guard let credential = credential else { return }
switch type {
case .basic:
let user = credential.user?.replacingOccurrences(of: ":", with: "") ?? ""
let pass = credential.password ?? ""
let authStr = "\(user):\(pass)"
if let base64Auth = authStr.data(using: .utf8)?.base64EncodedString() {
self.setValue("Basic \(base64Auth)", forHTTPHeaderField: "Authorization")
}
case .digest:
// handled by RemoteSessionDelegate
break
case .oAuth1:
if let oauth = credential.password {
self.setValue("OAuth \(oauth)", forHTTPHeaderField: "Authorization")
}
case .oAuth2:
if let bearer = credential.password {
self.setValue("Bearer \(bearer)", forHTTPHeaderField: "Authorization")
}
}
}
mutating func setValue(acceptCharset: String.Encoding, quality: Double? = nil) {
let cfEncoding = CFStringConvertNSStringEncodingToEncoding(acceptCharset.rawValue)
if let charsetString = CFStringConvertEncodingToIANACharSetName(cfEncoding) as String? {
if let qualityDesc = quality.flatMap({ String(format: "%.1f", min(0, max ($0, 1))) }) {
self.setValue("\(charsetString); q=\(qualityDesc)", forHTTPHeaderField: "Accept-Charset")
} else {
self.setValue(charsetString, forHTTPHeaderField: "Accept-Charset")
}
}
}
mutating func addValue(acceptCharset: String.Encoding, quality: Double? = nil) {
let cfEncoding = CFStringConvertNSStringEncodingToEncoding(acceptCharset.rawValue)
if let charsetString = CFStringConvertEncodingToIANACharSetName(cfEncoding) as String? {
if let qualityDesc = quality.flatMap({ String(format: "%.1f", min(0, max ($0, 1))) }) {
self.addValue("\(charsetString); q=\(qualityDesc)", forHTTPHeaderField: "Accept-Charset")
} else {
self.addValue(charsetString, forHTTPHeaderField: "Accept-Charset")
}
}
}
enum Encoding: String {
case all = "*"
case identity
case gzip
case deflate
}
mutating func setValue(acceptEncoding: Encoding, quality: Double? = nil) {
if let qualityDesc = quality.flatMap({ String(format: "%.1f", min(0, max ($0, 1))) }) {
self.setValue("\(acceptEncoding.rawValue); q=\(qualityDesc)", forHTTPHeaderField: "Accept-Encoding")
} else {
self.setValue(acceptEncoding.rawValue, forHTTPHeaderField: "Accept-Encoding")
}
}
mutating func addValue(acceptEncoding: Encoding, quality: Double? = nil) {
if let qualityDesc = quality.flatMap({ String(format: "%.1f", min(0, max ($0, 1))) }) {
self.addValue("\(acceptEncoding.rawValue); q=\(qualityDesc)", forHTTPHeaderField: "Accept-Encoding")
} else {
self.addValue(acceptEncoding.rawValue, forHTTPHeaderField: "Accept-Encoding")
}
}
mutating func setValue(acceptLanguage: Locale, quality: Double? = nil) {
let langCode = acceptLanguage.identifier.replacingOccurrences(of: "_", with: "-")
if let qualityDesc = quality.flatMap({ String(format: "%.1f", min(0, max ($0, 1))) }) {
self.setValue("\(langCode); q=\(qualityDesc)", forHTTPHeaderField: "Accept-Language")
} else {
self.setValue(langCode, forHTTPHeaderField: "Accept-Language")
}
}
mutating func addValue(acceptLanguage: Locale, quality: Double? = nil) {
let langCode = acceptLanguage.identifier.replacingOccurrences(of: "_", with: "-")
if let qualityDesc = quality.flatMap({ String(format: "%.1f", min(0, max ($0, 1))) }) {
self.addValue("\(langCode); q=\(qualityDesc)", forHTTPHeaderField: "Accept-Language")
} else {
self.addValue(langCode, forHTTPHeaderField: "Accept-Language")
}
}
mutating func setValue(rangeWithOffset offset: Int64, length: Int) {
if length > 0 {
self.setValue("bytes=\(offset)-\(offset + Int64(length) - 1)", forHTTPHeaderField: "Range")
} else if offset > 0 && length < 0 {
self.setValue("bytes=\(offset)-", forHTTPHeaderField: "Range")
}
}
mutating func setValue(range: Range<Int>) {
let range = max(0, range.lowerBound)..<range.upperBound
if range.upperBound < Int.max && range.count > 0 {
self.setValue("bytes=\(range.lowerBound)-\(range.upperBound - 1)", forHTTPHeaderField: "Range")
} else if range.lowerBound > 0 {
self.setValue("bytes=\(range.lowerBound)-", forHTTPHeaderField: "Range")
}
}
mutating func setValue(contentType: ContentMIMEType, charset: String.Encoding? = nil) {
var parameter = ""
if let charset = charset {
let cfEncoding = CFStringConvertNSStringEncodingToEncoding(charset.rawValue)
if let charsetString = CFStringConvertEncodingToIANACharSetName(cfEncoding) as String? {
parameter = ";charset=" + charsetString
}
}
self.setValue(contentType.rawValue + parameter, forHTTPHeaderField: "Content-Type")
}
mutating func setValue(dropboxArgKey requestDictionary: [String: AnyObject]) {
guard let jsonData = try? JSONSerialization.data(withJSONObject: requestDictionary, options: []) else {
return
}
guard var jsonString = String(data: jsonData, encoding: .utf8) else { return }
jsonString = jsonString.asciiEscaped().replacingOccurrences(of: "\\/", with: "/")
self.setValue(jsonString, forHTTPHeaderField: "Dropbox-API-Arg")
}
}
internal extension CharacterSet {
static let filePathAllowed = CharacterSet.urlPathAllowed.subtracting(CharacterSet(charactersIn: ":"))
}
internal extension Data {
internal var isPDF: Bool {
return self.count > 4 && self.scanString(length: 4, using: .ascii) == "%PDF"
}
init? (jsonDictionary dictionary: [String: AnyObject]) {
guard let data = try? JSONSerialization.data(withJSONObject: dictionary, options: []) else {
return nil
}
self = data
}
func deserializeJSON() -> [String: AnyObject]? {
if let dic = try? JSONSerialization.jsonObject(with: self, options: []) as? [String: AnyObject] {
return dic
}
return nil
}
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, using encoding: String.Encoding = .utf8) -> 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()
}
}
internal extension String {
init? (jsonDictionary: [String: AnyObject]) {
guard let data = Data(jsonDictionary: jsonDictionary) else {
return nil
}
self.init(data: data, encoding: .utf8)
}
func deserializeJSON(using encoding: String.Encoding = .utf8) -> [String: AnyObject]? {
guard let data = self.data(using: encoding) else {
return nil
}
return data.deserializeJSON()
}
func asciiEscaped() -> String {
var res = ""
for char in self.unicodeScalars {
let substring = String(char)
if substring.canBeConverted(to: .ascii) {
res.append(substring)
} else {
res = res.appendingFormat("\\u%04x", char.value)
}
}
return res
}
}
#if swift(>=4.0)
#else
extension String {
var count: Int {
return self.characters.count
}
}
#endif
internal extension TimeInterval {
internal var formatshort: String {
var result = "0:00"
if self < TimeInterval(Int32.max) {
result = ""
var time = DateComponents()
time.hour = Int(self / 3600)
time.minute = Int((self.truncatingRemainder(dividingBy: 3600)) / 60)
time.second = Int(self.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
}
}
public extension Date {
/// Date formats used commonly in internet messaging defined by various RFCs.
public enum RFCStandards: String {
/// Date format defined by usenet, commonly used in old implementations.
case rfc850 = "EEEE',' dd'-'MMM'-'yy HH':'mm':'ss z"
/// Date format defined by RFC 1132 for http.
case rfc1123 = "EEE',' dd' 'MMM' 'yyyy HH':'mm':'ss z"
/// Date format defined by ISO 8601, also defined in RFC 3339. Used by Dropbox.
case iso8601 = "yyyy'-'MM'-'dd'T'HH':'mm':'ssZ"
/// Date string returned by asctime() function.
case asctime = "EEE MMM d HH':'mm':'ss yyyy"
/// Equivalent to and defined by RFC 1123.
public static let http = RFCStandards.rfc1123
/// Equivalent to and defined by ISO 8610.
public static let rfc3339 = RFCStandards.iso8601
/// Equivalent to and defined by RFC 850.
public static let usenet = RFCStandards.rfc850
// Sorted by commonness
fileprivate static let allValues: [RFCStandards] = [.rfc1123, .rfc850, .iso8601, .asctime]
}
/// Checks date string against various RFC standards and returns `Date`.
public init?(rfcString: String) {
let dateFor: DateFormatter = DateFormatter()
dateFor.locale = Locale(identifier: "en_US")
for standard in RFCStandards.allValues {
dateFor.dateFormat = standard.rawValue
if let date = dateFor.date(from: rfcString) {
self = date
return
}
}
return nil
}
/// Formats date according to RFCs standard.
public func format(with standard: RFCStandards, locale: Locale? = nil, timeZone: TimeZone? = nil) -> String {
let fm = DateFormatter()
fm.dateFormat = standard.rawValue
fm.timeZone = timeZone ?? TimeZone(identifier: "UTC")
fm.locale = locale ?? Locale(identifier: "en_US_POSIX")
return fm.string(from: self)
}
}
internal extension NSPredicate {
func findValue(forKey key: String?, operator op: NSComparisonPredicate.Operator? = nil) -> Any? {
let val = findAllValues(forKey: key).lazy.filter { (op == nil || $0.operator == op!) && !$0.not }
return val.first?.value
}
func findAllValues(forKey key: String?) -> [(value: Any, operator: NSComparisonPredicate.Operator, not: Bool)] {
if let cQuery = self as? NSCompoundPredicate {
let find = cQuery.subpredicates.flatMap { ($0 as! NSPredicate).findAllValues(forKey: key) }
if cQuery.compoundPredicateType == .not {
return find.map { return ($0.value, $0.operator, !$0.not) }
}
return find
} else if let cQuery = self as? NSComparisonPredicate {
if cQuery.leftExpression.expressionType == .keyPath, key == nil || cQuery.leftExpression.keyPath == key!, let const = cQuery.rightExpression.constantValue {
return [(value: const, operator: cQuery.predicateOperatorType, false)]
}
if cQuery.rightExpression.expressionType == .keyPath, key == nil || cQuery.rightExpression.keyPath == key!, let const = cQuery.leftExpression.constantValue {
return [(value: const, operator: cQuery.predicateOperatorType, false)]
}
return []
} else {
return []
}
}
}
func ~=<T>(pattern: (T) -> Bool, value: T) -> Bool {
return pattern(value)
}
func hasPrefix(_ prefix: String) -> (_ value: String) -> Bool {
return { (value: String) -> Bool in
value.hasPrefix(prefix)
}
}
func hasSuffix(_ suffix: String) -> (_ value: String) -> Bool {
return { (value: String) -> Bool in
value.hasSuffix(suffix)
}
}
+445
View File
@@ -0,0 +1,445 @@
// Originally based on CryptoSwift by Marcin Krzyżanowski <marcin.krzyzanowski@gmail.com>
// Copyright (C) 2014 Marcin Krzyżanowski <marcin.krzyzanowski@gmail.com>
// This software is provided 'as-is', without any express or implied warranty.
//
// In no event will the authors be held liable for any damages arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
//
// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required.
// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
// - This notice may not be removed or altered from any source or binary distribution.
import Foundation
protocol SHA2Variant {
static var size: Int { get }
static var h: [UInt64] { get }
static var k: [UInt64] { get }
static func resultingArray<T>(_ hh:[T]) -> ArraySlice<T>
static func calculate(_ message: [UInt8]) -> [UInt8]
}
protocol SHA2Variant32: SHA2Variant { }
protocol SHA2Variant64: SHA2Variant { }
extension SHA2Variant32 {
static var size: Int {
return 64
}
static var k: [UInt64] {
return [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2]
}
// codebeat:disable[ABC]
static func calculate(_ message: [UInt8]) -> [UInt8] {
var tmpMessage = message
let len = Self.size
// Step 1. Append Padding Bits
tmpMessage.append(0x80) // append one bit (UInt8 with one bit) to message
// append "0" bit until message length in bits 448 (mod 512)
var msgLength = tmpMessage.count
var counter = 0
while msgLength % len != (len - 8) {
counter += 1
msgLength += 1
}
tmpMessage.append(contentsOf: [UInt8](repeating: 0, count: counter))
// hash values
var hh: [UInt32] = Self.h.map { UInt32($0) }
let k = Self.k
// append message length, in a 64-bit big-endian integer. So now the message length is a multiple of 512 bits.
tmpMessage.append(contentsOf: arrayOfBytes(message.count * 8, length: 64 / 8))
// Process the message in successive 512-bit chunks:
let chunkSizeBytes = 512 / 8 // 64
for chunk in BytesSequence(chunkSize: chunkSizeBytes, data: tmpMessage) {
// break chunk into sixteen 32-bit words M[j], 0 j 15, big-endian
// Extend the sixteen 32-bit words into sixty-four 32-bit words:
var M:[UInt32] = [UInt32](repeating: 0, count: k.count)
for x in 0..<M.count {
switch (x) {
case 0...15:
let start: Int = chunk.startIndex + (x * MemoryLayout.size(ofValue: M[x]))
let end: Int = start + MemoryLayout.size(ofValue: M[x])
let le = toUInt32Array(chunk[start..<end])[0]
M[x] = le.bigEndian
break
default:
let s0 = rotateRight(M[x-15], n: 7) ^ rotateRight(M[x-15], n: 18) ^ (M[x-15] >> 3) //FIXME: n
let s1 = rotateRight(M[x-2], n: 17) ^ rotateRight(M[x-2], n: 19) ^ (M[x-2] >> 10)
let s2 = M[x-16]
let s3 = M[x-7]
M[x] = s2 &+ s0 &+ s3 &+ s1
break
}
}
var A = hh[0], B = hh[1], C = hh[2], D = hh[3], E = hh[4], F = hh[5], G = hh[6], H = hh[7]
// Main loop
for j in 0..<k.count {
let s0 = rotateRight(A,n: 2) ^ rotateRight(A,n: 13) ^ rotateRight(A,n: 22)
let maj = (A & B) ^ (A & C) ^ (B & C)
let t2 = s0 &+ maj
let s1 = rotateRight(E,n: 6) ^ rotateRight(E,n: 11) ^ rotateRight(E,n: 25)
let ch = (E & F) ^ ((~E) & G)
let t1 = H &+ s1 &+ ch &+ UInt32(k[j]) &+ M[j]
H = G; G = F; F = E; E = D &+ t1
D = C; C = B; B = A; A = t1 &+ t2
}
hh[0] = (hh[0] &+ A)
hh[1] = (hh[1] &+ B)
hh[2] = (hh[2] &+ C)
hh[3] = (hh[3] &+ D)
hh[4] = (hh[4] &+ E)
hh[5] = (hh[5] &+ F)
hh[6] = (hh[6] &+ G)
hh[7] = (hh[7] &+ H)
}
// Produce the final hash value (big-endian) as a 160 bit number:
var result = [UInt8]()
result.reserveCapacity(hh.count / 4)
Self.resultingArray(hh).forEach {
let item = $0.bigEndian
#if swift(>=4.0)
result.append(UInt8(truncatingIfNeeded: item))
result.append(UInt8(truncatingIfNeeded: item >> 8))
result.append(UInt8(truncatingIfNeeded: item >> 16))
result.append(UInt8(truncatingIfNeeded: item >> 24))
#else
result.append(UInt8(item))
result.append(UInt8((item >> 8) & 0xFF))
result.append(UInt8((item >> 16) & 0xFF))
result.append(UInt8((item >> 24) & 0xFF))
#endif
}
return result
}
// codebeat:enable[ABC]
}
extension SHA2Variant64 {
static var size: Int {
return 128
}
static var k: [UInt64] {
return [0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc, 0x3956c25bf348b538,
0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118, 0xd807aa98a3030242, 0x12835b0145706fbe,
0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2, 0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235,
0xc19bf174cf692694, 0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65,
0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5, 0x983e5152ee66dfab,
0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4, 0xc6e00bf33da88fc2, 0xd5a79147930aa725,
0x06ca6351e003826f, 0x142929670a0e6e70, 0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed,
0x53380d139d95b3df, 0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b,
0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30, 0xd192e819d6ef5218,
0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8, 0x19a4c116b8d2d0c8, 0x1e376c085141ab53,
0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8, 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373,
0x682e6ff3d6b2b8a3, 0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec,
0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b, 0xca273eceea26619c,
0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, 0x06f067aa72176fba, 0x0a637dc5a2c898a6,
0x113f9804bef90dae, 0x1b710b35131c471b, 0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc,
0x431d67c49c100d4c, 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817]
}
// codebeat:disable[ABC]
static func calculate(_ message: [UInt8]) -> [UInt8] {
var tmpMessage = message
let len = Self.size
// Step 1. Append Padding Bits
tmpMessage.append(0x80) // append one bit (UInt8 with one bit) to message
// append "0" bit until message length in bits 448 (mod 512)
var msgLength = tmpMessage.count
var counter = 0
while msgLength % len != (len - 8) {
counter += 1
msgLength += 1
}
tmpMessage += [UInt8](repeating: 0, count: counter)
// hash values
var hh: [UInt64] = Self.h
let k = Self.k
// append message length, in a 64-bit big-endian integer. So now the message length is a multiple of 512 bits.
tmpMessage += arrayOfBytes(message.count * 8, length: 64 / 8)
// Process the message in successive 1024-bit chunks:
let chunkSizeBytes = 1024 / 8 // 128
for chunk in BytesSequence(chunkSize: chunkSizeBytes, data: tmpMessage) {
// break chunk into sixteen 64-bit words M[j], 0 j 15, big-endian
// Extend the sixteen 64-bit words into eighty 64-bit words:
var M = [UInt64](repeating: 0, count: k.count)
for x in 0..<M.count {
switch (x) {
case 0...15:
let start = chunk.startIndex + (x * MemoryLayout.size(ofValue: M[x]))
let end = start + MemoryLayout.size(ofValue: M[x])
let le = toUInt64Array(chunk[start..<end])[0]
M[x] = le.bigEndian
break
default:
let s0 = rotateRight(M[x-15], n: 1) ^ rotateRight(M[x-15], n: 8) ^ (M[x-15] >> 7)
let s1 = rotateRight(M[x-2], n: 19) ^ rotateRight(M[x-2], n: 61) ^ (M[x-2] >> 6)
let s2 = M[x-16]
let s3 = M[x-7]
M[x] = s2 &+ s0 &+ s3 &+ s1
break
}
}
var A = hh[0], B = hh[1], C = hh[2], D = hh[3], E = hh[4], F = hh[5], G = hh[6], H = hh[7]
// Main loop
for j in 0..<k.count {
let s0 = rotateRight(A,n: 28) ^ rotateRight(A,n: 34) ^ rotateRight(A,n: 39) //FIXME: n:
let maj = (A & B) ^ (A & C) ^ (B & C)
let t2 = s0 &+ maj
let s1 = rotateRight(E,n: 14) ^ rotateRight(E,n: 18) ^ rotateRight(E,n: 41)
let ch = (E & F) ^ ((~E) & G)
let t1 = H &+ s1 &+ ch &+ k[j] &+ UInt64(M[j])
H = G; G = F; F = E; E = D &+ t1
D = C; C = B; B = A; A = t1 &+ t2
}
hh[0] = (hh[0] &+ A)
hh[1] = (hh[1] &+ B)
hh[2] = (hh[2] &+ C)
hh[3] = (hh[3] &+ D)
hh[4] = (hh[4] &+ E)
hh[5] = (hh[5] &+ F)
hh[6] = (hh[6] &+ G)
hh[7] = (hh[7] &+ H)
}
// Produce the final hash value (big-endian)
var result = [UInt8]()
result.reserveCapacity(hh.count / 4)
Self.resultingArray(hh).forEach {
let item = $0.bigEndian
var partialResult = [UInt8]()
partialResult.reserveCapacity(8)
for i in 0..<8 {
let shift = UInt64(8 * i)
partialResult.append(UInt8((item >> shift) & 0xff))
}
result += partialResult
}
return result
}
// codebeat:enable[ABC]
}
final class SHA256 : SHA2Variant32 {
static let h: [UInt64] = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19]
static func resultingArray<T>(_ hh: [T]) -> ArraySlice<T> {
return ArraySlice(hh)
}
}
final class SHA384 : SHA2Variant64 {
static let h: [UInt64] = [0xcbbb9d5dc1059ed8, 0x629a292a367cd507, 0x9159015a3070dd17, 0x152fecd8f70e5939, 0x67332667ffc00b31, 0x8eb44a8768581511, 0xdb0c2e0d64f98fa7, 0x47b5481dbefa4fa4]
public static func resultingArray<T>(_ hh: [T]) -> ArraySlice<T> {
return hh[0..<6]
}
}
final class SHA512 : SHA2Variant64 {
static let h: [UInt64] = [0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, 0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179]
static func resultingArray<T>(_ hh: [T]) -> ArraySlice<T> {
return ArraySlice(hh)
}
}
final class SHA2<Variant: SHA2Variant> {
static var size: Int {
return Variant.size
}
static func calculate(_ message: [UInt8]) -> [UInt8] {
return Variant.calculate(message)
}
}
final class HMAC<Variant: SHA2Variant> {
public static func authenticate(message:[UInt8], withKey key: [UInt8]) -> [UInt8] {
var key = key
if (key.count > Variant.size) {
key = Variant.calculate(key)
}
if (key.count < Variant.size) { // keys shorter than blocksize are zero-padded
key = key + [UInt8](repeating: 0, count: Variant.size - key.count)
}
var opad = [UInt8](repeating: 0x5c, count: Variant.size)
for (idx, _) in key.enumerated() {
opad[idx] = key[idx] ^ opad[idx]
}
var ipad = [UInt8](repeating: 0x36, count: Variant.size)
for (idx, _) in key.enumerated() {
ipad[idx] = key[idx] ^ ipad[idx]
}
let ipadAndMessageHash = Variant.calculate(ipad + message)
let finalHash = Variant.calculate(opad + ipadAndMessageHash);
return finalHash
}
static func authenticate(message: String, withKey key: [UInt8]) -> [UInt8] {
return authenticate(message: [UInt8](message.utf8), withKey: key)
}
static func authenticate(message: Data, withKey key: Data) -> Data {
return Data(bytes: authenticate(message: Array(message), withKey: Array(key)))
}
static func authenticate(message: String, withKey key: Data) -> Data {
return Data(bytes: authenticate(message: [UInt8](message.utf8), withKey: Array(key)))
}
}
fileprivate struct BytesSequence: Sequence {
let chunkSize: Int
let data: [UInt8]
init(chunkSize: Int, data: [UInt8]) {
self.chunkSize = chunkSize
self.data = data
}
func makeIterator() -> AnyIterator<ArraySlice<UInt8>> {
var offset:Int = 0
return AnyIterator {
let end = Swift.min(self.chunkSize, self.data.count - offset)
let result = self.data[offset..<offset + end]
offset += result.count
return result.count > 0 ? result : nil
}
}
}
fileprivate func rotateRight(_ x:UInt16, n:UInt16) -> UInt16 {
return (x >> n) | (x << (16 - n))
}
fileprivate func rotateRight(_ x:UInt32, n:UInt32) -> UInt32 {
return (x >> n) | (x << (32 - n))
}
fileprivate func rotateRight(_ x:UInt64, n:UInt64) -> UInt64 {
return ((x >> n) | (x << (64 - n)))
}
fileprivate func toUInt32Array(_ slice: ArraySlice<UInt8>) -> Array<UInt32> {
var result = Array<UInt32>()
result.reserveCapacity(16)
for idx in stride(from: slice.startIndex, to: slice.endIndex, by: MemoryLayout<UInt32>.size) {
let val1:UInt32 = (UInt32(slice[idx.advanced(by: 3)]) << 24)
let val2:UInt32 = (UInt32(slice[idx.advanced(by: 2)]) << 16)
let val3:UInt32 = (UInt32(slice[idx.advanced(by: 1)]) << 8)
let val4:UInt32 = UInt32(slice[idx])
let val:UInt32 = val1 | val2 | val3 | val4
result.append(val)
}
return result
}
fileprivate func toUInt64Array(_ slice: ArraySlice<UInt8>) -> Array<UInt64> {
var result = Array<UInt64>()
result.reserveCapacity(32)
for idx in stride(from: slice.startIndex, to: slice.endIndex, by: MemoryLayout<UInt64>.size) {
var val:UInt64 = 0
val |= UInt64(slice[idx.advanced(by: 7)]) << 56
val |= UInt64(slice[idx.advanced(by: 6)]) << 48
val |= UInt64(slice[idx.advanced(by: 5)]) << 40
val |= UInt64(slice[idx.advanced(by: 4)]) << 32
val |= UInt64(slice[idx.advanced(by: 3)]) << 24
val |= UInt64(slice[idx.advanced(by: 2)]) << 16
val |= UInt64(slice[idx.advanced(by: 1)]) << 8
val |= UInt64(slice[idx.advanced(by: 0)]) << 0
result.append(val)
}
return result
}
fileprivate func arrayOfBytes<T>(_ value:T, length:Int? = nil) -> [UInt8] {
let totalBytes = length ?? MemoryLayout<T>.size
let valuePointer = UnsafeMutablePointer<T>.allocate(capacity: 1)
valuePointer.pointee = value
let bytesPointer = UnsafeMutableRawPointer(valuePointer).assumingMemoryBound(to: UInt8.self)
var bytes = [UInt8](repeating: 0, count: totalBytes)
for j in 0..<min(MemoryLayout<T>.size,totalBytes) {
bytes[totalBytes - 1 - j] = (bytesPointer + j).pointee
}
valuePointer.deinitialize()
valuePointer.deallocate(capacity: 1)
return bytes
}
public extension String {
public func fp_sha256() -> [UInt8] {
return SHA2<SHA256>.calculate([UInt8](self.utf8))
}
public func fp_sha384() -> [UInt8] {
return SHA2<SHA384>.calculate([UInt8](self.utf8))
}
public func fp_sha512() -> [UInt8] {
return SHA2<SHA512>.calculate([UInt8](self.utf8))
}
}
public extension Data {
public func fp_sha256() -> [UInt8] {
return SHA2<SHA256>.calculate(Array(self))
}
public func fp_sha384() -> [UInt8] {
return SHA2<SHA384>.calculate(Array(self))
}
public func fp_sha512() -> [UInt8] {
return SHA2<SHA512>.calculate(Array(self))
}
}
+745
View File
@@ -0,0 +1,745 @@
//
// FPSStreamTask.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2016 Mousavian. Distributed under MIT license.
//
import Foundation
private var lasttaskIdAssociated = 1_000_000_000
// codebeat:disable[TOTAL_LOC,TOO_MANY_IVARS]
/// This class is a replica of NSURLSessionStreamTask with same api for iOS 7/8
/// while it can actually fallback to NSURLSessionStreamTask in iOS 9.
public class FileProviderStreamTask: URLSessionTask, StreamDelegate {
fileprivate var inputStream: InputStream?
fileprivate var outputStream: OutputStream?
fileprivate var operation_queue: OperationQueue!
internal var _underlyingSession: URLSession
fileprivate var streamDelegate: FPSStreamDelegate? {
return (_underlyingSession.delegate as? FPSStreamDelegate)
}
fileprivate var _taskIdentifier: Int
fileprivate var _taskDescription: String?
/// Force using `URLSessionStreamTask` for iOS 9 and later
public let useURLSession: Bool
@available(iOS 9.0, macOS 10.11, *)
fileprivate static var streamTasks = [Int: URLSessionStreamTask]()
@available(iOS 9.0, macOS 10.11, *)
internal var _underlyingTask: URLSessionStreamTask? {
return FileProviderStreamTask.streamTasks[_taskIdentifier]
}
/**
* An identifier uniquely identifies the task within a given session.
*
* This value is unique only within the context of a single session;
* tasks in other sessions may have the same `taskIdentifier` value.
*/
open override var taskIdentifier: Int {
if #available(iOS 9.0, macOS 10.11, *) {
if self.useURLSession {
return _underlyingTask!.taskIdentifier
}
}
return _taskIdentifier
}
/// An app-provided description of the current task.
///
/// This value may be nil. It is intended to contain human-readable strings that you can
/// then display to the user as part of your apps user interface.
open override var taskDescription: String? {
get {
if #available(iOS 9.0, macOS 10.11, *) {
if self.useURLSession {
return _underlyingTask!.taskDescription
}
}
return _taskDescription
}
@objc(setTaskDescription:)
set {
if #available(iOS 9.0, macOS 10.11, *) {
if self.useURLSession {
_underlyingTask!.taskDescription = newValue
return
}
}
_taskDescription = newValue
}
}
fileprivate var _state: URLSessionTask.State = .suspended
/**
* The current state of the taskactive, suspended, in the process
* of being canceled, or completed.
*/
override open var state: URLSessionTask.State {
if #available(iOS 9.0, macOS 10.11, *) {
if self.useURLSession {
return _underlyingTask!.state
}
}
return _state
}
/**
* The original request object passed when the task was created.
* This value is typically the same as the currently active request (`currentRequest`)
* except when the server has responded to the initial request with a redirect to a different URL.
*/
override open var originalRequest: URLRequest? {
if #available(iOS 9.0, macOS 10.11, *) {
if self.useURLSession {
return _underlyingTask!.originalRequest
}
}
return nil
}
/**
* The URL request object currently being handled by the task.
* This value is typically the same as the initial request (`originalRequest`)
* except when the server has responded to the initial request with a redirect to a different URL.
*/
override open var currentRequest: URLRequest? {
if #available(iOS 9.0, macOS 10.11, *) {
return _underlyingTask!.currentRequest
} else {
return nil
}
}
fileprivate var _countOfBytesSent: Int64 = 0 {
willSet {
for observer in observers where observer.keyPath == "countOfBytesSent" {
observer.observer.observeValue(forKeyPath: observer.keyPath, of: self, change: [.oldKey: _countOfBytesSent, .oldKey: newValue], context: observer.context)
}
}
didSet {
for observer in observers where observer.keyPath == "countOfBytesSent" {
observer.observer.observeValue(forKeyPath: observer.keyPath, of: self, change: [.oldKey: oldValue, .oldKey: _countOfBytesSent], context: observer.context)
}
}
}
fileprivate var _countOfBytesRecieved: Int64 = 0 {
willSet {
for observer in observers where observer.keyPath == "countOfBytesRecieved" {
observer.observer.observeValue(forKeyPath: observer.keyPath, of: self, change: [.oldKey: _countOfBytesRecieved, .oldKey: newValue], context: observer.context)
}
}
didSet {
for observer in observers where observer.keyPath == "countOfBytesRecieved" {
observer.observer.observeValue(forKeyPath: observer.keyPath, of: self, change: [.oldKey: oldValue, .oldKey: _countOfBytesRecieved], context: observer.context)
}
}
}
/**
* The number of bytes that the task has sent to the server in the request body.
*
* This byte count includes only the length of the request body itself, not the request headers.
*
* To be notified when this value changes, implement the
* `urlSession(_:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:)` delegate method.
*/
override open var countOfBytesSent: Int64 {
if #available(iOS 9.0, macOS 10.11, *) {
if self.useURLSession {
return _underlyingTask!.countOfBytesSent
}
}
return _countOfBytesSent
}
/**
* The number of bytes that the task has received from the server in the response body.
*
* To be notified when this value changes, implement the `urlSession(_:dataTask:didReceive:)` delegate method (for data and upload tasks)
* or the `urlSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)` method (for download tasks).
*/
override open var countOfBytesReceived: Int64 {
if #available(iOS 9.0, macOS 10.11, *) {
if self.useURLSession {
return _underlyingTask!.countOfBytesReceived
}
}
return _countOfBytesRecieved
}
/**
* The number of bytes that the task expects to send in the request body.
*
* The `URL` loading system can determine the length of the upload data in three ways:
* - From the length of the `NSData` object provided as the upload body.
* - From the length of the file on disk provided as the upload body of an upload task (not a download task).
* - From the `Content-Length` in the request object, if you explicitly set it.
*
* Otherwise, the value is `NSURLSessionTransferSizeUnknown` (`-1`) if you provided a stream or body data object, or zero (`0`) if you did not.
*/
override open var countOfBytesExpectedToSend: Int64 {
if #available(iOS 9.0, macOS 10.11, *) {
return _underlyingTask!.countOfBytesExpectedToSend
} else {
return Int64(dataToBeSent.count)
}
}
/**
* The number of bytes that the task expects to receive in the response body.
*
* This value is determined based on the `Content-Length` header received from the server.
* If that header is absent, the value is `NSURLSessionTransferSizeUnknown`.
*/
override open var countOfBytesExpectedToReceive: Int64 {
if #available(iOS 9.0, macOS 10.11, *) {
if self.useURLSession {
return _underlyingTask!.countOfBytesExpectedToReceive
}
}
return Int64(dataReceived.count)
}
var observers: [(keyPath: String, observer: NSObject, context: UnsafeMutableRawPointer?)] = []
public override func addObserver(_ observer: NSObject, forKeyPath keyPath: String, options: NSKeyValueObservingOptions = [], context: UnsafeMutableRawPointer?) {
if #available(iOS 9.0, macOS 10.11, *) {
if self.useURLSession {
self._underlyingTask?.addObserver(observer, forKeyPath: keyPath, options: options, context: context)
return
}
}
switch keyPath {
case #keyPath(countOfBytesSent):
fallthrough
case #keyPath(countOfBytesReceived):
fallthrough
case #keyPath(countOfBytesExpectedToSend):
fallthrough
case #keyPath(countOfBytesExpectedToReceive):
observers.append((keyPath: keyPath, observer: observer, context: context))
default:
break
}
super.addObserver(observer, forKeyPath: keyPath, options: options, context: context)
}
public override func removeObserver(_ observer: NSObject, forKeyPath keyPath: String) {
var newObservers: [(keyPath: String, observer: NSObject, context: UnsafeMutableRawPointer?)] = []
for observer in observers where observer.keyPath != keyPath {
newObservers.append(observer)
}
self.observers = newObservers
super.removeObserver(observer, forKeyPath: keyPath)
}
public override func removeObserver(_ observer: NSObject, forKeyPath keyPath: String, context: UnsafeMutableRawPointer?) {
var newObservers: [(keyPath: String, observer: NSObject, context: UnsafeMutableRawPointer?)] = []
for observer in observers where observer.keyPath != keyPath || observer.context != context {
newObservers.append(observer)
}
self.observers = newObservers
super.removeObserver(observer, forKeyPath: keyPath, context: context)
}
override public init() {
fatalError("Use NSURLSession.fpstreamTask() method")
}
fileprivate var host: (hostname: String, port: Int)?
fileprivate var service: NetService?
internal static let defaultUseURLSession = false
internal init(session: URLSession, host: String, port: Int, useURLSession: Bool = defaultUseURLSession) {
self._underlyingSession = session
self.useURLSession = useURLSession
if #available(iOS 9.0, macOS 10.11, *) {
if useURLSession {
let task = session.streamTask(withHostName: host, port: port)
self._taskIdentifier = task.taskIdentifier
FileProviderStreamTask.streamTasks[_taskIdentifier] = task
return
}
}
lasttaskIdAssociated += 1
self._taskIdentifier = lasttaskIdAssociated
self.host = (host, port)
self.operation_queue = OperationQueue()
self.operation_queue.name = "FileProviderStreamTask"
self.operation_queue.maxConcurrentOperationCount = 1
}
internal init(session: URLSession, netService: NetService, useURLSession: Bool = defaultUseURLSession) {
self._underlyingSession = session
self.useURLSession = useURLSession
if #available(iOS 9.0, macOS 10.11, *) {
if useURLSession {
let task = session.streamTask(with: netService)
self._taskIdentifier = task.taskIdentifier
FileProviderStreamTask.streamTasks[_taskIdentifier] = task
return
}
}
lasttaskIdAssociated += 1
self._taskIdentifier = lasttaskIdAssociated
self.service = netService
self.operation_queue = OperationQueue()
self.operation_queue.name = "FileProviderStreamTask"
self.operation_queue.maxConcurrentOperationCount = 1
}
/**
* Cancels the task.
*
* This method returns immediately, marking the task as being canceled. Once a task is marked as being canceled,
* `urlSession(_:task:didCompleteWithError:)` will be sent to the task delegate, passing an error
* in the domain NSURLErrorDomain with the code `NSURLErrorCancelled`. A task may, under some circumstances,
* send messages to its delegate before the cancelation is acknowledged.
*
* This method may be called on a task that is suspended.
*/
override open func cancel() {
if #available(iOS 9.0, macOS 10.11, *) {
if self.useURLSession {
_underlyingTask!.cancel()
return
}
}
self._state = .canceling
//inputStream?.setValue(kCFBooleanTrue, forKey: kCFStreamPropertyShouldCloseNativeSocket as String)
//outputStream?.setValue(kCFBooleanTrue, forKey: kCFStreamPropertyShouldCloseNativeSocket as String)
self.inputStream?.close()
self.outputStream?.close()
self.inputStream?.remove(from: RunLoop.main, forMode: .defaultRunLoopMode)
self.outputStream?.remove(from: RunLoop.main, forMode: .defaultRunLoopMode)
self.inputStream?.delegate = nil
self.outputStream?.delegate = nil
self.inputStream = nil
self.outputStream = nil
self._state = .completed
self._countOfBytesSent = 0
self._countOfBytesRecieved = 0
}
var _error: Error? = nil
/**
* An error object that indicates why the task failed.
*
* This value is `NULL` if the task is still active or if the transfer completed successfully.
*/
override open var error: Error? {
if #available(iOS 9.0, macOS 10.11, *) {
if useURLSession {
return _underlyingTask!.error
}
}
return _error
}
/**
* Temporarily suspends a task.
*
* A task, while suspended, produces no network traffic and is not subject to timeouts.
* A download task can continue transferring data at a later time.
* All other tasks must start over when resumed.
*/
override open func suspend() {
if #available(iOS 9.0, macOS 10.11, *) {
if self.useURLSession {
_underlyingTask!.suspend()
return
}
}
self._state = .suspended
self.operation_queue.isSuspended = true
}
// Resumes the task, if it is suspended.
override open func resume() {
if #available(iOS 9.0, macOS 10.11, *) {
if self.useURLSession {
_underlyingTask!.resume()
return
}
}
var readStream : Unmanaged<CFReadStream>?
var writeStream : Unmanaged<CFWriteStream>?
if inputStream == nil || outputStream == nil {
if let host = host {
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, host.hostname as CFString, UInt32(host.port), &readStream, &writeStream)
} else if let service = service {
let cfnetService = CFNetServiceCreate(kCFAllocatorDefault, service.domain as CFString, service.type as CFString, service.name as CFString, Int32(service.port))
CFStreamCreatePairWithSocketToNetService(kCFAllocatorDefault, cfnetService.takeRetainedValue(), &readStream, &writeStream)
}
inputStream = readStream?.takeRetainedValue()
outputStream = writeStream?.takeRetainedValue()
guard let inputStream = inputStream, let outputStream = outputStream else {
return
}
streamDelegate?.urlSession?(self._underlyingSession, streamTask: self, didBecome: inputStream, outputStream: outputStream)
}
guard let inputStream = inputStream, let outputStream = outputStream else {
return
}
inputStream.delegate = self
outputStream.delegate = self
operation_queue.addOperation {
inputStream.schedule(in: RunLoop.main, forMode: .defaultRunLoopMode)
outputStream.schedule(in: RunLoop.main, forMode: .defaultRunLoopMode)
}
inputStream.open()
outputStream.open()
operation_queue.isSuspended = false
_state = .running
}
fileprivate var dataToBeSent: Data = Data()
fileprivate var dataReceived: Data = Data()
/**
* Asynchronously reads a number of bytes from the stream, and calls a handler upon completion.
*
* - Parameter minBytes: The minimum number of bytes to read.
* - ParametermaxBytes: The maximum number of bytes to read.
* - Parameter timeout: A timeout for reading bytes. If the read is not completed within the specified interval,
* the read is canceled and the completionHandler is called with an error. Pass `0` to prevent a read from timing out.
* - Parameter completionHandler: The completion handler to call when all bytes are read, or an error occurs.
* This handler is executed on the delegate queue. This completion handler takes the following parameters:
* - Parameter data: The data read from the stream.
* - Parameter atEOF: Whether or not the stream reached end-of-file (`EOF`), such that no more data can be read.
* - Parameter error: An error object that indicates why the read failed, or `nil` if the read was successful.
*/
open func readData(ofMinLength minBytes: Int, maxLength maxBytes: Int, timeout: TimeInterval, completionHandler: @escaping (_ data: Data?, _ atEOF: Bool, _ error :Error?) -> Void) {
if #available(iOS 9.0, macOS 10.11, *) {
if self.useURLSession {
_underlyingTask!.readData(ofMinLength: minBytes, maxLength: maxBytes, timeout: timeout, completionHandler: completionHandler)
return
}
}
guard let inputStream = inputStream else {
return
}
let expireDate = Date(timeIntervalSinceNow: timeout)
operation_queue.addOperation {
var timedOut: Bool = false
while (self.dataReceived.count == 0 || self.dataReceived.count < minBytes) && !timedOut {
Thread.sleep(forTimeInterval: 0.1)
timedOut = expireDate < Date()
}
var dR: Data?
if self.dataReceived.count > maxBytes {
let range: Range = 0..<maxBytes
dR = self.dataReceived.subdata(in: range)
self.dataReceived.removeFirst(maxBytes)
} else {
if self.dataReceived.count > 0 {
dR = self.dataReceived
self.dataReceived.removeAll(keepingCapacity: false)
}
}
let isEOF = inputStream.streamStatus == .atEnd && self.dataReceived.count == 0
completionHandler(dR, isEOF, dR == nil ? inputStream.streamError : nil)
}
}
/**
* Asynchronously writes the specified data to the stream, and calls a handler upon completion.
*
* There is no guarantee that the remote side of the stream has received all of the written bytes
* at the time that `completionHandler` is called, only that all of the data has been written to the kernel.
*
* - Parameter data: The data to be written.
* - Parameter timeout: A timeout for writing bytes. If the write is not completed within the specified interval,
* the write is canceled and the `completionHandler` is called with an error.
* Pass `0` to prevent a write from timing out.
* - Parameter completionHandler: The completion handler to call when all bytes are written, or an error occurs.
* This handler is executed on the delegate queue.
* This completion handler takes the following parameter:
* - Parameter error: An error object that indicates why the write failed, or `nil` if the write was successful.
*/
open func write(_ data: Data, timeout: TimeInterval, completionHandler: @escaping (_ error: Error?) -> Void) {
if #available(iOS 9.0, macOS 10.11, *) {
if self.useURLSession {
_underlyingTask!.write(data, timeout: timeout, completionHandler: completionHandler)
return
}
}
guard outputStream != nil else {
return
}
operation_queue.addOperation {
self.dataToBeSent.append(data)
let result = self.write(timeout: timeout, close: false)
if result < 0 {
let error = self.outputStream?.streamError ?? URLError(.cannotWriteToFile)
completionHandler(error)
} else {
completionHandler(nil)
}
}
}
/**
* Completes any already enqueued reads and writes, and then invokes the
* `urlSession(_:streamTask:didBecome:outputStream:)` delegate message.
*/
open func captureStreams() {
if #available(iOS 9.0, macOS 10.11, *) {
if self.useURLSession {
_underlyingTask!.captureStreams()
return
}
}
guard let outputStream = outputStream, let inputStream = inputStream else {
return
}
self.operation_queue.addOperation {
_=self.write(close: false)
while inputStream.streamStatus != .atEnd || outputStream.streamStatus == .writing {
Thread.sleep(forTimeInterval: 0.1)
}
self.streamDelegate?.urlSession?(self._underlyingSession, streamTask: self, didBecome: inputStream, outputStream: outputStream)
}
}
/**
* Completes any enqueued reads and writes, and then closes the write side of the underlying socket.
*
* You may continue to read data using the `readData(ofMinLength:maxLength:timeout:completionHandler:)`
* method after calling this method. Any calls to `write(_:timeout:completionHandler:)` after calling
* this method will result in an error.
*
* Because the server may continue to write bytes to the client, it is recommended that
* you continue reading until the stream reaches end-of-file (EOF).
*/
open func closeWrite() {
if #available(iOS 9.0, macOS 10.11, *) {
if self.useURLSession {
_underlyingTask!.closeWrite()
return
}
}
operation_queue.addOperation {
_ = self.write(close: true)
}
}
fileprivate func write(timeout: TimeInterval = 0, close: Bool) -> Int {
guard let outputStream = outputStream else {
return -1
}
var byteSent: Int = 0
let expireDate = Date(timeIntervalSinceNow: timeout)
while self.dataToBeSent.count > 0 && (timeout == 0 || expireDate > Date()) {
let bytesWritten = self.dataToBeSent.withUnsafeBytes {
outputStream.write($0, maxLength: self.dataToBeSent.count)
}
if bytesWritten > 0 {
let range = 0..<bytesWritten
self.dataToBeSent.replaceSubrange(range, with: Data())
byteSent += bytesWritten
} else if bytesWritten < 0 {
self._error = outputStream.streamError
return bytesWritten
}
if self.dataToBeSent.count == 0 {
break
}
}
self._countOfBytesSent += Int64(byteSent)
if close {
outputStream.close()
self.streamDelegate?.urlSession?(self._underlyingSession, writeClosedFor: self)
}
return byteSent
}
/**
* Completes any enqueued reads and writes, and then closes the read side of the underlying socket.
*
* You may continue to write data using the `write(_:timeout:completionHandler:)` method after
* calling this method. Any calls to `readData(ofMinLength:maxLength:timeout:completionHandler:)`
* after calling this method will result in an error.
*/
open func closeRead() {
if #available(iOS 9.0, macOS 10.11, *) {
if self.useURLSession {
_underlyingTask!.closeRead()
return
}
}
guard let inputStream = inputStream else {
return
}
operation_queue.addOperation {
while inputStream.streamStatus != .atEnd {
Thread.sleep(forTimeInterval: 0.1)
}
inputStream.close()
self.streamDelegate?.urlSession?(self._underlyingSession, readClosedFor: self)
}
}
/**
* Completes any enqueued reads and writes, and establishes a secure connection.
*
* Authentication callbacks are sent to the session's delegate using the
* `urlSession(_:task:didReceive:completionHandler:)` method.
*/
open func startSecureConnection() {
if #available(iOS 9.0, macOS 10.11, *) {
if self.useURLSession {
_underlyingTask!.startSecureConnection()
return
}
}
operation_queue.addOperation {
self.inputStream!.setProperty(StreamSocketSecurityLevel.negotiatedSSL.rawValue, forKey: .socketSecurityLevelKey)
self.outputStream!.setProperty(StreamSocketSecurityLevel.negotiatedSSL.rawValue, forKey: .socketSecurityLevelKey)
}
}
/**
* Completes any enqueued reads and writes, and closes the secure connection.
*/
open func stopSecureConnection() {
if #available(iOS 9.0, macOS 10.11, *) {
if self.useURLSession {
_underlyingTask!.stopSecureConnection()
return
}
}
operation_queue.addOperation {
self.inputStream!.setProperty(StreamSocketSecurityLevel.none.rawValue, forKey: .socketSecurityLevelKey)
self.outputStream!.setProperty(StreamSocketSecurityLevel.none.rawValue, forKey: .socketSecurityLevelKey)
}
}
open func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
if eventCode.contains(.errorOccurred) {
self._error = aStream.streamError
streamDelegate?.urlSession?(_underlyingSession, task: self, didCompleteWithError: error)
}
if aStream == inputStream && eventCode.contains(.hasBytesAvailable) {
while (inputStream!.hasBytesAvailable) {
var buffer = [UInt8](repeating: 0, count: 2048)
let len = inputStream!.read(&buffer, maxLength: buffer.count)
if len > 0 {
dataReceived.append(&buffer, count: len)
self._countOfBytesRecieved += Int64(len)
}
}
}
}
}
public extension URLSession {
/// Creates a bidirectional stream task to a given host and port.
func fpstreamTask(withHostName hostname: String, port: Int) -> FileProviderStreamTask {
return FileProviderStreamTask(session: self, host: hostname, port: port)
}
/**
* Creates a bidirectional stream task with an NSNetService to identify the endpoint.
* The NSNetService will be resolved before any IO completes.
*/
func fpstreamTask(withNetService service: NetService) -> FileProviderStreamTask {
return FileProviderStreamTask(session: self, netService: service)
}
}
@objc
internal protocol FPSStreamDelegate : URLSessionTaskDelegate {
/**
* Indiciates that the read side of a connection has been closed. Any
* outstanding reads complete, but future reads will immediately fail.
* This may be sent even when no reads are in progress. However, when
* this delegate message is received, there may still be bytes
* available. You only know that no more bytes are available when you
* are able to read until EOF. */
@objc optional func urlSession(_ session: URLSession, readClosedFor streamTask: FileProviderStreamTask)
/**
* Indiciates that the write side of a connection has been closed.
* Any outstanding writes complete, but future writes will immediately
* fail.
*/
@objc optional func urlSession(_ session: URLSession, writeClosedFor streamTask: FileProviderStreamTask)
/**
* A notification that the system has determined that a better route
* to the host has been detected (eg, a wi-fi interface becoming
* available.) This is a hint to the delegate that it may be
* desirable to create a new task for subsequent work. Note that
* there is no guarantee that the future task will be able to connect
* to the host, so callers should should be prepared for failure of
* reads and writes over any new interface. */
@objc optional func urlSession(_ session: URLSession, betterRouteDiscoveredFor streamTask: FileProviderStreamTask)
/**
* The given task has been completed, and unopened NSInputStream and
* NSOutputStream objects are created from the underlying network
* connection. This will only be invoked after all enqueued IO has
* completed (including any necessary handshakes.) The streamTask
* will not receive any further delegate messages.
*/
@objc optional func urlSession(_ session: URLSession, streamTask: FileProviderStreamTask, didBecome inputStream: InputStream, outputStream: OutputStream)
}
// codebeat:enable[TOTAL_LOC,TOO_MANY_IVARS]
private let ports: [String: Int] = ["http": 80, "https": 443, "smb": 445,"ftp": 21,
"telnet": 23, "pop": 110, "smtp": 25, "imap": 143]
private let securePorts: [String: Int] = ["ssh": 22, "https": 443, "smb": 445, "smtp": 465,
"ftps": 990,"telnet": 992, "imap": 993, "pop": 995]
+789
View File
@@ -0,0 +1,789 @@
//
// FTPFileProvider.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2017 Mousavian. Distributed under MIT license.
//
import Foundation
/**
Allows accessing to FTP files and directories. This provider doesn't cache or save files internally.
It's a complete reimplementation and doesn't use CFNetwork deprecated API.
*/
open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, FileProviderReadWrite {
open class var type: String { return "FTP" }
open let baseURL: URL?
/// **OBSOLETED** Current active path used in `contentsOfDirectory(path:completionHandler:)` method.
@available(*, obsoleted: 0.22, message: "This property is redundant with almost no use internally.")
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 var credential: URLCredential? {
didSet {
sessionDelegate?.credential = self.credential
}
}
open private(set) var cache: URLCache?
public var useCache: Bool
public var validatingCache: Bool
/// Determine either FTP session is in passive or active mode.
public let passiveMode: Bool
fileprivate var _session: URLSession!
internal var sessionDelegate: SessionDelegate?
public var session: URLSession {
get {
if _session == nil {
self.sessionDelegate = SessionDelegate(fileProvider: self)
let config = URLSessionConfiguration.default
_session = URLSession(configuration: config, delegate: sessionDelegate as URLSessionDelegate?, delegateQueue: self.operation_queue)
_session.sessionDescription = UUID().uuidString
initEmptySessionHandler(_session.sessionDescription!)
}
return _session
}
set {
assert(newValue.delegate is SessionDelegate, "session instances should have a SessionDelegate instance as delegate.")
_session = newValue
if _session.sessionDescription?.isEmpty ?? true {
_session.sessionDescription = UUID().uuidString
}
self.sessionDelegate = newValue.delegate as? SessionDelegate
initEmptySessionHandler(_session.sessionDescription!)
}
}
/**
Initializer for FTP provider with given username and password.
- Note: `passive` value should be set according to server settings and firewall presence.
- Parameter baseURL: a url with `ftp://hostaddress/` format.
- Parameter passive: FTP server data connection, `true` means passive connection (data connection created by client)
and `false` means active connection (data connection created by server). Default is `true` (passive mode).
- Parameter credential: a `URLCredential` object contains user and password.
- Parameter cache: A URLCache to cache downloaded files and contents. (unimplemented for FTP and should be nil)
*/
public init? (baseURL: URL, passive: Bool = true, credential: URLCredential? = nil, cache: URLCache? = nil) {
guard (baseURL.scheme ?? "ftp").lowercased().hasPrefix("ftp") else { return nil }
guard baseURL.host != nil else { return nil }
var urlComponents = URLComponents(url: baseURL, resolvingAgainstBaseURL: true)!
let defaultPort: Int = baseURL.scheme == "ftps" ? 990 : 21
urlComponents.port = urlComponents.port ?? defaultPort
urlComponents.scheme = urlComponents.scheme ?? "ftp"
urlComponents.path = urlComponents.path.hasSuffix("/") ? urlComponents.path : urlComponents.path + "/"
self.baseURL = urlComponents.url!.absoluteURL
self.passiveMode = passive
self.useCache = false
self.validatingCache = true
self.cache = cache
self.credential = credential
self.supportsRFC3659 = true
#if swift(>=3.1)
let queueLabel = "FileProvider.\(Swift.type(of: self).type)"
#else
let queueLabel = "FileProvider.\(type(of: self).type)"
#endif
dispatch_queue = DispatchQueue(label: queueLabel, attributes: .concurrent)
operation_queue = OperationQueue()
operation_queue.name = "\(queueLabel).Operation"
}
public required convenience init?(coder aDecoder: NSCoder) {
guard let baseURL = aDecoder.decodeObject(forKey: "baseURL") as? URL else { return nil }
self.init(baseURL: baseURL, passive: aDecoder.decodeBool(forKey: "passiveMode"), credential: aDecoder.decodeObject(forKey: "credential") as? URLCredential)
self.useCache = aDecoder.decodeBool(forKey: "useCache")
self.validatingCache = aDecoder.decodeBool(forKey: "validatingCache")
self.supportsRFC3659 = aDecoder.decodeBool(forKey: "supportsRFC3659")
}
public func encode(with aCoder: NSCoder) {
aCoder.encode(self.baseURL, forKey: "baseURL")
aCoder.encode(self.credential, forKey: "credential")
aCoder.encode(self.useCache, forKey: "useCache")
aCoder.encode(self.validatingCache, forKey: "validatingCache")
aCoder.encode(self.passiveMode, forKey: "passiveMode")
aCoder.encode(self.supportsRFC3659, forKey: "supportsRFC3659")
}
public static var supportsSecureCoding: Bool {
return true
}
open func copy(with zone: NSZone? = nil) -> Any {
let copy = FTPFileProvider(baseURL: self.baseURL!, credential: self.credential, cache: self.cache)!
copy.delegate = self.delegate
copy.fileOperationDelegate = self.fileOperationDelegate
copy.useCache = self.useCache
copy.validatingCache = self.validatingCache
copy.supportsRFC3659 = self.supportsRFC3659
return copy
}
deinit {
if let sessionuuid = _session?.sessionDescription {
removeSessionHandler(for: sessionuuid)
}
if fileProviderCancelTasksOnInvalidating {
_session?.invalidateAndCancel()
} else {
_session?.finishTasksAndInvalidate()
}
}
internal var supportsRFC3659: Bool
/**
Uploads files in chunk if `true`, Otherwise It will uploads entire file/data as single stream.
- Note: Due to an internal bug in `NSURLSessionStreamTask`, it must be true when using Apple's stream task,
otherwise it will occasionally throw `Assertion failed: (_writeBufferAlreadyWrittenForNextWrite == 0)`
fatal error. My implementation of `FileProviderStreamTask` doesn't have this bug.
- Note: Disabling this option will increase upload speed.
*/
public var uploadByREST: Bool = FileProviderStreamTask.defaultUseURLSession
open func contentsOfDirectory(path: String, completionHandler: @escaping ([FileObject], Error?) -> Void) {
self.contentsOfDirectory(path: path, rfc3659enabled: supportsRFC3659, completionHandler: completionHandler)
}
/**
Returns an Array of `FileObject`s identifying the the directory entries via asynchronous completion handler.
If the directory contains no entries or an error is occured, this method will return the empty array.
- Parameter path: path to target directory. If empty, root will be iterated.
- Parameter rfc3659enabled: uses MLST command instead of old LIST to get files attributes, default is `true`.
- Parameter completionHandler: a closure with result of directory entries or error.
- Parameter contents: An array of `FileObject` identifying the the directory entries.
- Parameter error: Error returned by system.
*/
open func contentsOfDirectory(path apath: String, rfc3659enabled: Bool , completionHandler: @escaping (_ contents: [FileObject], _ error: Error?) -> Void) {
let path = ftpPath(apath)
let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!)
self.ftpLogin(task) { (error) in
if let error = error {
self.dispatch_queue.async {
completionHandler([], error)
}
return
}
self.ftpList(task, of: self.ftpPath(path), useMLST: rfc3659enabled, completionHandler: { (contents, error) in
defer {
self.ftpQuit(task)
}
if let error = error {
if ((error as NSError).domain == URLError.errorDomain && (error as NSError).code == URLError.unsupportedURL.rawValue) {
self.contentsOfDirectory(path: path, rfc3659enabled: false, completionHandler: completionHandler)
return
}
self.dispatch_queue.async {
completionHandler([], error)
}
return
}
let files: [FileObject] = contents.flatMap {
rfc3659enabled ? self.parseMLST($0, in: path) : self.parseUnixList($0, in: path)
}
self.dispatch_queue.async {
completionHandler(files, nil)
}
})
}
}
open func attributesOfItem(path: String, completionHandler: @escaping (FileObject?, Error?) -> Void) {
self.attributesOfItem(path: path, rfc3659enabled: supportsRFC3659, completionHandler: completionHandler)
}
/**
Returns a `FileObject` containing the attributes of the item (file, directory, symlink, etc.) at the path in question via asynchronous completion handler.
If the directory contains no entries or an error is occured, this method will return the empty `FileObject`.
- Parameter path: path to target directory. If empty, attributes of root will be returned.
- Parameter rfc3659enabled: uses MLST command instead of old LIST to get files attributes, default is true.
- Parameter completionHandler: a closure with result of directory entries or error.
`attributes`: A `FileObject` containing the attributes of the item.
`error`: Error returned by system.
*/
open func attributesOfItem(path apath: String, rfc3659enabled: Bool, completionHandler: @escaping (_ attributes: FileObject?, _ error: Error?) -> Void) {
let path = ftpPath(apath)
let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!)
self.ftpLogin(task) { (error) in
if let error = error {
self.dispatch_queue.async {
completionHandler(nil, error)
}
return
}
let command = rfc3659enabled ? "MLST \(path)" : "LIST \(path)"
self.execute(command: command, on: task, completionHandler: { (response, error) in
defer {
self.ftpQuit(task)
}
do {
if let error = error {
throw error
}
guard let response = response, response.hasPrefix("250") || (response.hasPrefix("50") && rfc3659enabled) else {
throw self.urlError(path, code: .badServerResponse)
}
if response.hasPrefix("500") {
self.supportsRFC3659 = false
self.attributesOfItem(path: path, rfc3659enabled: false, completionHandler: completionHandler)
}
let lines = response.components(separatedBy: "\n").flatMap { $0.isEmpty ? nil : $0.trimmingCharacters(in: .whitespacesAndNewlines) }
guard lines.count > 2 else {
throw self.urlError(path, code: .badServerResponse)
}
let file: FileObject? = rfc3659enabled ? self.parseMLST(lines[1], in: path) : self.parseUnixList(lines[1], in: path)
self.dispatch_queue.async {
completionHandler(file, nil)
}
} catch {
self.dispatch_queue.async {
completionHandler(nil, error)
}
}
})
}
}
open func storageProperties(completionHandler: @escaping (_ volume: VolumeObject?) -> Void) {
dispatch_queue.async {
completionHandler(nil)
}
}
open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping (_ files: [FileObject], _ error: Error?) -> Void) -> Progress? {
let progress = Progress(totalUnitCount: -1)
if recursive {
return self.recursiveList(path: path, useMLST: true, foundItemsHandler: { items in
if let foundItemHandler = foundItemHandler {
for item in items where query.evaluate(with: item.mapPredicate()) {
foundItemHandler(item)
}
progress.totalUnitCount = Int64(items.count)
}
}, completionHandler: {files, error in
if let error = error {
completionHandler([], error)
return
}
let foundFiles = files.filter { query.evaluate(with: $0.mapPredicate()) }
completionHandler(foundFiles, nil)
})
} else {
self.contentsOfDirectory(path: path, completionHandler: { (items, error) in
if let error = error {
completionHandler([], error)
return
}
var result = [FileObject]()
for item in items where query.evaluate(with: item.mapPredicate()) {
foundItemHandler?(item)
result.append(item)
}
completionHandler(result, nil)
})
}
return progress
}
open func url(of path: String?) -> URL {
let path = path?.trimmingCharacters(in: CharacterSet(charactersIn: "/ ")).addingPercentEncoding(withAllowedCharacters: .filePathAllowed) ?? (path ?? "")
var baseUrlComponent = URLComponents(url: self.baseURL!, resolvingAgainstBaseURL: true)
baseUrlComponent?.user = credential?.user
baseUrlComponent?.password = credential?.password
return URL(string: path, relativeTo: baseUrlComponent?.url ?? baseURL) ?? baseUrlComponent?.url ?? baseURL!
}
open func relativePathOf(url: URL) -> String {
// check if url derieved from current base url
let relativePath = url.relativePath
if !relativePath.isEmpty, url.baseURL == self.baseURL {
return (relativePath.removingPercentEncoding ?? relativePath).replacingOccurrences(of: "/", with: "", options: .anchored)
}
if !relativePath.isEmpty, self.baseURL == self.url(of: "/") {
return (relativePath.removingPercentEncoding ?? relativePath).replacingOccurrences(of: "/", with: "", options: .anchored)
}
return relativePath.replacingOccurrences(of: "/", with: "", options: .anchored)
}
open func isReachable(completionHandler: @escaping (Bool) -> Void) {
self.attributesOfItem(path: "/") { (file, error) in
completionHandler(file != nil)
}
}
open weak var fileOperationDelegate: FileOperationDelegate?
open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> Progress? {
let path = (atPath as NSString).appendingPathComponent(folderName) + "/"
return doOperation(.create(path: path), completionHandler: completionHandler)
}
open func moveItem(path: String, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
return doOperation(.move(source: path, destination: toPath), completionHandler: completionHandler)
}
open func copyItem(path: String, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
return doOperation(.copy(source: path, destination: toPath), completionHandler: completionHandler)
}
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> Progress? {
return doOperation(.remove(path: path), completionHandler: completionHandler)
}
open func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
// check file is not a folder
guard (try? localFile.resourceValues(forKeys: [.fileResourceTypeKey]))?.fileResourceType ?? .unknown == .regular else {
dispatch_queue.async {
completionHandler?(self.urlError(localFile.path, code: .fileIsDirectory))
}
return nil
}
let operation = FileOperationType.copy(source: localFile.absoluteString, destination: toPath)
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: operation) ?? true == true else {
return nil
}
let progress = Progress(totalUnitCount: 0)
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
progress.kind = .file
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!)
self.ftpLogin(task) { (error) in
if let error = error {
self.dispatch_queue.async {
completionHandler?(error)
self.delegateNotify(operation, error: error)
}
return
}
self.ftpStore(task, filePath: self.ftpPath(toPath), fromData: nil, fromFile: localFile, onTask: { task in
weak var weakTask = task
progress.cancellationHandler = {
weakTask?.cancel()
}
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
}, onProgress: { bytesSent, totalSent, expectedBytes in
progress.totalUnitCount = expectedBytes
progress.completedUnitCount = totalSent
self.delegateNotify(operation, progress: progress.fractionCompleted)
}, completionHandler: { (error) in
if error != nil {
progress.cancel()
}
self.ftpQuit(task)
self.dispatch_queue.async {
completionHandler?(error)
self.delegateNotify(operation, error: error)
}
})
}
return progress
}
open func copyItem(path: String, toLocalURL destURL: URL, completionHandler: SimpleCompletionHandler) -> Progress? {
let operation = FileOperationType.copy(source: path, destination: destURL.absoluteString)
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: operation) ?? true == true else {
return nil
}
let progress = Progress(totalUnitCount: 0)
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
progress.kind = .file
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!)
self.ftpLogin(task) { (error) in
if let error = error {
self.dispatch_queue.async {
completionHandler?(error)
}
return
}
self.ftpDownload(task, filePath: self.ftpPath(path), onTask: { task in
weak var weakTask = task
progress.cancellationHandler = {
weakTask?.cancel()
}
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
}, onProgress: { recevied, totalReceived, totalSize in
progress.totalUnitCount = totalSize
progress.completedUnitCount = totalReceived
self.delegateNotify(operation, progress: progress.fractionCompleted)
}) { (tmpurl, error) in
if let error = error {
progress.cancel()
self.dispatch_queue.async {
completionHandler?(error)
self.delegateNotify(operation, error: error)
}
return
}
if let tmpurl = tmpurl {
try? FileManager.default.moveItem(at: tmpurl, to: destURL)
self.dispatch_queue.async {
completionHandler?(nil)
self.delegateNotify(operation)
}
}
}
}
return progress
}
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> Progress? {
let operation = FileOperationType.fetch(path: path)
if length == 0 || offset < 0 {
dispatch_queue.async {
completionHandler(Data(), nil)
self.delegateNotify(operation)
}
return nil
}
let progress = Progress(totalUnitCount: 0)
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
progress.kind = .file
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!)
self.ftpLogin(task) { (error) in
if let error = error {
self.dispatch_queue.async {
completionHandler(nil, error)
}
return
}
self.ftpFileData(task, filePath: self.ftpPath(path), from: offset, length: length, onTask: { task in
weak var weakTask = task
progress.cancellationHandler = {
weakTask?.cancel()
}
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
}, onProgress: { recevied, totalReceived, totalSize in
progress.totalUnitCount = totalSize
progress.completedUnitCount = totalReceived
self.delegateNotify(operation, progress: progress.fractionCompleted)
}) { (data, error) in
if let error = error {
progress.cancel()
self.dispatch_queue.async {
completionHandler(nil, error)
self.delegateNotify(operation, error: error)
}
return
}
if let data = data {
self.dispatch_queue.async {
completionHandler(data, nil)
self.delegateNotify(operation)
}
}
}
}
return progress
}
open func writeContents(path: String, contents data: Data?, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
let operation = FileOperationType.modify(path: path)
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: operation) ?? true == true else {
return nil
}
let progress = Progress(totalUnitCount: Int64(data?.count ?? 0))
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
progress.kind = .file
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!)
self.ftpLogin(task) { (error) in
if let error = error {
self.dispatch_queue.async {
completionHandler?(error)
self.delegateNotify(operation, error: error)
}
return
}
let storeHandler = {
self.ftpStore(task, filePath: self.ftpPath(path), fromData: data ?? Data(), fromFile: nil, onTask: { task in
weak var weakTask = task
progress.cancellationHandler = {
weakTask?.cancel()
}
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
}, onProgress: { bytesSent, totalSent, expectedBytes in
progress.completedUnitCount = totalSent
self.delegateNotify(operation, progress: progress.fractionCompleted)
}, completionHandler: { (error) in
if error != nil {
progress.cancel()
}
self.ftpQuit(task)
self.dispatch_queue.async {
completionHandler?(error)
self.delegateNotify(operation, error: error)
}
})
}
if overwrite {
storeHandler()
} else {
self.attributesOfItem(path: path, completionHandler: { (file, erroe) in
if file == nil {
storeHandler()
}
})
}
}
return progress
}
/**
Creates a symbolic link at the specified path that points to an item at the given path.
This method does not traverse symbolic links contained in destination path, making it possible
to create symbolic links to locations that do not yet exist.
Also, if the final path component is a symbolic link, that link is not followed.
- Note: Many servers does't support this functionality.
- Parameters:
- symbolicLink: The file path at which to create the new symbolic link. The last component of the path issued as the name of the link.
- withDestinationPath: The path that contains the item to be pointed to by the link. In other words, this is the destination of the link.
- completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
*/
open func create(symbolicLink path: String, withDestinationPath destPath: String, completionHandler: SimpleCompletionHandler) {
let operation = FileOperationType.link(link: path, target: destPath)
_=self.doOperation(operation, completionHandler: completionHandler)
}
}
extension FTPFileProvider {
fileprivate func doOperation(_ operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> Progress? {
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: operation) ?? true == true else {
return nil
}
let sourcePath = operation.source
let destPath = operation.destination
let command: String
switch operation {
case .create: command = "MKD \(ftpPath(sourcePath))"
case .copy: command = "SITE CPFR \(ftpPath(sourcePath))\r\nSITE CPTO \(ftpPath(destPath!))"
case .move: command = "RNFR \(ftpPath(sourcePath))\r\nRNTO \(ftpPath(destPath!))"
case .remove: command = "DELE \(ftpPath(sourcePath))"
case .link: command = "SITE SYMLINK \(ftpPath(sourcePath)) \(ftpPath(destPath!))"
default: return nil // modify, fetch
}
let progress = Progress(totalUnitCount: 1)
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
progress.kind = .file
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!)
self.ftpLogin(task) { (error) in
if let error = error {
completionHandler?(error)
self.delegateNotify(operation, error: error)
return
}
self.execute(command: command, on: task, completionHandler: { (response, error) in
if let error = error {
completionHandler?(error)
self.delegateNotify(operation, error: error)
return
}
guard let response = response else {
completionHandler?(error)
self.delegateNotify(operation, error: self.urlError(sourcePath, code: .badServerResponse))
return
}
let codes: [Int] = response.components(separatedBy: .newlines).flatMap({ $0.isEmpty ? nil : $0})
.flatMap {
let code = $0.components(separatedBy: .whitespaces).flatMap({ $0.isEmpty ? nil : $0}).first
return code != nil ? Int(code!) : nil
}
if codes.filter({ (450..<560).contains($0) }).count > 0 {
let errorCode: URLError.Code
switch operation {
case .create: errorCode = .cannotCreateFile
case .modify: errorCode = .cannotWriteToFile
case .copy:
self.fallbackCopy(operation, progress: progress, completionHandler: completionHandler)
return
case .move: errorCode = .cannotMoveFile
case .remove:
self.fallbackRemove(operation, progress: progress, on: task, completionHandler: completionHandler)
return
case .link: errorCode = .cannotWriteToFile
default: errorCode = .cannotOpenFile
}
let error = self.urlError(sourcePath, code: errorCode)
progress.cancel()
completionHandler?(error)
self.delegateNotify(operation, error: error)
return
}
progress.completedUnitCount = progress.totalUnitCount
completionHandler?(nil)
self.delegateNotify(operation)
})
}
progress.cancellationHandler = { [weak task] in
task?.cancel()
}
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
return progress
}
private func fallbackCopy(_ operation: FileOperationType, progress: Progress, completionHandler: SimpleCompletionHandler) {
let sourcePath = operation.source
guard let destPath = operation.destination else { return }
let localURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString).appendingPathExtension("tmp")
progress.becomeCurrent(withPendingUnitCount: 1)
_ = self.copyItem(path: sourcePath, toLocalURL: localURL) { (error) in
if let error = error {
self.dispatch_queue.async {
completionHandler?(error)
self.delegateNotify(operation, error: error)
}
return
}
progress.becomeCurrent(withPendingUnitCount: 1)
_ = self.copyItem(localFile: localURL, to: destPath) { error in
completionHandler?(nil)
self.delegateNotify(operation)
}
progress.resignCurrent()
}
progress.resignCurrent()
return
}
private func fallbackRemove(_ operation: FileOperationType, progress: Progress, on task: FileProviderStreamTask, completionHandler: SimpleCompletionHandler) {
let sourcePath = operation.source
self.execute(command: "SITE RMDIR \(ftpPath(sourcePath))", on: task) { (response, error) in
do {
if let error = error {
throw error
}
guard let response = response else {
throw self.urlError(sourcePath, code: .badServerResponse)
}
if response.hasPrefix("50") {
self.fallbackRecursiveRemove(operation, progress: progress, on: task, completionHandler: completionHandler)
return
}
if !response.hasPrefix("2") {
throw self.urlError(sourcePath, code: .cannotRemoveFile)
}
self.dispatch_queue.async {
completionHandler?(nil)
}
self.delegateNotify(operation)
} catch {
progress.cancel()
self.dispatch_queue.async {
completionHandler?(error)
}
self.delegateNotify(operation, error: error)
}
}
}
private func fallbackRecursiveRemove(_ operation: FileOperationType, progress: Progress, on task: FileProviderStreamTask, completionHandler: SimpleCompletionHandler) {
let sourcePath = operation.source
_ = self.recursiveList(path: sourcePath, useMLST: true, completionHandler: { (contents, error) in
if let error = error {
self.dispatch_queue.async {
completionHandler?(error)
self.delegateNotify(operation, error: error)
}
return
}
progress.becomeCurrent(withPendingUnitCount: 1)
let recursiveProgress = Progress(totalUnitCount: Int64(contents.count))
let sortedContents = contents.sorted(by: {
$0.path.localizedStandardCompare($1.path) == .orderedDescending
})
progress.resignCurrent()
var command = ""
for file in sortedContents {
command += (file.isDirectory ? "RMD \(self.ftpPath(file.path))" : "DELE \(self.ftpPath(file.path))") + "\r\n"
}
command += "RMD \(self.ftpPath(sourcePath))"
self.execute(command: command, on: task, completionHandler: { (response, error) in
recursiveProgress.completedUnitCount += 1
self.dispatch_queue.async {
completionHandler?(error)
self.delegateNotify(operation, error: error)
}
// TODO: Digest response
})
})
}
}
extension FTPFileProvider: FileProvider { }
+973
View File
@@ -0,0 +1,973 @@
//
// FTPHelper.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2017 Mousavian. Distributed under MIT license.
//
import Foundation
internal extension FTPFileProvider {
func execute(command: String, on task: FileProviderStreamTask, minLength: Int = 4,
afterSend: ((_ error: Error?) -> Void)? = nil,
completionHandler: @escaping (_ response: String?, _ error: Error?) -> Void) {
let timeout = session.configuration.timeoutIntervalForRequest
let terminalcommand = command + "\r\n"
task.write(terminalcommand.data(using: .utf8)!, timeout: timeout) { (error) in
if let error = error {
completionHandler(nil, error)
return
}
afterSend?(error)
if task.state == .suspended {
task.resume()
}
task.readData(ofMinLength: minLength, maxLength: 1024, timeout: timeout) { (data, eof, error) in
if let error = error {
completionHandler(nil, error)
return
}
if let data = data, let response = String(data: data, encoding: .utf8) {
completionHandler(response.trimmingCharacters(in: .whitespacesAndNewlines), nil)
} else {
completionHandler(nil, self.urlError("", code: .cannotParseResponse))
return
}
}
}
}
func ftpUserPass(_ task: FileProviderStreamTask, completionHandler: @escaping (_ error: Error?) -> Void) {
self.execute(command: "USER \(credential?.user ?? "anonymous")", on: task) { (response, error) in
if let error = error {
completionHandler(error)
return
}
guard let response = response else {
completionHandler(self.urlError("", code: .badServerResponse))
return
}
// successfully logged in
if response.hasPrefix("23") {
completionHandler(nil)
return
}
// needs password
if FileProviderFTPError(message: response).code == 331 {
self.execute(command: "PASS \(self.credential?.password ?? "fileprovider@")", on: task) { (response, error) in
if response?.hasPrefix("23") ?? false {
completionHandler(nil)
} else {
completionHandler(self.urlError("", code: .userAuthenticationRequired))
}
}
return
}
let error = FileProviderFTPError(message: response)
completionHandler(error)
}
}
func ftpLogin(_ task: FileProviderStreamTask, completionHandler: @escaping (_ error: Error?) -> Void) {
let timeout = session.configuration.timeoutIntervalForRequest
var isSecure = false
// Implicit FTP Connection
if self.baseURL?.port == 990 || self.baseURL?.scheme == "ftps" {
task.startSecureConnection()
isSecure = true
}
if task.state == .suspended {
task.resume()
}
task.readData(ofMinLength: 4, maxLength: 2048, timeout: timeout) { (data, eof, error) in
do {
if let error = error {
throw error
}
guard let data = data, let response = String(data: data, encoding: .utf8) else {
throw self.urlError("", code: .cannotParseResponse)
}
guard response.trimmingCharacters(in: .whitespacesAndNewlines).hasPrefix("22") else {
throw FileProviderFTPError(message: response)
}
} catch {
completionHandler(error)
return
}
if !isSecure && self.baseURL?.scheme == "ftpes" {
// Explicit FTP Connection, by upgrading connection to FTP/SSL
self.execute(command: "AUTH TLS", on: task, minLength: 0, completionHandler: { (response, error) in
if let error = error {
completionHandler(error)
return
}
if let response = response, response.hasPrefix("23") {
task.startSecureConnection()
isSecure = true
self.execute(command: "PBSZ 0\r\nPROT P", on: task, completionHandler: { (response, error) in
if let error = error {
completionHandler(error)
return
}
self.ftpUserPass(task, completionHandler: completionHandler)
})
}
})
} else if isSecure {
self.execute(command: "PBSZ 0\r\nPROT P", on: task, completionHandler: { (response, error) in
if let error = error {
completionHandler(error)
return
}
self.ftpUserPass(task, completionHandler: completionHandler)
})
} else {
self.ftpUserPass(task, completionHandler: completionHandler)
}
}
}
func ftpPassive(_ task: FileProviderStreamTask, completionHandler: @escaping (_ dataTask: FileProviderStreamTask?, _ error: Error?) -> Void) {
func trimmedNumber(_ s : String) -> String {
return s.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
}
self.execute(command: "PASV", on: task) { (response, error) in
do {
if let error = error {
throw error
}
guard let response = response, let destString = response.trimmingCharacters(in: .whitespacesAndNewlines).components(separatedBy: " ").last else {
throw self.urlError("", code: .badServerResponse)
}
let destArray = destString.components(separatedBy: ",").flatMap({ UInt32(trimmedNumber($0)) })
guard destArray.count == 6 else {
throw self.urlError("", code: .badServerResponse)
}
// first 4 elements are ip, 2 next are port, as byte
var host = destArray.prefix(4).flatMap(String.init).joined(separator: ".")
let portHi = Int(destArray[4]) << 8
let portLo = Int(destArray[5])
let port = portHi + portLo
// IPv6 workaround
if host == "127.555.555.555" {
host = self.baseURL!.host!
}
let passiveTask = self.session.fpstreamTask(withHostName: host, port: port)
passiveTask.resume()
if self.baseURL?.scheme == "ftps" || self.baseURL?.scheme == "ftpes" || self.baseURL?.port == 990 {
passiveTask.startSecureConnection()
}
completionHandler(passiveTask, nil)
} catch {
completionHandler(nil, error)
return
}
}
}
func ftpActive(_ task: FileProviderStreamTask, completionHandler: @escaping (_ dataTask: FileProviderStreamTask?, _ error: Error?) -> Void) {
let service = NetService(domain: "", type: "_tcp.", name: "", port: 0)
service.publish(options: .listenForConnections)
let startTime = Date()
while service.port < 1 && startTime.timeIntervalSinceNow > -self.session.configuration.timeoutIntervalForRequest {
usleep(100_000)
}
let activeTask = self.session.fpstreamTask(withNetService: service)
activeTask.resume()
if self.baseURL?.scheme == "ftps" || self.baseURL?.port == 990 {
activeTask.startSecureConnection()
}
self.execute(command: "PORT \(service.port)", on: task) { (response, error) in
do {
if let error = error {
throw error
}
guard let response = response else {
throw self.urlError("", code: .badServerResponse)
}
guard !response.hasPrefix("5") else {
throw self.urlError("", code: .cannotConnectToHost)
}
completionHandler(activeTask, nil)
} catch {
activeTask.cancel()
completionHandler(nil, error)
}
}
}
func ftpDataConnect(_ task: FileProviderStreamTask, completionHandler: @escaping (_ dataTask: FileProviderStreamTask?, _ error: Error?) -> Void) {
if self.passiveMode {
self.ftpPassive(task, completionHandler: completionHandler)
} else {
dispatch_queue.async {
self.ftpActive(task, completionHandler: completionHandler)
}
}
}
func ftpList(_ task: FileProviderStreamTask, of path: String, useMLST: Bool,
completionHandler: @escaping (_ contents: [String], _ error: Error?) -> Void) {
self.ftpDataConnect(task) { (dataTask, error) in
if let error = error {
completionHandler([], error)
return
}
guard let dataTask = dataTask else {
completionHandler([], self.urlError(path, code: .badServerResponse))
return
}
var success = false
let command = useMLST ? "MLSD \(path)" : "LIST \(path)"
self.execute(command: command, on: task, minLength: 20, afterSend: { error in
DispatchQueue.global().async {
let timeout = self.session.configuration.timeoutIntervalForRequest
var finalData = Data()
var eof = false
var error: Error?
do {
while !eof {
let group = DispatchGroup()
group.enter()
dataTask.readData(ofMinLength: 1, maxLength: Int.max, timeout: timeout) { (data, seof, serror) in
if let data = data {
finalData.append(data)
}
eof = seof
error = serror
group.leave()
}
let waitResult = group.wait(timeout: .now() + timeout)
if let error = error {
if !((error as NSError).domain == URLError.errorDomain
&& (error as NSError).code == URLError.cancelled.rawValue) {
throw error
}
return
}
if waitResult == .timedOut {
throw self.urlError(path, code: .timedOut)
}
}
guard let response = String(data: finalData, encoding: .utf8) else {
throw self.urlError(path, code: .badServerResponse)
}
let contents: [String] = response.components(separatedBy: "\n")
.flatMap({ $0.trimmingCharacters(in: .whitespacesAndNewlines) })
success = true
completionHandler(contents, nil)
} catch {
completionHandler([], error)
}
}
}) { (response, error) in
do {
if let error = error {
throw error
}
guard let response = response else {
throw self.urlError(path, code: .cannotParseResponse)
}
if response.hasPrefix("500") && useMLST {
dataTask.cancel()
self.supportsRFC3659 = false
throw self.urlError(path, code: .unsupportedURL)
}
if !success && !(response.hasPrefix("25") || response.hasPrefix("15")) {
throw FileProviderFTPError(message: response, path: path)
}
} catch {
self.dispatch_queue.async {
completionHandler([], error)
}
}
}
}
}
func recursiveList(path: String, useMLST: Bool, foundItemsHandler: ((_ contents: [FileObject]) -> Void)? = nil,
completionHandler: @escaping (_ contents: [FileObject], _ error: Error?) -> Void) -> Progress? {
let progress = Progress(totalUnitCount: -1)
let queue = DispatchQueue(label: "\(self.type).recursiveList")
queue.async {
let group = DispatchGroup()
var result = [FileObject]()
var success = true
group.enter()
self.contentsOfDirectory(path: path, completionHandler: { (files, error) in
success = success && (error == nil)
if let error = error {
completionHandler([], error)
group.leave()
return
}
result.append(contentsOf: files)
progress.completedUnitCount = Int64(files.count)
foundItemsHandler?(files)
let directories: [FileObject] = files.filter { $0.isDirectory }
progress.becomeCurrent(withPendingUnitCount: Int64(directories.count))
for dir in directories {
group.enter()
_=self.recursiveList(path: dir.path, useMLST: useMLST, foundItemsHandler: foundItemsHandler) {
(contents, error) in
success = success && (error == nil)
if let error = error {
completionHandler([], error)
group.leave()
return
}
foundItemsHandler?(files)
result.append(contentsOf: contents)
group.leave()
}
}
progress.resignCurrent()
group.leave()
})
group.wait()
if success {
self.dispatch_queue.async {
completionHandler(result, nil)
}
}
}
return progress
}
func ftpRetrieve(_ task: FileProviderStreamTask, filePath: String, from position: Int64 = 0, length: Int = -1,
onTask: ((_ task: FileProviderStreamTask) -> Void)?,
onProgress: @escaping (_ data: Data, _ totalReceived: Int64, _ expectedBytes: Int64) -> Void,
completionHandler: @escaping (_ error: Error?) -> Void) {
self.attributesOfItem(path: filePath) { (file, error) in
let totalSize = file?.size ?? -1
// Retreive data from server
self.ftpDataConnect(task) { (dataTask, error) in
if let error = error {
completionHandler(error)
return
}
guard let dataTask = dataTask else {
completionHandler(self.urlError(filePath, code: .badServerResponse))
return
}
// Send retreive command
let len = 19 /* TYPE response */ + 65 + String(position).count /* REST Response */ + 53 + filePath.count + String(totalSize).count /* RETR open response */ + 26 /* RETR Transfer complete message. */
self.execute(command: "TYPE I" + "\r\n" + "REST \(position)" + "\r\n" + "RETR \(filePath)", on: task, minLength: len, afterSend: { error in
// starting passive task
onTask?(dataTask)
let timeout = self.session.configuration.timeoutIntervalForRequest
DispatchQueue.global().async {
var totalReceived: Int64 = 0
var eof = false
var error: Error?
while !eof {
let group = DispatchGroup()
group.enter()
dataTask.readData(ofMinLength: 1, maxLength: Int.max, timeout: timeout) { (data, segeof, segerror) in
if let data = data {
var data = data
if length > 0, Int64(data.count) + totalReceived > Int64(length) {
data.count = Int(Int64(length) - totalReceived)
}
totalReceived += Int64(data.count)
onProgress(data, totalReceived, totalSize)
}
eof = segeof || (length > 0 && totalReceived >= Int64(length))
error = segerror
group.leave()
}
let waitResult = group.wait(timeout: .now() + timeout)
if let error = error {
completionHandler(error)
return
}
if waitResult == .timedOut {
completionHandler(self.urlError(filePath, code: .timedOut))
return
}
}
dataTask.closeRead()
dataTask.closeWrite()
completionHandler(nil)
return
}
}) { (response, error) in
do {
if let error = error {
throw error
}
guard let response = response else {
throw self.urlError(filePath, code: .cannotParseResponse)
}
if !(response.hasPrefix("1") || response.hasPrefix("2")) {
throw FileProviderFTPError(message: response)
}
} catch {
self.dispatch_queue.async {
completionHandler(error)
}
}
}
}
}
}
func ftpFileData(_ task: FileProviderStreamTask, filePath: String, from position: Int64 = 0, length: Int = -1,
onTask: ((_ task: FileProviderStreamTask) -> Void)?,
onProgress: ((_ bytesReceived: Int64, _ totalReceived: Int64, _ expectedBytes: Int64) -> Void)?,
completionHandler: @escaping (_ data: Data?, _ error: Error?) -> Void) {
// Check cache
if useCache, let url = URL(string: filePath.addingPercentEncoding(withAllowedCharacters: .filePathAllowed) ?? filePath, relativeTo: self.baseURL!)?.absoluteURL, let cachedResponse = self.cache?.cachedResponse(for: URLRequest(url: url)), cachedResponse.data.count > 0 {
dispatch_queue.async {
completionHandler(cachedResponse.data, nil)
}
return
}
var finalData = Data()
self.ftpRetrieve(task, filePath: filePath, from: position, length: length, onTask: onTask, onProgress: { (data, total, expected) in
finalData.append(data)
onProgress?(Int64(data.count), total, expected)
}) { (error) in
if let error = error {
completionHandler(nil, error)
}
if let url = URL(string: filePath.addingPercentEncoding(withAllowedCharacters: .filePathAllowed) ?? filePath, relativeTo: self.baseURL!)?.absoluteURL {
let urlresponse = URLResponse(url: url, mimeType: nil, expectedContentLength: finalData.count, textEncodingName: nil)
let cachedResponse = CachedURLResponse(response: urlresponse, data: finalData)
let request = URLRequest(url: url)
self.cache?.storeCachedResponse(cachedResponse, for: request)
}
completionHandler(finalData, nil)
}
}
func ftpDownload(_ task: FileProviderStreamTask, filePath: String, from position: Int64 = 0, length: Int = -1,
onTask: ((_ task: FileProviderStreamTask) -> Void)?,
onProgress: ((_ bytesReceived: Int64, _ totalReceived: Int64, _ expectedBytes: Int64) -> Void)?,
completionHandler: @escaping (_ file: URL?, _ error: Error?) -> Void) {
let tempURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString).appendingPathExtension("tmp")
// Check cache
if useCache, let url = URL(string: filePath.addingPercentEncoding(withAllowedCharacters: .filePathAllowed) ?? filePath, relativeTo: self.baseURL!)?.absoluteURL, let cachedResponse = self.cache?.cachedResponse(for: URLRequest(url: url)), cachedResponse.data.count > 0 {
dispatch_queue.async {
do {
try cachedResponse.data.write(to: tempURL)
completionHandler(tempURL, nil)
} catch {
completionHandler(nil, error)
}
try? FileManager.default.removeItem(at: tempURL)
}
return
}
do {
try Data().write(to: tempURL, options: [])
let fileHandle = try FileHandle(forWritingTo: tempURL)
self.ftpRetrieve(task, filePath: filePath, from: position, length: length, onTask: onTask, onProgress: { (data, total, expected) in
fileHandle.write(data)
onProgress?(Int64(data.count), total, expected)
}) { (error) in
defer {
try? FileManager.default.removeItem(at: tempURL)
}
fileHandle.closeFile()
if let error = error {
completionHandler(nil, error)
return
}
completionHandler(tempURL, nil)
}
} catch {
completionHandler(nil, error)
}
}
func ftpStore(_ task: FileProviderStreamTask, filePath: String, fromData: Data?, fromFile: URL?,
onTask: ((_ task: FileProviderStreamTask) -> Void)?,
onProgress: ((_ bytesSent: Int64, _ totalSent: Int64, _ expectedBytes: Int64) -> Void)?,
completionHandler: @escaping (_ error: Error?) -> Void) {
if self.uploadByREST {
ftpStoreParted(task, filePath: filePath, fromData: fromData, fromFile: fromFile, onTask: onTask, onProgress: onProgress, completionHandler: completionHandler)
} else {
ftpStoreSerial(task, filePath: filePath, fromData: fromData, fromFile: fromFile, onTask: onTask, onProgress: onProgress, completionHandler: completionHandler)
}
}
func ftpStoreSerial(_ task: FileProviderStreamTask, filePath: String, fromData: Data?, fromFile: URL?,
onTask: ((_ task: FileProviderStreamTask) -> Void)?,
onProgress: ((_ bytesSent: Int64, _ totalSent: Int64, _ expectedBytes: Int64) -> Void)?, completionHandler: @escaping (_ error: Error?) -> Void) {
guard let size: Int64 = (fromData != nil ? Int64(fromData!.count) : nil) ?? fromFile?.fileSize else { return }
ftpDataConnect(task) { (dataTask, error) in
if let error = error {
completionHandler(error)
return
}
guard let dataTask = dataTask else {
completionHandler(self.urlError(filePath, code: .badServerResponse))
return
}
let len = 19 /* TYPE response */ + 44 + filePath.count /* STOR open response */ + 10 /* RETR Transfer complete message. */
var success = false
self.execute(command: "TYPE I" + "\r\n" + "STOR \(filePath)", on: task, minLength: len, afterSend: { error in
onTask?(dataTask)
let timeout = self.session.configuration.timeoutIntervalForResource
var error: Error?
var fileHandle: FileHandle?
if let file = fromFile {
fileHandle = FileHandle(forReadingAtPath: file.path)
}
defer {
fileHandle?.closeFile()
dataTask.closeRead()
dataTask.closeWrite()
}
let chunkSize = 4096
var eof = false
var sent: Int64 = 0
repeat {
let subdata: Data
if let data = fromData {
let endIndex = min(data.count, Int(sent) + chunkSize)
subdata = data.subdata(in: Int(sent)..<endIndex)
} else if let fileHandle = fileHandle {
fileHandle.seek(toFileOffset: UInt64(sent))
subdata = fileHandle.readData(ofLength: chunkSize)
} else {
return
}
if subdata.count == 0 { return }
let group = DispatchGroup()
group.enter()
dataTask.write(subdata, timeout: timeout, completionHandler: { (serror) in
error = serror
sent += Int64(subdata.count)
group.leave()
onProgress?(Int64(subdata.count), sent, size)
//print("ftp \(filePath): \(subdata.count), \(sent), \(size)")
})
let waitResult = group.wait(timeout: .now() + timeout)
if waitResult == .timedOut {
error = self.urlError(filePath, code: .timedOut)
}
if let error = error {
completionHandler(error)
return
}
if let data = fromData {
let endIndex = min(data.count, Int(sent) + chunkSize)
eof = endIndex == data.count
} else if let fileHandle = fileHandle {
eof = Int64(fileHandle.offsetInFile) == size
}
} while !eof
success = true
}) { (response, error) in
guard success else { return }
do {
if let error = error {
throw error
}
guard let response = response else {
throw self.urlError(filePath, code: .cannotParseResponse)
}
if !(response.hasPrefix("1") || response.hasPrefix("2")) {
throw FileProviderFTPError(message: response)
}
completionHandler(nil)
} catch {
self.dispatch_queue.async {
completionHandler(error)
}
}
}
}
}
func ftpStoreParted(_ task: FileProviderStreamTask, filePath: String, fromData: Data?, fromFile: URL?, from position: Int64 = 0,
onTask: ((_ task: FileProviderStreamTask) -> Void)?,
onProgress: ((_ bytesSent: Int64, _ totalSent: Int64, _ expectedBytes: Int64) -> Void)?,
completionHandler: @escaping (_ error: Error?) -> Void) {
operation_queue.addOperation {
guard let size: Int64 = (fromData != nil ? Int64(fromData!.count) : nil) ?? fromFile?.fileSize else { return }
let timeout = self.session.configuration.timeoutIntervalForResource
var error: Error?
let chunkSize: Int
switch size {
case 0..<262_144:
chunkSize = 32_768 // 0KB To 256KB, chunk size is 32KB
case 262_144..<1_048_576:
chunkSize = 65_536 // 256KB To 1MB, chunk size is 64KB
case 1_048_576..<10_485_760:
chunkSize = 131_072 // 1MB To 10MB, chunk size is 128KB
case 0_048_576..<33_554_432:
chunkSize = 262_144 // 10MB To 32MB, chunk size is 256KB
default:
chunkSize = 524_288 // Larger than 32MB, chunk size is 512KB
}
var fileHandle: FileHandle?
if let file = fromFile {
fileHandle = FileHandle(forReadingAtPath: file.path)
}
defer {
fileHandle?.closeFile()
}
var eof = false
var sent: Int64 = position
var retried = 0
repeat {
let subdata: Data
if let data = fromData {
let endIndex = min(data.count, Int(sent - position) + chunkSize)
subdata = data.subdata(in: Int(sent - position)..<endIndex)
} else if let fileHandle = fileHandle {
fileHandle.seek(toFileOffset: UInt64(sent))
subdata = fileHandle.readData(ofLength: chunkSize)
} else {
return
}
if subdata.count == 0 { break }
let group = DispatchGroup()
group.enter()
self.ftpStore(task, data: subdata, to: filePath, from: sent, onTask: onTask, completionHandler: { (serror) in
error = serror
sent += Int64(subdata.count)
retried = 0
group.leave()
onProgress?(Int64(subdata.count), sent, size)
})
let waitResult = group.wait(timeout: .now() + timeout)
if waitResult == .timedOut {
error = self.urlError(filePath, code: .timedOut)
}
if let error = error {
retried += 1
print(error.localizedDescription)
if retried > 3 {
completionHandler(error)
return
}
}
if let data = fromData {
let endIndex = min(data.count, Int(sent) + chunkSize)
eof = endIndex == data.count
} else if let fileHandle = fileHandle {
eof = Int64(fileHandle.offsetInFile) == size
}
} while !eof
completionHandler(nil)
}
}
func ftpStore(_ task: FileProviderStreamTask, data: Data, to filePath: String, from position: Int64,
onTask: ((_ task: FileProviderStreamTask) -> Void)?,
completionHandler: @escaping (_ error: Error?) -> Void) {
self.ftpDataConnect(task) { (dataTask, error) in
if let error = error {
completionHandler(error)
return
}
guard let dataTask = dataTask else {
completionHandler(self.urlError(filePath, code: .badServerResponse))
return
}
// Send retreive command
var success = false
let len = 19 /* TYPE response */ + 65 + String(position).count /* REST Response */ + 44 + filePath.count /* STOR open response */ + 10 /* RETR Transfer complete message. */
self.execute(command: "TYPE I" + "\r\n" + "REST \(position)" + "\r\n" + "STOR \(filePath)", on: task, minLength: len, afterSend: { error in
// starting passive task
let timeout = self.session.configuration.timeoutIntervalForRequest
onTask?(dataTask)
if data.count == 0 { return }
dataTask.write(data, timeout: timeout, completionHandler: { (error) in
if let error = error {
completionHandler(error)
return
}
success = true
dataTask.closeRead()
dataTask.closeWrite()
})
}) { (response, error) in
guard success else { return }
do {
if let error = error {
throw error
}
guard let response = response else {
throw self.urlError(filePath, code: .cannotParseResponse)
}
if !(response.hasPrefix("1") || response.hasPrefix("2")) {
throw FileProviderFTPError(message: response)
}
completionHandler(nil)
} catch {
self.dispatch_queue.async {
completionHandler(error)
}
}
}
}
}
func ftpQuit(_ task: FileProviderStreamTask) {
self.execute(command: "QUIT", on: task) { (_, _) in
//task.closeRead()
//task.closeWrite()
}
}
func ftpPath(_ apath: String) -> String {
// path of base url should be concreted into file path! And remove final slash
var path = baseURL!.appendingPathComponent(apath).path.replacingOccurrences(of: "/", with: "", options: [.anchored, .backwards])
// Fixing slashes
if !path.hasPrefix("/") {
path = "/" + path
}
return path
}
func parseUnixList(_ text: String, in path: String) -> FileObject? {
let gregorian = Calendar(identifier: .gregorian)
let nearDateFormatter = DateFormatter()
nearDateFormatter.calendar = gregorian
nearDateFormatter.locale = Locale(identifier: "en_US_POSIX")
nearDateFormatter.dateFormat = "MMM dd hh:ss yyyy"
let farDateFormatter = DateFormatter()
farDateFormatter.calendar = gregorian
farDateFormatter.locale = Locale(identifier: "en_US_POSIX")
farDateFormatter.dateFormat = "MMM dd yyyy"
let thisYear = gregorian.component(.year, from: Date())
let components = text.components(separatedBy: " ").flatMap { $0.isEmpty ? nil : $0 }
guard components.count >= 9 else { return nil }
let posixPermission = components[0]
let linksCount = Int(components[1]) ?? 0
//let owner = components[2]
//let groupOwner = components[3]
let size = Int64(components[4]) ?? -1
let date = components[5..<8].joined(separator: " ")
let name = components[8..<components.count].joined(separator: " ")
guard name != "." && name != ".." else { return nil }
let path = (path as NSString).appendingPathComponent(name).replacingOccurrences(of: "/", with: "", options: .anchored)
let file = FileObject(url: url(of: path), name: name, path: "/" + path)
#if swift(>=4.0)
let typeChar = posixPermission.first ?? Character(" ")
#else
let typeChar = posixPermission.characters.first ?? Character(" ")
#endif
switch String(typeChar) {
case "d": file.type = .directory
case "l": file.type = .symbolicLink
default: file.type = .regular
}
file.isReadOnly = !posixPermission.contains("w")
file.size = file.isDirectory ? -1 : size
file.allValues[.linkCountKey] = linksCount
if let parsedDate = nearDateFormatter.date(from: date + " " + String(thisYear)) {
if parsedDate > Date() {
file.modifiedDate = gregorian.date(byAdding: .year, value: -1, to: parsedDate)
} else {
file.modifiedDate = parsedDate
}
} else if let parsedDate = farDateFormatter.date(from: date) {
file.modifiedDate = parsedDate
}
return file
}
func parseMLST(_ text: String, in path: String) -> FileObject? {
var components = text.components(separatedBy: ";").flatMap { $0.isEmpty ? nil : $0 }
guard components.count > 1 else { return nil }
let nameOrPath = components.removeLast().trimmingCharacters(in: .whitespacesAndNewlines)
var correctedPath: String
let name: String
if nameOrPath.hasPrefix("/") {
correctedPath = nameOrPath.replacingOccurrences(of: baseURL!.path, with: "", options: .anchored)
name = (nameOrPath as NSString).lastPathComponent
} else {
name = nameOrPath
correctedPath = (path as NSString).appendingPathComponent(nameOrPath)
}
correctedPath = correctedPath.replacingOccurrences(of: "/", with: "", options: .anchored)
var attributes = [String: String]()
for component in components {
let keyValue = component.components(separatedBy: "=") .flatMap { $0.isEmpty ? nil : $0 }
guard keyValue.count >= 2, !keyValue[0].isEmpty else { continue }
attributes[keyValue[0].lowercased()] = keyValue.dropFirst().joined(separator: "=")
}
let file = FileObject(url: url(of: correctedPath), name: name, path: "/" + correctedPath)
let dateFormatter = DateFormatter()
dateFormatter.calendar = Calendar(identifier: .gregorian)
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyyMMddhhmmss"
for (key, attribute) in attributes {
switch key {
case "type":
switch attribute.lowercased() {
case "file": file.type = .regular
case "dir": file.type = .directory
case "link": file.type = .symbolicLink
case "os.unix=block": file.type = .blockSpecial
case "cdir", "pdir": return nil // . and .. files are redundant in listing
default: file.type = .unknown
}
case "unique":
file.allValues[.fileResourceIdentifierKey] = attribute
case "modify":
file.modifiedDate = dateFormatter.date(from: attribute)
case "create":
file.creationDate = dateFormatter.date(from: attribute)
case "perm":
file.allValues[.isReadableKey] = attribute.contains("r") || attribute.contains("l")
file.allValues[.isWritableKey] = attribute.contains("w") || attribute.contains("a")
case "size":
file.size = Int64(attribute) ?? -1
case "media-type":
file.allValues[.mimeTypeKey] = attribute
default:
break
}
}
return file
}
}
/// Contains error code and description returned by FTP/S provider.
public struct FileProviderFTPError: Error {
/// HTTP status code returned for error by server.
public let code: Int
/// Path of file/folder casued that error
public let path: String
/// Contents returned by server as error description
public let errorDescription: String?
init(code: Int, path: String, errorDescription: String?) {
self.code = code
self.path = path
self.errorDescription = errorDescription
}
init(message response: String, path: String = "") {
let message = response.components(separatedBy: .newlines).last ?? "No Response"
#if swift(>=4.0)
let startIndex = (message.index(of: "-") ?? message.index(of: " ")) ?? message.startIndex
self.code = Int(message[..<startIndex].trimmingCharacters(in: .whitespacesAndNewlines)) ?? -1
#else
let startIndex = (message.characters.index(of: "-") ?? message.characters.index(of: " ")) ?? message.startIndex
self.code = Int(message.substring(to: startIndex).trimmingCharacters(in: .whitespacesAndNewlines)) ?? -1
#endif
self.path = path
if code > 0 {
#if swift(>=4.0)
self.errorDescription = message[startIndex...].trimmingCharacters(in: .whitespacesAndNewlines)
#else
self.errorDescription = message.substring(from: startIndex).trimmingCharacters(in: .whitespacesAndNewlines)
#endif
} else {
self.errorDescription = message
}
}
}
+408
View File
@@ -0,0 +1,408 @@
//
// FileProvider.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2016 Mousavian. Distributed under MIT license.
//
import Foundation
/// Containts path, url and attributes of a file or resource.
open class FileObject: Equatable {
/// A `Dictionary` contains file information, using `URLResourceKey` keys.
open internal(set) var allValues: [URLResourceKey: Any]
public init(allValues: [URLResourceKey: Any]) {
self.allValues = allValues
}
internal init(url: URL?, name: String, path: String) {
self.allValues = [URLResourceKey: Any]()
if let url = url {
self.url = url
}
self.name = name
self.path = path
}
/// URL to access the resource, can be a relative URL against base URL.
/// not supported by Dropbox provider.
open internal(set) var url: URL {
get {
if let url = allValues[.fileURLKey] as? URL {
return url
} else {
let path = self.path.addingPercentEncoding(withAllowedCharacters: .filePathAllowed) ?? self.path
return URL(string: path) ?? URL(string: "/")!
}
}
set {
allValues[.fileURLKey] = newValue
}
}
/// Name of the file, usually equals with the last path component
open internal(set) var name: String {
get {
return allValues[.nameKey] as! String
}
set {
allValues[.nameKey] = newValue
}
}
/// Relative path of file object
open internal(set) var path: String {
get {
return allValues[.pathKey] as! String
}
set {
allValues[.pathKey] = newValue
}
}
/// Size of file on disk, return -1 for directories.
open internal(set) var size: Int64 {
get {
return allValues[.fileSizeKey] as? Int64 ?? -1
}
set {
allValues[.fileSizeKey] = newValue
}
}
/// Count of children items of a driectory.
open internal(set) var childrensCount: Int? {
get {
return allValues[.childrensCount] as? Int
}
set {
allValues[.childrensCount] = newValue
}
}
/// The time contents of file has been created, returns nil if not set
open internal(set) var creationDate: Date? {
get {
return allValues[.creationDateKey] as? Date
}
set {
allValues[.creationDateKey] = newValue
}
}
/// The time contents of file has been modified, returns nil if not set
open internal(set) var modifiedDate: Date? {
get {
return allValues[.contentModificationDateKey] as? Date
}
set {
allValues[.contentModificationDateKey] = newValue
}
}
/// return resource type of file, usually directory, regular or symLink
open internal(set) var type: URLFileResourceType {
get {
return allValues[.fileResourceTypeKey] as? URLFileResourceType ?? .unknown
}
set {
allValues[.fileResourceTypeKey] = newValue
}
}
/// 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[.isHiddenKey] as? Bool ?? false
}
set {
allValues[.isHiddenKey] = newValue
}
}
/// File can not be written
open internal(set) var isReadOnly: Bool {
get {
return !(allValues[.isWritableKey] as? Bool ?? true)
}
set {
allValues[.isWritableKey] = !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
}
/// Check `FileObject` equality
public static func ==(lhs: FileObject, rhs: FileObject) -> Bool {
if rhs === lhs {
return true
}
#if swift(>=3.1)
if Swift.type(of: lhs) != Swift.type(of: rhs) {
return false
}
#else
if type(of: lhs) != type(of: rhs) {
return false
}
#endif
if let rurl = rhs.allValues[.fileURLKey] as? URL, let lurl = lhs.allValues[.fileURLKey] as? URL {
return rurl == lurl
}
return rhs.path == lhs.path && rhs.size == lhs.size && rhs.modifiedDate == lhs.modifiedDate
}
internal func mapPredicate() -> [String: Any] {
let mapDict: [URLResourceKey: String] = [.fileURLKey: "url", .nameKey: "name", .pathKey: "path",
.fileSizeKey: "filesize", .creationDateKey: "creationDate",
.contentModificationDateKey: "modifiedDate", .isHiddenKey: "isHidden",
.isWritableKey: "isWritable", .serverDateKey: "serverDate",
.entryTagKey: "entryTag", .mimeTypeKey: "mimeType"]
let typeDict: [URLFileResourceType: String] = [.directory: "directory", .regular: "regular",
.symbolicLink: "symbolicLink", .unknown: "unknown"]
var result = [String: Any]()
for (key, value) in allValues {
if let convertkey = mapDict[key] {
result[convertkey] = value
}
}
result["eTag"] = result["entryTag"]
result["isReadOnly"] = self.isReadOnly
result["isDirectory"] = self.isDirectory
result["isRegularFile"] = self.isRegularFile
result["isSymLink"] = self.isSymLink
result["type"] = typeDict[self.type] ?? "unknown"
return result
}
/// Converts macOS spotlight query for searching files to a query that can be used for `searchFiles()` method
static public func convertPredicate(fromSpotlight query: NSPredicate) -> NSPredicate {
let mapDict: [String: URLResourceKey] = [NSMetadataItemURLKey: .fileURLKey, NSMetadataItemFSNameKey: .nameKey,
NSMetadataItemPathKey: .pathKey, NSMetadataItemFSSizeKey: .fileSizeKey,
NSMetadataItemFSCreationDateKey: .creationDateKey, NSMetadataItemFSContentChangeDateKey: .contentModificationDateKey,
"kMDItemFSInvisible": .isHiddenKey, "kMDItemFSIsWriteable": .isWritableKey,
"kMDItemKind": .mimeTypeKey]
if let cQuery = query as? NSCompoundPredicate {
let newSub = cQuery.subpredicates.map { convertPredicate(fromSpotlight: $0 as! NSPredicate) }
switch cQuery.compoundPredicateType {
case .and: return NSCompoundPredicate(andPredicateWithSubpredicates: newSub)
case .not: return NSCompoundPredicate(notPredicateWithSubpredicate: newSub[0])
case .or: return NSCompoundPredicate(orPredicateWithSubpredicates: newSub)
}
} else if let cQuery = query as? NSComparisonPredicate {
var newLeft = cQuery.leftExpression
var newRight = cQuery.rightExpression
if newLeft.expressionType == .keyPath, let newKey = mapDict[newLeft.keyPath] {
newLeft = NSExpression(forKeyPath: newKey.rawValue)
}
if newRight.expressionType == .keyPath, let newKey = mapDict[newRight.keyPath] {
newRight = NSExpression(forKeyPath: newKey.rawValue)
}
return NSComparisonPredicate(leftExpression: newLeft, rightExpression: newRight, modifier: cQuery.comparisonPredicateModifier, type: cQuery.predicateOperatorType, options: cQuery.options)
} else {
return query
}
}
}
/// Containts attributes of a provider.
open class VolumeObject {
/// A `Dictionary` contains volume information, using `URLResourceKey` keys.
open internal(set) var allValues: [URLResourceKey: Any]
public init(allValues: [URLResourceKey: Any]) {
self.allValues = allValues
}
/// The root directory of the resources volume, returned as an `URL` object.
open internal(set) var url: URL? {
get {
return allValues[.volumeURLKey] as? URL
}
set {
allValues[.volumeURLKey] = newValue
}
}
/// The name of the volume.
open internal(set) var name: String? {
get {
return allValues[.volumeNameKey] as? String
}
set {
allValues[.volumeNameKey] = newValue
}
}
/// the volumes capacity in bytes, return -1 if is undetermined.
open internal(set) var totalCapacity: Int64 {
get {
return allValues[.volumeTotalCapacityKey] as? Int64 ?? -1
}
set {
allValues[.volumeTotalCapacityKey] = newValue
}
}
/// The volumes available capacity in bytes.
open internal(set) var availableCapacity: Int64 {
get {
return allValues[.volumeAvailableCapacityKey] as? Int64 ?? 0
}
set {
allValues[.volumeAvailableCapacityKey] = newValue
}
}
open internal(set) var usage: Int64 {
get {
return totalCapacity >= 0 ? totalCapacity - availableCapacity : -availableCapacity
}
set {
availableCapacity = totalCapacity >= 0 ? totalCapacity - newValue : -newValue
}
}
/// the volumes creation date, returned as an `Date` object, or NULL if it cannot be determined
open internal(set) var creationDate: Date? {
get {
return allValues[.volumeCreationDateKey] as? Date
}
set {
allValues[.volumeCreationDateKey] = newValue
}
}
/// Determining whether the volume is read-only
open internal(set) var isReadOnly: Bool {
get {
return allValues[.volumeIsReadOnlyKey] as? Bool ?? false
}
set {
allValues[.volumeIsReadOnlyKey] = newValue
}
}
@available(iOS 10.0, macOS 10.12, tvOS 10.0, *)
open internal(set) var isEncrypted: Bool {
get {
return allValues[.volumeIsEncryptedKey] as? Bool ?? false
}
set {
allValues[.volumeIsEncryptedKey] = !newValue
}
}
}
/// 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)
/// Initializes a `FileObjectSorting` allows to sort an `Array` of `FileObject`.
///
/// - Parameters:
/// - type: Determines to sort based on which file property.
/// - ascending: `true` of resulting `Array` is ascending
/// - isDirectoriesFirst: Puts directoris on the top of resulting `Array`.
public init (type: SortType, ascending: Bool = true, isDirectoriesFirst: Bool = false) {
self.sortType = type
self.ascending = ascending
self.isDirectoriesFirst = isDirectoriesFirst
}
/// Sorts array of `FileObject`s by criterias set in attributes.
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
}
}
}
}
+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.
+987 -291
View File
File diff suppressed because it is too large Load Diff
+555
View File
@@ -0,0 +1,555 @@
//
// HTTPFileProvider.swift
// FilesProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2017 Mousavian. Distributed under MIT license.
//
import Foundation
/**
The abstract base class for all REST/Web based providers such as WebDAV, Dropbox, OneDrive, Google Drive, etc. and encapsulates basic
functionalitis such as downloading/uploading.
No instance of this class should (and can) be created. Use derived classes instead. It leads to a crash with `fatalError()`.
*/
open class HTTPFileProvider: FileProviderBasicRemote, FileProviderOperations, FileProviderReadWrite {
open class var type: String { fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.") }
open let baseURL: URL?
/// **OBSOLETED** Current active path used in `contentsOfDirectory(path:completionHandler:)` method.
@available(*, obsoleted: 0.22, message: "This property is redundant with almost no use internally.")
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 var credential: URLCredential? {
didSet {
sessionDelegate?.credential = self.credential
}
}
open private(set) var cache: URLCache?
public var useCache: Bool
public var validatingCache: Bool
fileprivate var _session: URLSession!
internal fileprivate(set) var sessionDelegate: SessionDelegate?
public var session: URLSession {
get {
if _session == nil {
self.sessionDelegate = SessionDelegate(fileProvider: self)
let config = URLSessionConfiguration.default
config.urlCache = cache
config.requestCachePolicy = .returnCacheDataElseLoad
_session = URLSession(configuration: config, delegate: sessionDelegate as URLSessionDelegate?, delegateQueue: self.operation_queue)
_session.sessionDescription = UUID().uuidString
initEmptySessionHandler(_session.sessionDescription!)
}
return _session
}
set {
assert(newValue.delegate is SessionDelegate, "session instances should have a SessionDelegate instance as delegate.")
_session = newValue
if _session.sessionDescription?.isEmpty ?? true {
_session.sessionDescription = UUID().uuidString
}
self.sessionDelegate = newValue.delegate as? SessionDelegate
initEmptySessionHandler(_session.sessionDescription!)
}
}
fileprivate var _longpollSession: URLSession?
/// This session has extended timeout up to 10 minutes, suitable for monitoring.
internal var longpollSession: URLSession {
if _longpollSession == nil {
let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = 600
_longpollSession = URLSession(configuration: config, delegate: nil, delegateQueue: nil)
}
return _longpollSession!
}
/**
This is parent initializer for subclasses. Using this method on `HTTPFileProvider` will fail as `type` is not implemented.
- Parameters:
- baseURL: Location of WebDAV server.
- credential: An `URLCredential` object with `user` and `password`.
- cache: A URLCache to cache downloaded files and contents.
*/
public init(baseURL: URL?, credential: URLCredential?, cache: URLCache?) {
// Make base url absolute and path as directory
let urlStr = baseURL?.absoluteString
self.baseURL = urlStr.flatMap { $0.hasSuffix("/") ? URL(string: $0) : URL(string: $0 + "/") }
self.useCache = false
self.validatingCache = true
self.cache = cache
self.credential = credential
#if swift(>=3.1)
let queueLabel = "FileProvider.\(Swift.type(of: self).type)"
#else
let queueLabel = "FileProvider.\(type(of: self).type)"
#endif
dispatch_queue = DispatchQueue(label: queueLabel, attributes: .concurrent)
operation_queue = OperationQueue()
operation_queue.name = "\(queueLabel).Operation"
}
public required convenience init?(coder aDecoder: NSCoder) {
fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.")
}
deinit {
if let sessionuuid = _session?.sessionDescription {
removeSessionHandler(for: sessionuuid)
}
if fileProviderCancelTasksOnInvalidating {
_session?.invalidateAndCancel()
} else {
_session?.finishTasksAndInvalidate()
}
}
public func encode(with aCoder: NSCoder) {
aCoder.encode(self.baseURL, forKey: "baseURL")
aCoder.encode(self.credential, forKey: "credential")
aCoder.encode(self.useCache, forKey: "useCache")
aCoder.encode(self.validatingCache, forKey: "validatingCache")
}
public static var supportsSecureCoding: Bool {
return true
}
open func copy(with zone: NSZone? = nil) -> Any {
fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.")
}
open func contentsOfDirectory(path: String, completionHandler: @escaping (_ contents: [FileObject], _ error: Error?) -> Void) {
fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.")
}
open func attributesOfItem(path: String, completionHandler: @escaping (_ attributes: FileObject?, _ error: Error?) -> Void) {
fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.")
}
open func storageProperties(completionHandler: @escaping (_ volumeInfo: VolumeObject?) -> Void) {
fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.")
}
open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping (_ files: [FileObject], _ error: Error?) -> Void) -> Progress? {
fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.")
}
open func isReachable(completionHandler: @escaping (Bool) -> Void) {
self.storageProperties { volume in
completionHandler(volume != nil)
}
}
open weak var fileOperationDelegate: FileOperationDelegate?
open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> Progress? {
let path = (atPath as NSString).appendingPathComponent(folderName) + "/"
return doOperation(.create(path: path), completionHandler: completionHandler)
}
open func moveItem(path: String, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
return doOperation(.move(source: path, destination: toPath), overwrite: overwrite, completionHandler: completionHandler)
}
open func copyItem(path: String, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
return doOperation(.copy(source: path, destination: toPath), overwrite: overwrite, completionHandler: completionHandler)
}
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> Progress? {
return doOperation(.remove(path: path), completionHandler: completionHandler)
}
open func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
// check file is not a folder
guard (try? localFile.resourceValues(forKeys: [.fileResourceTypeKey]))?.fileResourceType ?? .unknown == .regular else {
dispatch_queue.async {
completionHandler?(self.urlError(localFile.path, code: .fileIsDirectory))
}
return nil
}
let operation = FileOperationType.copy(source: localFile.absoluteString, destination: toPath)
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: operation) ?? true == true else {
return nil
}
let request = self.request(for: operation, overwrite: overwrite)
return upload_simple(toPath, request: request, localFile: localFile, operation: operation, completionHandler: completionHandler)
}
open func copyItem(path: String, toLocalURL destURL: URL, completionHandler: SimpleCompletionHandler) -> Progress? {
let operation = FileOperationType.copy(source: path, destination: destURL.absoluteString)
let request = self.request(for: operation)
let cantLoadError = urlError(path, code: .cannotLoadFromNetwork)
return self.download_simple(path: path, request: request, operation: operation, completionHandler: { [weak self] (tempURL, error) in
do {
if let error = error {
throw error
}
guard let tempURL = tempURL else {
throw cantLoadError
}
var coordError: NSError?
NSFileCoordinator().coordinate(writingItemAt: tempURL, options: .forMoving, writingItemAt: destURL, options: .forReplacing, error: &coordError, byAccessor: { (tempURL, destURL) in
do {
try FileManager.default.moveItem(at: tempURL, to: destURL)
completionHandler?(nil)
self?.delegateNotify(operation)
} catch {
completionHandler?(error)
self?.delegateNotify(operation, error: error)
}
})
if let error = coordError {
throw error
}
} catch {
completionHandler?(error)
self?.delegateNotify(operation, error: error)
}
})
}
/**
Progressively fetch data of file and returns fetched data in `progressHandler`.
If path specifies a directory, or if some other error occurs, data will be nil.
- Parameters:
- path: Path of file.
- progressHandler: a closure called every time a new `Data` is available.
- position: start position of data fetched.
- data: a portion of contents of file in a `Data` object.
- completionHandler: a closure with result of file contents or error.
- error: `Error` returned by system if occured.
- Returns: An `Progress` to get progress or cancel progress.
*/
open func contents(path: String, offset: Int64 = 0, responseHandler: ((_ response: URLResponse) -> Void)? = nil, progressHandler: @escaping (_ position: Int64, _ data: Data) -> Void, completionHandler: SimpleCompletionHandler) -> Progress? {
let operation = FileOperationType.fetch(path: path)
var request = self.request(for: operation)
if offset > 0 {
request.addValue("bytes \(offset)-", forHTTPHeaderField: "Range")
}
var position: Int64 = offset
return download_progressive(path: path, request: request, operation: operation, responseHandler: responseHandler, progressHandler: { data in
progressHandler(position, data)
position += Int64(data.count)
}, completionHandler: (completionHandler ?? { _ in return }))
}
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> Progress? {
if length == 0 || offset < 0 {
dispatch_queue.async {
completionHandler(Data(), nil)
}
return nil
}
let operation = FileOperationType.fetch(path: path)
var request = self.request(for: operation)
let cantLoadError = urlError(path, code: .cannotLoadFromNetwork)
request.setValue(rangeWithOffset: offset, length: length)
return self.download_simple(path: path, request: request, operation: operation, completionHandler: { (tempURL, error) in
do {
if let error = error {
throw error
}
guard let tempURL = tempURL else {
throw cantLoadError
}
let data = try Data(contentsOf: tempURL)
completionHandler(data, nil)
} catch {
completionHandler(nil, error)
}
})
}
public func writeContents(path: String, contents data: Data?, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
let operation = FileOperationType.modify(path: path)
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: operation) ?? true == true else {
return nil
}
let request = self.request(for: operation, overwrite: overwrite, attributes: [.contentModificationDateKey: Date()])
return upload_simple(path, request: request, data: data ?? Data(), operation: operation, completionHandler: completionHandler)
}
internal func request(for operation: FileOperationType, overwrite: Bool = false, attributes: [URLResourceKey: Any] = [:]) -> URLRequest {
fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.")
}
internal func serverError(with code: FileProviderHTTPErrorCode, path: String?, data: Data?) -> FileProviderHTTPError {
fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.")
}
internal func multiStatusHandler(source: String, data: Data, completionHandler: SimpleCompletionHandler) -> Void {
// WebDAV will override this function
}
fileprivate func doOperation(_ operation: FileOperationType, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> Progress? {
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: operation) ?? true == true else {
return nil
}
let progress = Progress(totalUnitCount: 1)
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
progress.kind = .file
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
let request = self.request(for: operation, overwrite: overwrite)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var serverError: FileProviderHTTPError?
if let response = response as? HTTPURLResponse, response.statusCode >= 300, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) {
serverError = self.serverError(with: code, path: operation.source, data: data)
}
if let response = response as? HTTPURLResponse, FileProviderHTTPErrorCode(rawValue: response.statusCode) == .multiStatus, let data = data {
self.multiStatusHandler(source: operation.source, data: data, completionHandler: completionHandler)
}
if serverError == nil && error == nil {
progress.completedUnitCount = 1
} else {
progress.cancel()
}
completionHandler?(serverError ?? error)
self.delegateNotify(operation, error: serverError ?? error)
})
task.taskDescription = operation.json
progress.cancellationHandler = { [weak task] in
task?.cancel()
}
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
task.resume()
return progress
}
/// This method should be used in subclasses to fetch directory content from servers which support paginated results.
/// Almost all HTTP based provider, except WebDAV, supports this method.
///
/// - Important: Please use `[weak self]` when implementing handlers to prevent retain cycles. In these cases,
/// return `nil` as the result of handler as the operation will be aborted.
///
/// - Parameters:
/// - path: path of directory which enqueued for listing, for informational use like errpr reporting.
/// - requestHandler: Get token of next page and returns appropriate `URLRequest` to be sent to server.
/// handler can return `nil` to cancel entire operation.
/// - token: Token of the page which `URLRequest` is needed, token will be `nil` for initial page. .
/// - pageHandler: Handler which is called after fetching results of a page to parse data. will return parse result as
/// array of `FileObject` or error if data is nil or parsing is failed. Method will not continue to next page if
/// `error` is returned, otherwise `nextToken` will be used for next page. `nil` value for `newToken` will indicate
/// last page of directory contents.
/// - data: Raw data returned from server. Handler should parse them and return files.
/// - progress: `Progress` object that `completedUnits` will be increased when a new `FileObject` is parsed in method.
/// - completionHandler: All file objects returned by `pageHandler` will be passed to this handler, or error if occured.
/// This handler will be called when `pageHandler` returns `nil for `newToken`.
/// - contents: all files parsed via `pageHandler` will be return aggregated.
/// - error: `Error` returned by server. `nil` means success. If exists, it means `contents` are incomplete.
internal func paginated(_ path: String, requestHandler: @escaping (_ token: String?) -> URLRequest?, pageHandler: @escaping (_ data: Data?, _ progress: Progress) -> (files: [FileObject], error: Error?, newToken: String?), completionHandler: @escaping (_ contents: [FileObject], _ error: Error?) -> Void) -> Progress {
let progress = Progress(totalUnitCount: -1)
self.paginated(path, startToken: nil, currentProgress: progress, previousResult: [], requestHandler: requestHandler, pageHandler: pageHandler, completionHandler: completionHandler)
return progress
}
// codebeat:disable[ARITY]
private func paginated(_ path: String, startToken: String?, currentProgress progress: Progress, previousResult: [FileObject], requestHandler: @escaping (_ token: String?) -> URLRequest?, pageHandler: @escaping (_ data: Data?, _ progress: Progress) -> (files: [FileObject], error: Error?, newToken: String?), completionHandler: @escaping (_ contents: [FileObject], _ error: Error?) -> Void) {
guard !progress.isCancelled, let request = requestHandler(startToken) else {
return
}
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
if let error = error {
completionHandler(previousResult, error)
return
}
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
let responseError = self.serverError(with: rCode, path: path, data: data)
completionHandler(previousResult, responseError)
return
}
let (newFiles, err, newToken) = pageHandler(data, progress)
if let error = err {
completionHandler(previousResult, error)
return
}
let files = previousResult + newFiles
if let newToken = newToken, !progress.isCancelled {
_ = self.paginated(path, startToken: newToken, currentProgress: progress, previousResult: files, requestHandler: requestHandler, pageHandler: pageHandler, completionHandler: completionHandler)
} else {
completionHandler(files, nil)
}
})
progress.cancellationHandler = { [weak task] in
task?.cancel()
}
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
task.resume()
}
// codebeat:enable[ARITY]
internal var maxUploadSimpleSupported: Int64 { return Int64.max }
internal func upload_simple(_ targetPath: String, request: URLRequest, data: Data? = nil, localFile: URL? = nil, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> Progress? {
let size: Int64
if let data = data {
size = Int64(data.count)
} else if let localFile = localFile {
let fSize = (try? localFile.resourceValues(forKeys: [.fileSizeKey]))?.fileSize
size = Int64(fSize ?? -1)
} else {
return nil
}
if size > maxUploadSimpleSupported {
let error = self.serverError(with: .payloadTooLarge, path: targetPath, data: nil)
completionHandler?(error)
self.delegateNotify(operation, error: error)
return nil
}
var progress = Progress(totalUnitCount: -1)
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
progress.kind = .file
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
progress.totalUnitCount = size
let taskHandler = { (task: URLSessionTask) -> Void in
completionHandlersForTasks[self.session.sessionDescription!]?[task.taskIdentifier] = { [weak self] error in
var responseError: FileProviderHTTPError?
if let code = (task.response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
// We can't fetch server result from delegate!
responseError = self?.serverError(with: rCode, path: targetPath, data: nil)
}
if !(responseError == nil && error == nil) {
progress.cancel()
}
completionHandler?(responseError ?? error)
self?.delegateNotify(operation, error: responseError ?? error)
}
task.taskDescription = operation.json
task.addObserver(self.sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesSent), options: .new, context: &progress)
progress.cancellationHandler = { [weak task] in
task?.cancel()
}
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
task.resume()
}
if let data = data {
let task = session.uploadTask(with: request, from: data)
taskHandler(task)
} else if let localFile = localFile {
var error: NSError?
NSFileCoordinator().coordinate(readingItemAt: localFile, options: .forUploading, error: &error, byAccessor: { (url) in
let task = self.session.uploadTask(with: request, fromFile: localFile)
taskHandler(task)
})
if let error = error {
completionHandler?(error)
}
}
return progress
}
internal func download_progressive(path: String, request: URLRequest, operation: FileOperationType, responseHandler: ((_ response: URLResponse) -> Void)? = nil, progressHandler: @escaping (_ data: Data) -> Void, completionHandler: @escaping (_ error: Error?) -> Void) -> Progress? {
var progress = Progress(totalUnitCount: -1)
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
progress.kind = .file
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
let task = session.dataTask(with: request)
if let responseHandler = responseHandler {
responseCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { response in
responseHandler(response)
}
}
dataCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { data in
progressHandler(data)
}
completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { error in
if error != nil {
progress.cancel()
}
completionHandler(error)
self.delegateNotify(operation, error: error)
}
task.taskDescription = operation.json
task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesReceived), options: .new, context: &progress)
task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToReceive), options: .new, context: &progress)
progress.cancellationHandler = { [weak task] in
task?.cancel()
}
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
task.resume()
return progress
}
internal func download_simple(path: String, request: URLRequest, operation: FileOperationType, completionHandler: @escaping ((_ tempURL: URL?, _ error: Error?) -> Void)) -> Progress? {
var progress = Progress(totalUnitCount: -1)
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
progress.kind = .file
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
let task = session.downloadTask(with: request)
completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { error in
if error != nil {
progress.cancel()
}
completionHandler(nil, error)
self.delegateNotify(operation, error: error)
}
downloadCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { tempURL in
guard let httpResponse = task.response as? HTTPURLResponse , httpResponse.statusCode < 300 else {
let code = FileProviderHTTPErrorCode(rawValue: (task.response as? HTTPURLResponse)?.statusCode ?? -1)
let errorData : Data? = try? Data(contentsOf: tempURL)
let serverError = code.flatMap { self.serverError(with: $0, path: path, data: errorData) }
if serverError != nil {
progress.cancel()
}
completionHandler(nil, serverError)
self.delegateNotify(operation)
return
}
completionHandler(tempURL, nil)
}
task.taskDescription = operation.json
task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesReceived), options: .new, context: &progress)
task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToReceive), options: .new, context: &progress)
progress.cancellationHandler = { [weak task] in
task?.cancel()
}
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
task.resume()
return progress
}
}
extension HTTPFileProvider: FileProvider { }
File diff suppressed because it is too large Load Diff
+237
View File
@@ -0,0 +1,237 @@
//
// LocalFileProvider.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2016 Mousavian. Distributed under MIT license.
//
import Foundation
/// Containts path, url and attributes of a local file or resource.
public final class LocalFileObject: FileObject {
internal override init(url: URL?, name: String, path: String) {
super.init(url: url, name: name, path: path)
}
/// Initiates a `LocalFileObject` with attributes of file in path.
public convenience init? (fileWithPath path: String, relativeTo relativeURL: URL?) {
var fileURL: URL?
var rpath = path.replacingOccurrences(of: relativeURL?.path ?? "", with: "", options: .anchored).replacingOccurrences(of: "/", with: "", options: .anchored)
if #available(iOS 9.0, macOS 10.11, *) {
fileURL = URL(fileURLWithPath: rpath, relativeTo: relativeURL)
} else {
rpath = rpath.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? rpath
fileURL = URL(string: rpath, relativeTo: relativeURL) ?? relativeURL
}
if let fileURL = fileURL {
self.init(fileWithURL: fileURL)
} else {
return nil
}
}
/// Initiates a `LocalFileObject` with attributes of file in url.
public convenience init?(fileWithURL fileURL: URL) {
do {
let values = try fileURL.resourceValues(forKeys: [.nameKey, .fileSizeKey, .totalFileSizeKey, .fileAllocatedSizeKey, .totalFileAllocatedSizeKey, .creationDateKey, .contentModificationDateKey, .fileResourceTypeKey, .isHiddenKey, .isWritableKey, .typeIdentifierKey, .generationIdentifierKey, .documentIdentifierKey])
let path = fileURL.relativePath.hasPrefix("/") ? fileURL.relativePath : "/" + fileURL.relativePath
self.init(url: fileURL, name: values.name ?? fileURL.lastPathComponent, path: path)
for (key, value) in values.allValues {
self.allValues[key] = value
}
} catch {
return nil
}
}
/// The total size allocated on disk for the file
open internal(set) var allocatedSize: Int64 {
get {
return allValues[.fileAllocatedSizeKey] as? Int64 ?? 0
}
set {
allValues[.fileAllocatedSizeKey] = Int(exactly: newValue) ?? Int.max
}
}
/// The document identifier is a value assigned by the kernel/system to a file or directory.
/// This value is used to identify the document regardless of where it is moved on a volume.
/// The identifier persists across system restarts.
open internal(set) var id: Int? {
get {
return allValues[.documentIdentifierKey] as? Int
}
set {
allValues[.documentIdentifierKey] = newValue
}
}
/// The revision of file, which changes when a file contents are modified.
/// Changes to attributes or other file metadata do not change the identifier.
open var rev: String? {
get {
let data = allValues[.generationIdentifierKey] as? Data
return data?.map { String(format: "%02hhx", $0) }.joined()
}
}
/// Count of children items of a driectory. It costs disk access for local directories.
open public(set) override var childrensCount: Int? {
get {
return try? FileManager.default.contentsOfDirectory(atPath: self.url.path).count
}
set {
//
}
}
}
internal final 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: .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 = { [weak self] in
guard let `self` = self else { return }
if Date().timeIntervalSinceReferenceDate < self.monitoredTime + 0.2 {
return
}
self.monitoredTime = Date().timeIntervalSinceReferenceDate
self.source.suspend()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25, execute: {
handler()
self.source.resume()
})
}
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))
}
}
class UndoBox: NSObject {
weak var provider: FileProvideUndoable?
let operation: FileOperationType
let undoOperation: FileOperationType
init(provider: FileProvideUndoable, operation: FileOperationType, undoOperation: FileOperationType) {
self.provider = provider
self.operation = operation
self.undoOperation = undoOperation
}
}
+515
View File
@@ -0,0 +1,515 @@
//
// OneDriveFileProvider.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2017 Mousavian. Distributed under MIT license.
//
import Foundation
import CoreGraphics
/**
Allows accessing to OneDrive stored files, either hosted on Microsoft servers or business coprporate one.
This provider doesn't cache or save files internally, however you can set `useCache` and `cache` properties
to use Foundation `NSURLCache` system.
- Note: You can pass file id instead of file path, e.g `"id:1234abcd"`, to point to a file or folder by ID.
- Note: Uploading files and data are limited to 100MB, for now.
*/
open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
override open class var type: String { return "OneDrive" }
/// Route to access file container on OneDrive. For default logined user use `.me` otherwise you can acesss
/// container based on drive id, group id, site id or user id for another user's default container
public enum Route: RawRepresentable {
/// Access to default container for current user
case me
/// Access to a specific drive by id
case drive(uuid: UUID)
/// Access to a default drive of a group by their id
case group(uuid: UUID)
/// Access to a default drive of a site by their id
case site(uuid: UUID)
/// Access to a default drive of a user by their id
case user(uuid: UUID)
public init?(rawValue: String) {
let components = rawValue.components(separatedBy: ";")
guard let type = components.first else {
return nil
}
if type == "me" {
self = .me
}
guard let uuid = components.last.flatMap({ UUID(uuidString: $0) }) else {
return nil
}
switch type {
case "drive":
self = .drive(uuid: uuid)
case "group":
self = .group(uuid: uuid)
case "site":
self = .site(uuid: uuid)
case "user":
self = .user(uuid: uuid)
default:
return nil
}
}
public var rawValue: String {
switch self {
case .me:
return "me;"
case .drive(uuid: let uuid):
return "drive;" + uuid.uuidString
case .group(uuid: let uuid):
return "group;" + uuid.uuidString
case .site(uuid: let uuid):
return "site;" + uuid.uuidString
case .user(uuid: let uuid):
return "user;" + uuid.uuidString
}
}
/// Return path component in URL for selected drive
var drivePath: String {
switch self {
case .me:
return "me/drive"
case .drive(uuid: let uuid):
return "drives/" + uuid.uuidString
case .group(uuid: let uuid):
return "groups/" + uuid.uuidString + "/drive"
case .site(uuid: let uuid):
return "sites/" + uuid.uuidString + "/drive"
case .user(uuid: let uuid):
return "users/" + uuid.uuidString + "/drive"
}
}
}
/// Route for container, default is `.me`.
open let route: Route
/**
Initializer for Onedrive provider with given client ID and Token.
These parameters must be retrieved via [Authentication for the OneDrive API](https://dev.onedrive.com/auth/readme.htm).
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.
- Parameters:
- credential: a `URLCredential` object with Client ID set as `user` and Token set as `password`.
- serverURL: server url, Set it if you are trying to connect OneDrive Business server, otherwise leave it
`nil` to connect to OneDrive Personal user.
- drive: drive name for user on server, default value is `root`.
- cache: A URLCache to cache downloaded files and contents.
*/
@available(*, deprecated, message: "use init(credential:, serverURL:, route:, cache:) instead.")
public init(credential: URLCredential?, serverURL: URL? = nil, drive: String?, cache: URLCache? = nil) {
let baseURL = serverURL?.absoluteURL ?? URL(string: "https://api.onedrive.com/")!
let refinedBaseURL = baseURL.absoluteString.hasSuffix("/") ? baseURL : baseURL.appendingPathComponent("")
self.route = drive.flatMap({ UUID(uuidString: $0) }).flatMap({ Route.drive(uuid: $0) }) ?? .me
super.init(baseURL: refinedBaseURL, credential: credential, cache: cache)
}
/**
Initializer for Onedrive provider with given client ID and Token.
These parameters must be retrieved via [Authentication for the OneDrive API](https://dev.onedrive.com/auth/readme.htm).
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. Also you can use [auth0/Lock](https://github.com/auth0/Lock.iOS-OSX) which provides graphical user interface.
- Parameters:
- credential: a `URLCredential` object with Client ID set as `user` and Token set as `password`.
- serverURL: server url, Set it if you are trying to connect OneDrive Business server, otherwise leave it
`nil` to connect to OneDrive Personal uses.
- route: drive name for user on server, default value is `.me`.
- cache: A URLCache to cache downloaded files and contents.
*/
public init(credential: URLCredential?, serverURL: URL? = nil, route: Route = .me, cache: URLCache? = nil) {
let baseURL = serverURL?.absoluteURL ?? URL(string: "https://api.onedrive.com/")!
let refinedBaseURL = baseURL.absoluteString.hasSuffix("/") ? baseURL : baseURL.appendingPathComponent("")
self.route = route
super.init(baseURL: refinedBaseURL, credential: credential, cache: cache)
}
public required convenience init?(coder aDecoder: NSCoder) {
let route: Route
if let driveId = aDecoder.decodeObject(forKey: "drive") as? String, let uuid = UUID(uuidString: driveId) {
route = .drive(uuid: uuid)
} else {
route = (aDecoder.decodeObject(forKey: "route") as? String).flatMap({ Route(rawValue: $0) }) ?? .me
}
self.init(credential: aDecoder.decodeObject(forKey: "credential") as? URLCredential,
serverURL: aDecoder.decodeObject(forKey: "baseURL") as? URL,
route: route)
self.useCache = aDecoder.decodeBool(forKey: "useCache")
self.validatingCache = aDecoder.decodeBool(forKey: "validatingCache")
}
open override func encode(with aCoder: NSCoder) {
super.encode(with: aCoder)
aCoder.encode(self.route.rawValue, forKey: "route")
}
open override func copy(with zone: NSZone? = nil) -> Any {
let copy = OneDriveFileProvider(credential: self.credential, serverURL: self.baseURL, route: self.route, cache: self.cache)
copy.delegate = self.delegate
copy.fileOperationDelegate = self.fileOperationDelegate
copy.useCache = self.useCache
copy.validatingCache = self.validatingCache
return copy
}
open override func contentsOfDirectory(path: String, completionHandler: @escaping (_ contents: [FileObject], _ error: Error?) -> Void) {
_ = paginated(path, requestHandler: { [weak self] (token) -> URLRequest? in
guard let `self` = self else { return nil }
let url = token.flatMap(URL.init(string:)) ?? self.url(of: path, modifier: "children")
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue(authentication: self.credential, with: .oAuth2)
return request
}, pageHandler: { [weak self] (data, _) -> (files: [FileObject], error: Error?, newToken: String?) in
guard let `self` = self else { return ([], nil, nil) }
guard let json = data?.deserializeJSON(), let entries = json["value"] as? [AnyObject] else {
let err = self.urlError(path, code: .badServerResponse)
return ([], err, nil)
}
var files = [FileObject]()
for entry in entries {
if let entry = entry as? [String: AnyObject], let file = OneDriveFileObject(baseURL: self.baseURL, route: self.route, json: entry) {
files.append(file)
}
}
return (files, nil, json["@odata.nextLink"] as? String)
}, completionHandler: completionHandler)
}
open override func attributesOfItem(path: String, completionHandler: @escaping (_ attributes: FileObject?, _ error: Error?) -> Void) {
var request = URLRequest(url: url(of: path))
request.httpMethod = "GET"
request.setValue(authentication: self.credential, with: .oAuth2)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var serverError: FileProviderHTTPError?
var fileObject: OneDriveFileObject?
if let response = response as? HTTPURLResponse {
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
serverError = code.flatMap { self.serverError(with: $0, path: path, data: data) }
if let json = data?.deserializeJSON(), let file = OneDriveFileObject(baseURL: self.baseURL, route: self.route, json: json) {
fileObject = file
}
}
completionHandler(fileObject, serverError ?? error)
})
task.resume()
}
open override func storageProperties(completionHandler: @escaping (_ volumeInfo: VolumeObject?) -> Void) {
var request = URLRequest(url: url(of: ""))
request.httpMethod = "GET"
request.setValue(authentication: self.credential, with: .oAuth2)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
guard let json = data?.deserializeJSON() else {
completionHandler(nil)
return
}
let volume = VolumeObject(allValues: [:])
volume.url = request.url
volume.name = json["name"] as? String
volume.creationDate = (json["createdDateTime"] as? String).flatMap { Date(rfcString: $0) }
volume.totalCapacity = (json["quota"]?["total"] as? NSNumber)?.int64Value ?? -1
volume.availableCapacity = (json["quota"]?["remaining"] as? NSNumber)?.int64Value ?? 0
completionHandler(volume)
})
task.resume()
}
open override func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping (_ files: [FileObject], _ error: Error?) -> Void) -> Progress? {
let queryStr = query.findValue(forKey: "name") as? String ?? query.findAllValues(forKey: nil).flatMap { $0.value as? String }.first
return paginated(path, requestHandler: { [weak self] (token) -> URLRequest? in
guard let `self` = self else { return nil }
let url: URL
if let next = token.flatMap(URL.init(string:)) {
url = next
} else {
let bURL = self.baseURL!.appendingPathComponent(self.route.drivePath).appendingPathComponent("root/search")
var components = URLComponents(url: bURL, resolvingAgainstBaseURL: false)!
let qItem = URLQueryItem(name: "q", value: (queryStr ?? "*"))
components.queryItems = [qItem]
if recursive {
components.queryItems?.append(URLQueryItem(name: "expand", value: "children"))
}
url = components.url!
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
return request
}, pageHandler: { [weak self] (data, progress) -> (files: [FileObject], error: Error?, newToken: String?) in
guard let `self` = self else { return ([], nil, nil) }
guard let json = data?.deserializeJSON(), let entries = json["value"] as? [AnyObject] else {
let err = self.urlError(path, code: .badServerResponse)
return ([], err, nil)
}
var foundFiles = [FileObject]()
for entry in entries {
if let entry = entry as? [String: AnyObject], let file = OneDriveFileObject(baseURL: self.baseURL, route: self.route, json: entry), query.evaluate(with: file.mapPredicate()) {
foundFiles.append(file)
foundItemHandler?(file)
}
}
return (foundFiles, nil, json["@odata.nextLink"] as? String)
}, completionHandler: completionHandler)
}
open func url(of path: String, modifier: String? = nil) -> URL {
var url: URL = baseURL!
var rpath: String = path
let isId = path.hasPrefix("id:")
url.appendPathComponent(route.drivePath)
if isId {
url.appendPathComponent("root:")
} else {
url.appendPathComponent("items")
}
rpath = rpath.trimmingCharacters(in: pathTrimSet)
switch (modifier == nil, rpath.isEmpty, isId) {
case (true, false, _):
url.appendPathComponent(rpath)
case (true, true, _):
break
case (false, true, _):
url.appendPathComponent(modifier!)
case (false, false, true):
url.appendPathComponent(rpath)
url.appendPathComponent(modifier!)
case (false, false, false):
url.appendPathComponent(rpath + ":")
url.appendPathComponent(modifier!)
}
return url
}
open override func isReachable(completionHandler: @escaping (Bool) -> Void) {
var request = URLRequest(url: url(of: ""))
request.httpMethod = "HEAD"
request.setValue(authentication: credential, with: .oAuth2)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
let status = (response as? HTTPURLResponse)?.statusCode ?? 400
completionHandler(status == 200)
})
task.resume()
}
override func request(for operation: FileOperationType, overwrite: Bool = false, attributes: [URLResourceKey : Any] = [:]) -> URLRequest {
func correctPath(_ path: String) -> String {
if path.hasPrefix("id:") {
return path
}
var p = path.hasPrefix("/") ? path : "/" + path
if p.hasSuffix("/") {
p.remove(at: p.index(before:p.endIndex))
}
return p
}
let method: String
let url: URL
switch operation {
case .fetch(path: let path):
method = "GET"
url = self.url(of: path, modifier: "content")
case .modify(path: let path):
method = "PUT"
let queryStr = overwrite ? "" : "?@name.conflictBehavior=fail"
url = self.url(of: path, modifier: "content\(queryStr)")
case .create(path: let path):
method = "CREATE"
url = self.url(of: path)
case .copy(let source, let dest) where !source.hasPrefix("file://") && !dest.hasPrefix("file://"):
method = "POST"
url = self.url(of: source)
case .copy(let source, let dest) where source.hasPrefix("file://"):
method = "PUT"
let queryStr = overwrite ? "" : "?@name.conflictBehavior=fail"
url = self.url(of: dest, modifier: "content\(queryStr)")
case .copy(let source, let dest) where dest.hasPrefix("file://"):
method = "GET"
url = self.url(of: source, modifier: "content")
case .move(source: let source, destination: _):
method = "PATCH"
url = self.url(of: source)
case .remove(path: let path):
method = "DELETE"
url = self.url(of: path)
default: // link
fatalError("Unimplemented operation \(operation.description) in \(#file)")
}
var request = URLRequest(url: url)
request.httpMethod = method
request.setValue(authentication: self.credential, with: .oAuth2)
// Remove gzip to fix availability of progress per (Oleg Marchik)[https://github.com/evilutioner] PR (#61)
request.setValue(acceptEncoding: .deflate)
request.addValue(acceptEncoding: .identity)
switch operation {
case .copy(let source, let dest) where !source.hasPrefix("file://") && !dest.hasPrefix("file://"),
.move(source: let source, destination: let dest):
request.setValue(contentType: .json)
let cdest = correctPath(dest) as NSString
var parentRefrence: [String: AnyObject] = [:]
if cdest.hasPrefix("id:") {
parentRefrence["id"] = cdest.components(separatedBy: "/").first as NSString?
switch self.route {
case .drive(uuid: let uuid):
parentRefrence["driveId"] = uuid.uuidString as NSString
default:
break
}
} else {
parentRefrence["path"] = cdest.deletingLastPathComponent as NSString
}
var requestDictionary = [String: AnyObject]()
requestDictionary["parentReference"] = parentRefrence as NSDictionary
requestDictionary["name"] = (cdest as NSString).lastPathComponent as NSString
request.httpBody = Data(jsonDictionary: requestDictionary)
default:
break
}
return request
}
override func serverError(with code: FileProviderHTTPErrorCode, path: String?, data: Data?) -> FileProviderHTTPError {
let errorDesc: String?
if let response = data?.deserializeJSON() {
errorDesc = response["error"]?["message"] as? String
} else {
errorDesc = data.flatMap({ String(data: $0, encoding: .utf8) })
}
return FileProviderOneDriveError(code: code, path: path ?? "", errorDescription: errorDesc)
}
override var maxUploadSimpleSupported: Int64 {
return 104_857_600 // 100MB
}
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()
}
open func publicLink(to path: String, completionHandler: @escaping ((_ link: URL?, _ attribute: FileObject?, _ expiration: Date?, _ error: Error?) -> Void)) {
var request = URLRequest(url: self.url(of: path, modifier: "action.createLink"))
request.httpMethod = "POST"
let requestDictionary: [String: AnyObject] = ["type": "view" as NSString]
request.httpBody = Data(jsonDictionary: requestDictionary)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var serverError: FileProviderHTTPError?
var link: URL?
if let response = response as? HTTPURLResponse {
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
serverError = code.flatMap { self.serverError(with: $0, path: path, data: data) }
if let json = data?.deserializeJSON() {
if let linkDic = json["link"] as? NSDictionary, let linkStr = linkDic["webUrl"] as? String {
link = URL(string: linkStr)
}
}
}
completionHandler(link, nil, nil, serverError ?? error)
})
task.resume()
}
}
extension OneDriveFileProvider: ExtendedFileProvider {
open func thumbnailOfFileSupported(path: String) -> Bool {
return true
}
open 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
}
}
open func thumbnailOfFile(path: String, dimension: CGSize?, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void)) {
let url: URL
if let dimension = dimension {
url = self.url(of: path, modifier: "thumbnails/0/=c\(dimension.width)x\(dimension.height)/content")
} else {
url = self.url(of: path, modifier: "thumbnails/0/small/content")
}
var request = URLRequest(url: url)
request.setValue(authentication: credential, with: .oAuth2)
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 = self.serverError(with: rCode, path: path, data: data)
completionHandler(nil, responseError)
return
}
if let data = data {
image = ImageClass(data: data)
}
completionHandler(image, error)
})
task.resume()
}
open func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String : Any], _ keys: [String], _ error: Error?) -> Void)) {
var request = URLRequest(url: url(of: path))
request.httpMethod = "GET"
request.setValue(authentication: credential, with: .oAuth2)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var serverError: FileProviderHTTPError?
var dic = [String: Any]()
var keys = [String]()
if let response = response as? HTTPURLResponse {
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
serverError = code.flatMap { self.serverError(with: $0, path: path, data: data) }
if let json = data?.deserializeJSON() {
(dic, keys) = self.mapMediaInfo(json)
}
}
completionHandler(dic, keys, serverError ?? error)
})
task.resume()
}
}
+166
View File
@@ -0,0 +1,166 @@
//
// OneDriveHelper.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2017 Mousavian. Distributed under MIT license.
//
import Foundation
/// Error returned by OneDrive server when trying to access or do operations on a file or folder.
public struct FileProviderOneDriveError: FileProviderHTTPError {
public let code: FileProviderHTTPErrorCode
public let path: String
public let errorDescription: String?
}
/// Containts path, url and attributes of a OneDrive file or resource.
public final class OneDriveFileObject: FileObject {
internal init(baseURL: URL?, name: String, path: String) {
let rpath = (URL(string:path)?.appendingPathComponent(name).absoluteString)!.replacingOccurrences(of: "/", with: "", options: .anchored)
let url = URL(string: rpath, relativeTo: baseURL) ?? URL(string: rpath)!
super.init(url: url, name: name, path: rpath.removingPercentEncoding ?? path)
}
internal convenience init? (baseURL: URL?, route: OneDriveFileProvider.Route, jsonStr: String) {
guard let json = jsonStr.deserializeJSON() else { return nil }
self.init(baseURL: baseURL, route: route, json: json)
}
internal convenience init? (baseURL: URL?, route: OneDriveFileProvider.Route, json: [String: AnyObject]) {
guard let name = json["name"] as? String else { return nil }
guard let path = json["parentReference"]?["path"] as? String else { return nil }
var lPath = path.replacingOccurrences(of: route.drivePath, with: "", options: .anchored, range: nil)
lPath = lPath.replacingOccurrences(of: "/:", with: "", options: .anchored)
lPath = lPath.replacingOccurrences(of: "//", with: "", options: .anchored)
self.init(baseURL: baseURL, name: name, path: lPath)
self.size = (json["size"] as? NSNumber)?.int64Value ?? -1
self.childrensCount = json["folder"]?["childCount"] as? Int
self.modifiedDate = (json["lastModifiedDateTime"] as? String).flatMap { Date(rfcString: $0) }
self.creationDate = (json["createdDateTime"] as? String).flatMap { Date(rfcString: $0) }
self.type = json["folder"] != nil ? .directory : .regular
self.contentType = (json["file"]?["mimeType"] as? String).flatMap(ContentMIMEType.init(rawValue:)) ?? .stream
self.id = json["id"] as? String
self.entryTag = json["eTag"] as? String
let hashes = json["file"]?["hashes"] as? NSDictionary
// checks for both sha1 or quickXor. First is available in personal drives, second in business one.
self.hash = (hashes?["sha1Hash"] as? String) ?? (hashes?["quickXorHash"] as? String)
}
/// The document identifier is a value assigned by the OneDrive to a file.
/// This value is used to identify the document regardless of where it is moved on a volume.
open internal(set) var id: String? {
get {
return allValues[.fileResourceIdentifierKey] as? String
}
set {
allValues[.fileResourceIdentifierKey] = newValue
}
}
/// MIME type of file contents returned by OneDrive server.
open internal(set) var contentType: ContentMIMEType {
get {
return (allValues[.mimeTypeKey] as? String).flatMap(ContentMIMEType.init(rawValue:)) ?? .stream
}
set {
allValues[.mimeTypeKey] = newValue.rawValue
}
}
/// HTTP E-Tag, can be used to mark changed files.
open internal(set) var entryTag: String? {
get {
return allValues[.entryTagKey] as? String
}
set {
allValues[.entryTagKey] = newValue
}
}
/// Calculated hash from OneDrive server. Hex string SHA1 in personal or Base65 string [QuickXOR](https://dev.onedrive.com/snippets/quickxorhash.htm) in business drives.
open internal(set) var hash: String? {
get {
return allValues[.documentIdentifierKey] as? String
}
set {
allValues[.documentIdentifierKey] = newValue
}
}
}
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]()
func add(key: String, value: Any?) {
if let value = value {
keys.append(key)
dic[key] = value
}
}
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 {
add(key: "Dimensions", value: "\(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
let latStr = OneDriveFileProvider.decimalFormatter.string(from: NSNumber(value: latitude))!
let longStr = OneDriveFileProvider.decimalFormatter.string(from: NSNumber(value: longitude))!
add(key: "Location", value: "\(latStr), \(longStr)")
}
if let parent = json["image"] as? [String: Any] ?? json["video"] as? [String: Any], let duration = parent["duration"] as? UInt64 {
add(key: "Duration", value: (TimeInterval(duration) / 1000).formatshort)
}
if let timeTakenStr = json["takenDateTime"] as? String, let timeTaken = Date(rfcString: timeTakenStr) {
OneDriveFileProvider.dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
add(key: "Date taken", value: OneDriveFileProvider.dateFormatter.string(from: timeTaken))
}
if let photo = json["photo"] as? [String: Any] {
add(key: "Device make", value: photo["cameraMake"] as? String)
add(key: "Device model", value: photo["cameraModel"] as? String)
add(key: "focalLength", value: photo["focalLength"] as? Double)
add(key: "fNumber", value: photo["fNumber"] as? Double)
if let expNom = photo["exposureNumerator"] as? Double, let expDen = photo["exposureDenominator"] as? Double {
add(key: "Exposure time", value: "\(Int(expNom))/\(Int(expDen))")
}
add(key: "ISO speed", value: photo["iso"] as? Int64)
}
if let audio = json["audio"] as? [String: Any] {
for (key, value) in audio {
if key == "bitrate" || key == "isVariableBitrate" { continue }
let casedKey = spaceCamelCase(key)
add(key: casedKey, value: value)
}
}
add(key: "Bitrate", value: (json["video"] as? NSDictionary)?["bitrate"] as? Int)
return (dic, keys)
}
}
+376
View File
@@ -0,0 +1,376 @@
//
// SessionDelegate.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2016 Mousavian. Distributed under MIT license.
//
import Foundation
/// A protocol defines properties for errors returned by HTTP/S based providers.
/// Including Dropbox, OneDrive and WebDAV.
public protocol FileProviderHTTPError: Error, CustomStringConvertible {
/// HTTP status codes as an enum.
typealias Code = FileProviderHTTPErrorCode
/// HTTP status code returned for error by server.
var code: FileProviderHTTPErrorCode { get }
/// Path of file/folder casued that error
var path: String { get }
/// Contents returned by server as error description
var errorDescription: String? { get }
}
extension FileProviderHTTPError {
public var description: String {
return code.description
}
public var localizedDescription: String {
return description
}
}
internal var completionHandlersForTasks = [String: [Int: SimpleCompletionHandler]]()
internal var downloadCompletionHandlersForTasks = [String: [Int: (URL) -> Void]]()
internal var dataCompletionHandlersForTasks = [String: [Int: (Data) -> Void]]()
internal var responseCompletionHandlersForTasks = [String: [Int: (URLResponse) -> Void]]()
internal func initEmptySessionHandler(_ uuid: String) {
completionHandlersForTasks[uuid] = [:]
downloadCompletionHandlersForTasks[uuid] = [:]
dataCompletionHandlersForTasks[uuid] = [:]
}
internal func removeSessionHandler(for uuid: String) {
_ = completionHandlersForTasks.removeValue(forKey: uuid)
_ = downloadCompletionHandlersForTasks.removeValue(forKey: uuid)
_ = dataCompletionHandlersForTasks.removeValue(forKey: uuid)
}
/// All objects set to `FileProviderRemote.session` must be an instance of this class
final public class SessionDelegate: NSObject, URLSessionDataDelegate, URLSessionDownloadDelegate, URLSessionStreamDelegate {
weak var fileProvider: (FileProviderBasicRemote & FileProviderOperations)?
var credential: URLCredential?
public init(fileProvider: FileProviderBasicRemote & FileProviderOperations) {
self.fileProvider = fileProvider
self.credential = fileProvider.credential
}
open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let progress = context?.load(as: Progress.self), let newVal = change?[.newKey] as? Int64 {
switch keyPath ?? "" {
case #keyPath(URLSessionTask.countOfBytesReceived):
progress.completedUnitCount = newVal
if let startTime = progress.userInfo[ProgressUserInfoKey.startingTimeKey] as? Date, let task = object as? URLSessionTask {
let elapsed = Date().timeIntervalSince(startTime)
let throughput = Double(newVal) / elapsed
progress.setUserInfoObject(NSNumber(value: throughput), forKey: .throughputKey)
if task.countOfBytesExpectedToReceive > 0 {
let remain = task.countOfBytesExpectedToReceive - task.countOfBytesReceived
let estimatedTimeRemaining = Double(remain) / elapsed
progress.setUserInfoObject(NSNumber(value: estimatedTimeRemaining), forKey: .estimatedTimeRemainingKey)
}
}
case #keyPath(URLSessionTask.countOfBytesSent):
progress.completedUnitCount = newVal
if let startTime = progress.userInfo[ProgressUserInfoKey.startingTimeKey] as? Date, let task = object as? URLSessionTask {
let elapsed = Date().timeIntervalSince(startTime)
let throughput = Double(newVal) / elapsed
progress.setUserInfoObject(NSNumber(value: throughput), forKey: .throughputKey)
if task.countOfBytesExpectedToSend > 0 {
let remain = task.countOfBytesExpectedToSend - task.countOfBytesSent
let estimatedTimeRemaining = Double(remain) / elapsed
progress.setUserInfoObject(NSNumber(value: estimatedTimeRemaining), forKey: .estimatedTimeRemainingKey)
}
}
case #keyPath(URLSessionTask.countOfBytesExpectedToReceive), #keyPath(URLSessionTask.countOfBytesExpectedToSend):
progress.totalUnitCount = newVal
default:
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
}
// codebeat:disable[ARITY]
public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if task is URLSessionUploadTask {
task.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesSent))
//task.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToSend))
} else if task is URLSessionDownloadTask {
task.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesReceived))
task.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToReceive))
}
_ = dataCompletionHandlersForTasks[session.sessionDescription!]?.removeValue(forKey: task.taskIdentifier)
if !(error == nil && task is URLSessionDownloadTask) {
let completionHandler = completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] ?? nil
completionHandler?(error)
_ = completionHandlersForTasks[session.sessionDescription!]?.removeValue(forKey: task.taskIdentifier)
}
guard let json = task.taskDescription?.deserializeJSON(),
let op = FileOperationType(json: json), let fileProvider = fileProvider else {
return
}
switch op {
case .fetch:
if task is URLSessionDataTask {
task.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesReceived))
task.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToReceive))
}
default:
break
}
if !(task is URLSessionDownloadTask), case FileOperationType.fetch = op {
return
}
if #available(iOS 9.0, macOS 10.11, *) {
if task is URLSessionStreamTask {
return
}
}
fileProvider.delegateNotify(op, error: error)
}
public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
let dcompletionHandler = downloadCompletionHandlersForTasks[session.sessionDescription!]?[downloadTask.taskIdentifier]
dcompletionHandler?(location)
_ = downloadCompletionHandlersForTasks[session.sessionDescription!]?.removeValue(forKey: downloadTask.taskIdentifier)
_ = completionHandlersForTasks[session.sessionDescription!]?.removeValue(forKey: downloadTask.taskIdentifier)
}
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
let handler = responseCompletionHandlersForTasks[session.sessionDescription!]?[dataTask.taskIdentifier] ?? nil
handler?(response)
completionHandler(.allow)
_ = responseCompletionHandlersForTasks[session.sessionDescription!]?.removeValue(forKey: dataTask.taskIdentifier)
}
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
if let completionHandler = dataCompletionHandlersForTasks[session.sessionDescription!]?[dataTask.taskIdentifier] {
if let json = dataTask.taskDescription?.deserializeJSON(),
let op = FileOperationType(json: json), let fileProvider = fileProvider {
fileProvider.delegateNotify(op, progress: Double(dataTask.countOfBytesReceived) / Double(dataTask.countOfBytesExpectedToReceive))
}
completionHandler(data)
}
}
public func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
guard let json = task.taskDescription?.deserializeJSON(),
let op = FileOperationType(json: json), let fileProvider = fileProvider else {
return
}
switch op {
case .create(path: let path):
if path.hasSuffix("/") { return }
break
case .modify:
break
case .copy(source: let source, destination: _) where source.hasPrefix("file://"):
break
default:
return
}
fileProvider.delegateNotify(op, progress: Double(totalBytesSent) / Double(totalBytesExpectedToSend))
}
public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
if totalBytesExpectedToWrite == NSURLSessionTransferSizeUnknown { return }
guard let json = downloadTask.taskDescription?.deserializeJSON(),
let op = FileOperationType(json: json), let fileProvider = fileProvider else {
return
}
fileProvider.delegateNotify(op, progress: Double(totalBytesWritten) / Double(totalBytesExpectedToWrite))
}
public func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
authenticate(didReceive: challenge, completionHandler: completionHandler)
}
public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
authenticate(didReceive: challenge, completionHandler: completionHandler)
}
func authenticate(didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
switch (challenge.previousFailureCount, credential != nil) {
case (0...1, true):
completionHandler(.useCredential, credential)
case (0, false):
completionHandler(.useCredential, challenge.proposedCredential)
default:
completionHandler(.performDefaultHandling, nil)
}
}
}
/// HTTP status codes as an enum.
public enum FileProviderHTTPErrorCode: Int, CustomStringConvertible {
/// `Continue` informational status with HTTP code 100
case `continue` = 100
/// `Switching Protocols` informational status with HTTP code 101
case switchingProtocols = 101
/// `Processing` informational status with HTTP code 102
case processing = 102
/// `OK` success status with HTTP code 200
case ok = 200
/// `Created` success status with HTTP code 201
case created = 201
/// `Accepted` success status with HTTP code 202
case accepted = 202
/// `Non Authoritative Information` success status with HTTP code 203
case nonAuthoritativeInformation = 203
/// `No Content` success status with HTTP code 204
case noContent = 204
/// `ResetcContent` success status with HTTP code 205
case resetContent = 205
/// `Partial Content` success status with HTTP code 206
case partialContent = 206
/// `Multi Status` success status with HTTP code 207
case multiStatus = 207
/// `Already Reported` success status with HTTP code 208
case alreadyReported = 208
/// `IM Used` success status with HTTP code 226
case imUsed = 226
/// `Multiple Choices` redirection status with HTTP code 300
case multipleChoices = 300
/// `Moved Permanently` redirection status with HTTP code 301
case movedPermanently = 301
/// `Found` redirection status with HTTP code 302
case found = 302
/// `See Other` redirection status with HTTP code 303
case seeOther = 303
/// `Not Modified` redirection status with HTTP code 304
case notModified = 304
/// `Use Proxy` redirection status with HTTP code 305
case useProxy = 305
/// `Switch Proxy` redirection status with HTTP code 306
case switchProxy = 306
/// `Temporary Redirect` redirection status with HTTP code 307
case temporaryRedirect = 307
/// `Permanent Redirect` redirection status with HTTP code 308
case permanentRedirect = 308
/// `Bad Request` client error status with HTTP code 400
case badRequest = 400
/// `Unauthorized` client error status with HTTP code 401
case unauthorized = 401
/// `Payment Required` client error status with HTTP code 402
case paymentRequired = 402
/// `Forbidden` client error status with HTTP code 403
case forbidden = 403
/// `Not Found` client error status with HTTP code 404
case notFound = 404
/// `Method Not Allowed` client error status with HTTP code 405
case methodNotAllowed = 405
/// `Not Acceptable` client error status with HTTP code 406
case notAcceptable = 406
/// `Proxy Authentication Required` client error status with HTTP code 407
case proxyAuthenticationRequired = 407
/// `Request Timeout` client error status with HTTP code 408
case requestTimeout = 408
/// `Conflict` client error status with HTTP code 409
case conflict = 409
/// `Gone` client error status with HTTP code 410
case gone = 410
/// `Length Required` client error status with HTTP code 411
case lengthRequired = 411
/// `Precondition Failed` client error status with HTTP code 412
case preconditionFailed = 412
/// `Payload Too Large` client error status with HTTP code 413
case payloadTooLarge = 413
/// `URI Too Long` client error status with HTTP code 414
case uriTooLong = 414
/// `Unsupported Media Type` status with HTTP code 415
case unsupportedMediaType = 415
/// `Range Not Satisfiable` client error status with HTTP code 416
case rangeNotSatisfiable = 416
/// `Expectation Failed` client error status with HTTP code 417
case expectationFailed = 417
/// `Misdirected Request` client error status with HTTP code 421
case misdirectedRequest = 421
/// `Unprocessable Entity` client error status with HTTP code 422
case unprocessableEntity = 422
/// `Locked` client error status with HTTP code 423
case locked = 423
/// `Failed Dependency` client error status with HTTP code 424
case failedDependency = 424
/// `Unordered Collection` client error status with HTTP code 425
case unorderedCollection = 425
/// `Upgrade Required` client error status with HTTP code 426
case upgradeRequired = 426
/// `Precondition Required` client error status with HTTP code 428
case preconditionRequired = 428
/// `Too Many Requests` client error status with HTTP code 429
case tooManyRequests = 429
/// `Request Header Fields Too Large` client error status with HTTP code 431
case requestHeaderFieldsTooLarge = 431
/// `Unavailable For Legal Reasons` client error status with HTTP code 451
case unavailableForLegalReasons = 451
/// `Internal Server Error` server error status with HTTP code 500
case internalServerError = 500
/// `Bad Gateway` server error status with HTTP code 502
case badGateway = 502
/// `Service Unavailable` server error status with HTTP code 503
case serviceUnavailable = 503
/// `Gateway Timeout` server error status with HTTP code 504
case gatewayTimeout = 504
/// `HTTP Version Not Supported` server error status with HTTP code 505
case httpVersionNotSupported = 505
/// `Variant Also Negotiates` server error status with HTTP code 506
case variantAlsoNegotiates = 506
/// `Insufficient Storage` server error status with HTTP code 507
case insufficientStorage = 507
/// `Loop Detected` server error status with HTTP code 508
case loopDetected = 508
/// `Bandwidth Limit Exceeded` server error status with HTTP code 509
case bandwidthLimitExceeded = 509
/// `Not Extended` server error status with HTTP code 510
case notExtended = 510
/// `Network Authentication Required` server error status with HTTP code 511
case networkAuthenticationRequired = 511
fileprivate static let status1xx: [Int: String] = [100: "Continue", 101: "Switching Protocols", 102: "Processing"]
fileprivate static let status2xx: [Int: String] = [200: "OK", 201: "Created", 202: "Accepted", 203: "Non-Authoritative Information", 204: "No Content", 205: "Reset Content", 206: "Partial Content", 207: "Multi-Status", 208: "Already Reported", 226: "IM Used"]
fileprivate static let status3xx: [Int: String] = [300: "Multiple Choices", 301: "Moved Permanently", 302: "Found", 303: "See Other", 304: "Not Modified", 305: "Use Proxy", 306: "Switch Proxy", 307: "Temporary Redirect", 308: "Permanent Redirect"]
fileprivate static let status4xx: [Int: String] = [400: "Bad Request", 401: "Unauthorized/Expired Session", 402: "Payment Required", 403: "Forbidden", 404: "Not Found", 405: "Method Not Allowed", 406: "Not Acceptable", 407: "Proxy Authentication Required", 408: "Request Timeout", 409: "Conflict", 410: "Gone", 411: "Length Required", 412: "Precondition Failed", 413: "Payload Too Large", 414: "URI Too Long", 415: "Unsupported Media Type", 416: "Range Not Satisfiable", 417: "Expectation Failed", 421: "Misdirected Request", 422: "Unprocessable Entity", 423: "Locked", 424: "Failed Dependency", 425: "Unordered Collection", 426: "Upgrade Required", 428: "Precondition Required", 429: "Too Many Requests", 431: "Request Header Fields Too Large", 451: "Unavailable For Legal Reasons"]
fileprivate static let status5xx: [Int: String] = [500: "Internal Server Error", 501: "Not Implemented", 502: "Bad Gateway", 503: "Service Unavailable", 504: "Gateway Timeout", 505: "HTTP Version Not Supported", 506: "Variant Also Negotiates", 507: "Insufficient Storage", 508: "Loop Detected", 509: "Bandwidth Limit Exceeded", 510: "Not Extended", 511: "Network Authentication Required"]
public var description: String {
switch self.rawValue {
case 100...102: return FileProviderHTTPErrorCode.status1xx[self.rawValue]!
case 200...208, 226: return FileProviderHTTPErrorCode.status2xx[self.rawValue]!
case 300...308: return FileProviderHTTPErrorCode.status3xx[self.rawValue]!
case 400...417, 421...426: fallthrough
case 428, 429, 431, 451: return FileProviderHTTPErrorCode.status4xx[self.rawValue]!
case 500...511: return FileProviderHTTPErrorCode.status5xx[self.rawValue]!
default: return typeDescription
}
}
public var localizedDescription: String {
return HTTPURLResponse.localizedString(forStatusCode: self.rawValue)
}
/// Description of status based on first digit which indicated fail or success.
public var typeDescription: String {
switch self.rawValue {
case 100...199: return "Informational"
case 200...299: return "Success"
case 300...399: return "Redirection"
case 400...499: return "Client Error"
case 500...599: return "Server Error"
default: return "Unknown Error"
}
}
}
+145 -105
View File
@@ -8,116 +8,162 @@
import Foundation
internal func encode<T>(inout value: T) -> NSData {
return withUnsafePointer(&value) { p in
NSData(bytes: p, length: sizeofValue(value))
}
}
internal func encode<T>(value: T) -> NSData {
var value = value
return withUnsafePointer(&value) { p in
NSData(bytes: p, length: sizeofValue(value))
}
}
internal func decode<T>(data: NSData) -> T {
let pointer = UnsafeMutablePointer<T>.alloc(sizeof(T.Type))
data.getBytes(pointer, length: sizeof(T.Type))
return pointer.move()
}
// This client implementation is for little-endian platform, namely x86, x64 & arm
// For big-endian platforms like PowerPC, there must be a huge overhaul
class SMBProtocolClient: TCPSocketClient {
var currentMessageID: UInt64 = 0
protocol FileProviderSMBTaskDelegate: class {
func receivedResponse(client: FileProviderSMBTask, response: SMBResponse, for: SMBRequest)
}
class FileProviderSMBTask: FileProviderStreamTask {
var timeout: TimeInterval = 30
func negotiateToSMB2() -> SMB2.NegotiateResponse? {
let smbHeader = SMB2.Header(command: .NEGOTIATE, creditRequestResponse: 126, messageId: messageId(), treeId: 0, sessionId: 0)
currentMessageID += 1
let negMessage = SMB2.NegotiateRequest(request: SMB2.NegotiateRequest.Header(capabilities: []))
SMBProtocolClient.createSMB2Message(smbHeader, message: negMessage)
do {
try self.send(data: nil)
} catch _ {
return nil
}
self.waitUntilResponse()
let response = try? SMBProtocolClient.digestSMB2Message(dataReceived)
return response??.message as? SMB2.NegotiateResponse
}
func sessionSetupForSMB2() -> SMB2.SessionSetupResponse? {
return nil
}
func messageId() -> UInt64 {
private(set) var lastMessageID: UInt64 = 0
private(set) var sessionId: UInt64 = 0
private func messageId() -> UInt64 {
defer {
currentMessageID += 1
lastMessageID += 1
}
return currentMessageID
return lastMessageID
}
// MARK: create and analyse messages
private(set) var establishedTrees = Array<SMB2.TreeConnectResponse>()
private(set) var requestStack = [Int: SMBRequest]()
private(set) var responseStack = [Int: SMBResponse]()
class func determineSMBVersion(data: NSData) -> Float {
var smbverChar: Int8 = 0
data.getBytes(&smbverChar, length: 1)
weak var delegate: FileProviderSMBTaskDelegate?
func sendNegotiate(completionHandler: SimpleCompletionHandler) -> UInt64 {
let mId = messageId()
let smbHeader = SMB2.Header(command: .NEGOTIATE, creditRequestResponse: UInt16(126), messageId: mId, treeId: UInt32(0), sessionId: UInt64(0))
let msg = SMB2.NegotiateRequest()
let data = createSMB2Message(header: smbHeader, message: msg)
self.write(data, timeout: timeout, completionHandler: { (e) in
completionHandler?(e)
})
return mId
}
func sendSessionSetup(completionHandler: SimpleCompletionHandler) -> UInt64 {
let mId = messageId()
let credit = UInt16(sessionId > 0 ? 124 : 125)
let smbHeader = SMB2.Header(command: SMB2.Command.SESSION_SETUP, creditRequestResponse: credit, messageId: mId, treeId: UInt32(0), sessionId: sessionId)
let msg = SMB2.SessionSetupRequest(singing: [])
let data = createSMB2Message(header: smbHeader, message: msg)
self.write(data, timeout: timeout, completionHandler: { (e) in
if self.sessionId == 0 {
self.readData(ofMinLength: 64, maxLength: 65536, timeout: self.timeout, completionHandler: { (data, eof, e2) in
// TODO: set session id
completionHandler?(e2 ?? e)
})
}
})
return mId
}
func sendTreeConnect(completionHandler: SimpleCompletionHandler) -> UInt64 {
let req = self.currentRequest ?? self.originalRequest
guard let url = req?.url, let host = url.host else {
return 0
}
let mId = messageId()
let smbHeader = SMB2.Header(command: .TREE_CONNECT, creditRequestResponse: 123, messageId: mId, treeId: 0, sessionId: sessionId)
let cmp = url.pathComponents
let share = cmp.first ?? ""
let tcHeader = SMB2.TreeConnectRequest.Header(flags: [])
let msg = SMB2.TreeConnectRequest(header: tcHeader, host: host, share: share)
let data = createSMB2Message(header: smbHeader, message: msg!)
self.write(data, timeout: timeout, completionHandler: { (e) in
completionHandler?(e)
})
return mId
}
func sendTreeDisconnect(id treeId: UInt32, completionHandler: SimpleCompletionHandler) -> UInt64 {
let mId = messageId()
let smbHeader = SMB2.Header(command: .TREE_DISCONNECT, creditRequestResponse: 111, messageId: mId, treeId: treeId, sessionId: sessionId)
let msg = SMB2.TreeDisconnect()
let data = createSMB2Message(header: smbHeader, message: msg)
self.write(data, timeout: timeout, completionHandler: { (e) in
completionHandler?(e)
})
return mId
}
func sendLogoff(id treeId: UInt32, completionHandler: SimpleCompletionHandler) -> UInt64 {
let mId = messageId()
let smbHeader = SMB2.Header(command: .LOGOFF, creditRequestResponse: 0, messageId: mId, treeId: 0, sessionId: sessionId)
let msg = SMB2.LogOff()
let data = createSMB2Message(header: smbHeader, message: msg)
self.write(data, timeout: timeout, completionHandler: { (e) in
completionHandler?(e)
})
return mId
}
func reset() {
}
}
// MARK: create and analyse messages
extension FileProviderSMBTask {
func determineSMBVersion(_ data: Data) -> Float {
let smbverChar: Int8 = Int8(bitPattern: data.first ?? 0)
let version = 0 - smbverChar
return Float(version)
}
class func digestSMBMessage(data: NSData) throws -> (header: SMB1.Header, blocks: [(params: [UInt16], message: NSData?)]) {
guard data.length > 30 else {
throw NSURLError.BadServerResponse
func digestSMBMessage(_ data: Data) throws -> (header: SMB1.Header, blocks: [(params: [UInt16], message: Data?)]) {
guard data.count > 30 else {
throw URLError(.badServerResponse)
}
var buffer = [UInt8](count: data.length, repeatedValue: 0)
var buffer = [UInt8](repeating: 0, count: data.count)
guard determineSMBVersion(data) == 1 else {
throw SMBFileProviderError.IncompatibleHeader
throw SMBFileProviderError.incompatibleHeader
}
let headersize = sizeof(SMB1.Header.self)
let header: SMB1.Header = decode(data)
var blocks = [(params: [UInt16], message: NSData?)]()
let headersize = MemoryLayout<SMB1.Header>.size
let header: SMB1.Header = data.scanValue()!
var blocks = [(params: [UInt16], message: Data?)]()
var offset = headersize
while offset < data.length {
while offset < data.count {
let paramWords: [UInt16]
let paramWordsCount = Int(buffer[offset])
guard data.length > (paramWordsCount * 2 + offset) else {
throw SMBFileProviderError.IncorrectParamsLength
guard data.count > (paramWordsCount * 2 + offset) else {
throw SMBFileProviderError.incorrectParamsLength
}
offset += sizeof(UInt8)
offset += MemoryLayout<UInt8>.size
var rawParamWords = [UInt8](buffer[offset..<(offset + paramWordsCount * 2)])
let paramData = NSData(bytesNoCopy: &rawParamWords, length: rawParamWords.count)
paramWords = decode(paramData)
let paramData = Data(bytesNoCopy: UnsafeMutablePointer<UInt8>(&rawParamWords), count: rawParamWords.count, deallocator: .free)
paramWords = paramData.scanValue()!
offset += paramWordsCount * 2
let messageBytesCountLittleEndian = [UInt8](buffer[offset...(offset + 1)])
let messageBytesCount = Int(UnsafePointer<UInt16>(messageBytesCountLittleEndian).memory)
offset += sizeof(UInt16)
guard data.length >= (offset + messageBytesCount) else {
throw SMBFileProviderError.IncorrectMessageLength
let messageBytesCountHi = Int(buffer[1]) << 8
let messageBytesCount = Int(buffer[0]) + messageBytesCountHi
offset += MemoryLayout<UInt16>.size
guard data.count >= (offset + messageBytesCount) else {
throw SMBFileProviderError.incorrectMessageLength
}
var rawMessage = [UInt8](buffer[offset..<(offset + messageBytesCount)])
let rawMessage = [UInt8](buffer[offset..<(offset + messageBytesCount)])
offset += messageBytesCount
let message = NSData(bytes: &rawMessage, length: rawMessage.count)
let message = Data(bytes: rawMessage)
blocks.append((params: paramWords, message: message))
}
return (header, blocks)
}
class func digestSMB2Message(data: NSData) throws -> (header: SMB2.Header, message: SMBResponse?)? {
guard data.length > 65 else {
throw NSURLError.BadServerResponse
func digestSMB2Message(_ data: Data) throws -> SMBResponse? {
guard data.count > 65 else {
throw URLError(.badServerResponse)
}
guard determineSMBVersion(data) == 2 else {
throw SMBFileProviderError.IncompatibleHeader
throw SMBFileProviderError.incompatibleHeader
}
let headersize = sizeof(SMB2.Header.self)
let headerData = data.subdataWithRange(NSRange(location: 0, length: headersize))
let messageSize = data.length - headersize
let messageData = data.subdataWithRange(NSRange(location: headersize, length: messageSize))
let header: SMB2.Header = decode(headerData)
let headersize = MemoryLayout<SMB2.Header>.size
let headerData = data.subdata(in: 0..<headersize)
let messageSize = data.count - headersize
let messageData = data.subdata(in: headersize..<(headersize + messageSize))
let header: SMB2.Header = headerData.scanValue()!
switch header.command {
case .NEGOTIATE:
return (header, SMB2.NegotiateResponse(data: messageData))
@@ -136,59 +182,53 @@ class SMBProtocolClient: TCPSocketClient {
case .FLUSH:
return (header, SMB2.FlushResponse(data: messageData))
case .READ:
return (header, nil) // FIXME:
return (header, SMB2.ReadRespone(data: messageData))
case .WRITE:
return (header, nil) // FIXME:
return (header, SMB2.WriteResponse(data: messageData))
case .LOCK:
return (header, nil) // FIXME:
return (header, SMB2.LockResponse(data: messageData))
case .IOCTL:
return (header, nil)
return (header, SMB2.IOCtlResponse(data: messageData))
case .CANCEL:
return (header, nil)
case .ECHO:
return (header, SMB2.Echo(data: messageData))
case .QUERY_DIRECTORY:
return (header, nil) // FIXME:
return (header, SMB2.QueryDirectoryResponse(data: messageData))
case .CHANGE_NOTIFY:
return (header, nil) // FIXME:
return (header, SMB2.ChangeNotifyResponse(data: messageData))
case .QUERY_INFO:
return (header, nil) // FIXME:
return (header, SMB2.QueryInfoResponse(data: messageData))
case .SET_INFO:
return (header, nil) // FIXME:
return (header, SMB2.SetInfoResponse(data: messageData))
case .OPLOCK_BREAK:
return (header, nil) // FIXME:
case .INVALID:
throw SMBFileProviderError.InvalidCommand
throw SMBFileProviderError.invalidCommand
}
}
class func createSMBMessage(header: SMB1.Header, blocks: [(params: NSData?, message: NSData?)]) -> NSData {
var headerv = header
let result = NSMutableData(data: encode(&headerv))
func createSMBMessage(header: SMB1.Header, blocks: [(params: Data?, message: Data?)]) -> Data {
var result = Data(value: header)
for block in blocks {
var paramWordsCount = UInt8(block.params?.length ?? 0)
result.appendBytes(&paramWordsCount, length: sizeofValue(paramWordsCount))
var paramWordsCount = UInt8(block.params?.count ?? 0)
result.append(&paramWordsCount, count: MemoryLayout.size(ofValue: paramWordsCount))
if let params = block.params {
result.appendData(params)
result.append(params)
}
var messageLen = UInt16(block.message?.length ?? 0)
result.appendBytes(&messageLen, length: sizeofValue(messageLen))
var messageLen = UInt16(block.message?.count ?? 0)
let b = UnsafeBufferPointer(start: &messageLen, count: MemoryLayout.size(ofValue: messageLen))
result.append(b)
if let message = block.message {
result.appendData(message)
result.append(message)
}
}
return result
}
class func createSMB2Message(header: SMB2.Header, message: SMBRequest) -> NSData {
var headerv = header
let result = NSMutableData(data: encode(&headerv))
result.appendData(message.data())
func createSMB2Message(header: SMB2.Header, message: SMBRequestBody) -> Data {
var result = Data(value: header)
result.append(message.data())
return result
}
}
protocol FileProviderSMBHeader {
var protocolID: UInt32 { get }
static var protocolConst: UInt32 { get }
}
+113 -67
View File
@@ -8,104 +8,150 @@
import Foundation
public class SMBFileProvider: FileProvider, FileProviderMonitor {
public var type: String = "Samba"
public var isPathRelative: Bool = true
public var baseURL: NSURL?
public var currentPath: String = ""
public var dispatch_queue: dispatch_queue_t
public weak var delegate: FileProviderDelegate?
public let credential: NSURLCredential?
class SMBFileProvider: FileProvider, FileProviderMonitor {
open class var type: String { return "SMB" }
open var baseURL: URL?
open var currentPath: String = ""
open var dispatch_queue: DispatchQueue
open var operation_queue: OperationQueue
open weak var delegate: FileProviderDelegate?
open var credential: URLCredential?
public typealias FileObjectClass = FileObject
public init? (baseURL: NSURL, credential: NSURLCredential, afterInitialized: SimpleCompletionHandler) {
guard baseURL.uw_scheme.lowercaseString == "smb" else {
public init? (baseURL: URL, credential: URLCredential?) {
guard baseURL.uw_scheme.lowercased() == "smb" else {
return nil
}
self.baseURL = baseURL
dispatch_queue = dispatch_queue_create("FileProvider.\(type)", DISPATCH_QUEUE_CONCURRENT)
//let url = baseURL.uw_absoluteString
self.baseURL = baseURL.appendingPathComponent("")
#if swift(>=3.1)
let queueLabel = "FileProvider.\(Swift.type(of: self).type)"
#else
let queueLabel = "FileProvider.\(type(of: self).type)"
#endif
dispatch_queue = DispatchQueue(label: queueLabel, attributes: .concurrent)
operation_queue = OperationQueue()
operation_queue.name = "\(queueLabel).Operation"
self.credential = credential
}
public func contentsOfDirectoryAtPath(path: String, completionHandler: ((contents: [FileObjectClass], error: ErrorType?) -> Void)) {
NotImplemented()
dispatch_async(dispatch_queue) {
public required convenience init?(coder aDecoder: NSCoder) {
guard let baseURL = aDecoder.decodeObject(forKey: "baseURL") as? URL else {
return nil
}
self.init(baseURL: baseURL,
credential: aDecoder.decodeObject(forKey: "credential") as? URLCredential)
self.currentPath = aDecoder.decodeObject(forKey: "currentPath") as? String ?? ""
}
public func attributesOfItemAtPath(path: String, completionHandler: ((attributes: FileObjectClass?, error: ErrorType?) -> Void)) {
open func encode(with aCoder: NSCoder) {
aCoder.encode(self.baseURL, forKey: "baseURL")
aCoder.encode(self.credential, forKey: "credential")
aCoder.encode(self.currentPath, forKey: "currentPath")
}
public static var supportsSecureCoding: Bool {
return true
}
open func contentsOfDirectory(path: String, completionHandler: @escaping (_ contents: [FileObjectClass], _ error: Error?) -> Void) {
NotImplemented()
}
public weak var fileOperationDelegate: FileOperationDelegate?
public func createFolder(folderName: String, atPath: String, completionHandler: SimpleCompletionHandler) {
open func attributesOfItem(path: String, completionHandler: @escaping (_ attributes: FileObjectClass?, _ error: Error?) -> Void) {
NotImplemented()
}
public func createFile(fileAttribs: FileObject, atPath: String, contents data: NSData?, completionHandler: SimpleCompletionHandler) {
open func storageProperties(completionHandler: @escaping (_ volume: VolumeObject?) -> Void) {
NotImplemented()
}
public func moveItemAtPath(path: String, toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
func isReachable(completionHandler: @escaping (Bool) -> Void) {
NotImplemented()
}
public func copyItemAtPath(path: String, toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
open weak var fileOperationDelegate: FileOperationDelegate?
open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> Progress? {
NotImplemented()
return nil
}
open func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> Progress? {
NotImplemented()
return nil
}
open func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> Progress? {
NotImplemented()
return nil
}
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> Progress? {
NotImplemented()
return nil
}
open func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
NotImplemented()
return nil
}
open func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) -> Progress? {
NotImplemented()
return nil
}
open func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> Progress? {
NotImplemented()
return nil
}
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> Progress? {
NotImplemented()
return nil
}
open func writeContents(path: String, contents data: Data?, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
NotImplemented()
return nil
}
open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler:((FileObjectClass) -> Void)?, completionHandler: @escaping ((_ files: [FileObjectClass], _ error: Error?) -> Void)) -> Progress? {
NotImplemented()
return nil
}
open func registerNotifcation(path: String, eventHandler: @escaping (() -> Void)) {
NotImplemented()
}
public func removeItemAtPath(path: String, completionHandler: SimpleCompletionHandler) {
open func unregisterNotifcation(path: String) {
NotImplemented()
}
public func copyLocalFileToPath(localFile: NSURL, toPath: String, completionHandler: SimpleCompletionHandler) {
NotImplemented()
}
public func copyPathToLocalFile(path: String, toLocalURL: NSURL, completionHandler: SimpleCompletionHandler) {
NotImplemented()
}
public func contentsAtPath(path: String, completionHandler: ((contents: NSData?, error: ErrorType?) -> Void)) {
NotImplemented()
}
public func contentsAtPath(path: String, offset: Int64, length: Int, completionHandler: ((contents: NSData?, error: ErrorType?) -> Void)) {
NotImplemented()
}
public func writeContentsAtPath(path: String, contents data: NSData, atomically: Bool, completionHandler: SimpleCompletionHandler) {
NotImplemented()
}
public func searchFilesAtPath(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObjectClass) -> Void)?, completionHandler: ((files: [FileObjectClass], error: ErrorType?) -> Void)) {
NotImplemented()
}
public func registerNotifcation(path: String, eventHandler: (() -> Void)) {
NotImplemented()
}
public func unregisterNotifcation(path: String) {
NotImplemented()
}
public func isRegisteredForNotification(path: String) -> Bool {
open func isRegisteredForNotification(path: String) -> Bool {
return false
}
open func copy(with zone: NSZone? = nil) -> Any {
let copy = SMBFileProvider(baseURL: self.baseURL!, credential: self.credential!)!
copy.currentPath = self.currentPath
copy.delegate = self.delegate
copy.fileOperationDelegate = self.fileOperationDelegate
return copy
}
}
// MARK: basic CIFS interactivity
public enum SMBFileProviderError: Int, ErrorType, CustomStringConvertible {
case BadHeader
case IncompatibleHeader
case IncorrectParamsLength
case IncorrectMessageLength
case InvalidCommand
enum SMBFileProviderError: Int, Error, CustomStringConvertible {
case badHeader
case incompatibleHeader
case incorrectParamsLength
case incorrectMessageLength
case invalidCommand
public var description: String {
return "SMB message structure is invalid"
@@ -113,8 +159,8 @@ public enum SMBFileProviderError: Int, ErrorType, CustomStringConvertible {
}
private extension SMBFileProvider {
private func getPID() -> UInt32 {
return UInt32(NSProcessInfo.processInfo().processIdentifier)
func getPID() -> UInt32 {
return UInt32(ProcessInfo.processInfo.processIdentifier)
}
}
+12 -8
View File
@@ -8,13 +8,14 @@
import Foundation
// codebeat:disable[TOO_MANY_IVARS]
// SMB/CIFS Types
struct SMB1 {
struct Header { // 32 bytes
// header is always \u{ff}SMB
let protocolID: UInt32
static let protocolConst: UInt32 = 0x424d53ff
private var _command: UInt8
fileprivate var _command: UInt8
var command: Command {
get {
return Command(rawValue: _command) ?? .INVALID
@@ -24,7 +25,7 @@ struct SMB1 {
}
}
// error messages from the server to the client
private var _status: (UInt8, UInt8, UInt8, UInt8)
fileprivate var _status: (UInt8, UInt8, UInt8, UInt8)
var error: (Class: UInt8, code: UInt16) {
get {
return (_status.0, UInt16(_status.2) + (UInt16(_status.3) << 8))
@@ -47,7 +48,7 @@ struct SMB1 {
var flags2: Flags2
var pidHigh: UInt16
// encryption key used for validating messages over connectionless transports
private var _securityKey: (UInt16, UInt16)
fileprivate var _securityKey: (UInt16, UInt16)
var securityKey: UInt32 {
get {
return UInt32(_securityKey.1) << 16 + UInt32(_securityKey.0)
@@ -60,7 +61,7 @@ struct SMB1 {
var securityCID: UInt16
/// Identifier of the sequence of a message over connectionless transports
var securitySequenceNumber: UInt16
private var ununsed: UInt16
fileprivate var ununsed: UInt16
var treeId: UInt16
var pidLow: UInt16
var userId: UInt16
@@ -75,7 +76,8 @@ struct SMB1 {
}
}
init(command: Command, ntStatus: UInt32 = 0, flags: Flags, flags2: Flags2 = [.LONG_NAMES, .ERR_STATUS, .UNICODE], securityKey: UInt32 = 0, securityCID: UInt16 = 0, securitySequenceNumber: UInt16 = 0, treeId: UInt16, pid: UInt32, userId: UInt16, multiplexId: UInt16) {
// codebeat:disable[ARITY]
init(command: Command, treeId: UInt16, pid: UInt32, userId: UInt16, multiplexId: UInt16, flags: Flags, flags2: Flags2 = [.LONG_NAMES, .ERR_STATUS, .UNICODE], ntStatus: UInt32 = 0, securityKey: UInt32 = 0, securityCID: UInt16 = 0, securitySequenceNumber: UInt16 = 0) {
self.protocolID = Header.protocolConst
self._command = command.rawValue
_status = (UInt8(ntStatus & 0xff), UInt8(ntStatus >> 8 & 0xff), UInt8(ntStatus >> 16 & 0xff), UInt8(ntStatus >> 24 & 0xff))
@@ -91,9 +93,10 @@ struct SMB1 {
self.userId = userId
self.multiplexId = multiplexId
}
// codebeat:enable[ARITY]
}
struct Flags: OptionSetType {
struct Flags: OptionSet {
let rawValue: UInt8
init(rawValue: UInt8) {
@@ -111,7 +114,7 @@ struct SMB1 {
static let REPLY = Flags(rawValue: 0x80)
}
struct Flags2: OptionSetType {
struct Flags2: OptionSet {
let rawValue: UInt16
init(rawValue: UInt16) {
@@ -214,4 +217,5 @@ struct SMB1 {
case WRITE_BULK_DATA = 0xDA
case INVALID = 0xFE
}
}
}
// codebeat:enable[TOO_MANY_IVARS]
+33 -14
View File
@@ -8,34 +8,53 @@
import Foundation
protocol SMBRequest {
func data() -> NSData
protocol SMBRequestBody {
func data() -> Data
}
protocol SMBResponse {
init? (data: NSData)
extension SMBRequestBody {
func data() -> Data {
return Data(value: self)
}
}
protocol IOCtlRequestProtocol: SMBRequest {}
protocol IOCtlResponseProtocol: SMBResponse {}
protocol SMBResponseBody {
init? (data: Data)
}
extension SMBResponseBody {
init? (data: Data) {
if let v: Self = data.scanValue() {
self = v
} else {
return nil
}
}
}
typealias SMBRequest = (header: SMB2.Header, body: SMBRequestBody?)
typealias SMBResponse = (header: SMB2.Header, body: SMBResponseBody?)
protocol IOCtlRequestProtocol: SMBRequestBody {}
protocol IOCtlResponseProtocol: SMBResponseBody {}
struct SMBTime {
var time: UInt64
var time: Int64
init(time: UInt64) {
init(time: Int64) {
self.time = time
}
init(unixTime: UInt) {
self.time = (UInt64(unixTime) + 11644473600) * 10000000
self.time = (Int64(unixTime) + 11644473600) * 10000000
}
init(timeIntervalSince1970: NSTimeInterval) {
self.time = UInt64((timeIntervalSince1970 + 11644473600) * 10000000)
init(timeIntervalSince1970: TimeInterval) {
self.time = Int64((timeIntervalSince1970 + 11644473600) * 10000000)
}
init(date: NSDate) {
init(date: Date) {
self.init(timeIntervalSince1970: date.timeIntervalSince1970)
}
@@ -43,7 +62,7 @@ struct SMBTime {
return UInt(self.time / 10000000 - 11644473600)
}
var date: NSDate {
return NSDate(timeIntervalSince1970: Double(self.time) / 10000000 - 11644473600)
var date: Date {
return Date(timeIntervalSince1970: Double(self.time) / 10000000 - 11644473600)
}
}
+71 -93
View File
@@ -11,7 +11,7 @@ import Foundation
extension SMB2 {
// MARK: SMB2 Create
struct CreateRequest: SMBRequest {
struct CreateRequest: SMBRequestBody {
let header: CreateRequest.Header
let name: String?
let contexts: [CreateContext]
@@ -22,15 +22,15 @@ extension SMB2 {
self.contexts = contexts
}
func data() -> NSData {
func data() -> Data {
var header = self.header
var offset = 0x78 //UInt16(sizeof(SMB2.Header.self) + sizeof(CreateContext.Header.self) - 1)
let body = NSMutableData()
if let name = self.name, let nameData = name.dataUsingEncoding(NSUTF8StringEncoding) {
var body = Data()
if let name = self.name, let nameData = name.data(using: .utf16) {
header.nameOffset = UInt16(offset)
header.nameLength = UInt16(nameData.length)
offset += nameData.length
body.appendData(nameData)
header.nameLength = UInt16(nameData.count)
offset += nameData.count
body.append(nameData)
}
if contexts.count > 0 {
// TODO: Context CreateRequest implementation, 8 bit allign offset
@@ -40,15 +40,15 @@ extension SMB2 {
header.contextLength = 0
//result.appendData(nameData)
}
let result = NSMutableData(data: encode(&header))
result.appendData(body)
var result = Data(value: header)
result.append(body)
return result
}
struct Header {
let size: UInt16
private let securityFlags: UInt8
private var _requestedOplockLevel: UInt8
fileprivate let securityFlags: UInt8
fileprivate var _requestedOplockLevel: UInt8
var requestedOplockLevel: OplockLevel {
get {
return OplockLevel(rawValue: _requestedOplockLevel)!
@@ -57,7 +57,7 @@ extension SMB2 {
_requestedOplockLevel = newValue.rawValue
}
}
private var _impersonationLevel: UInt32
fileprivate var _impersonationLevel: UInt32
var impersonationLevel: ImpersonationLevel {
get {
return ImpersonationLevel(rawValue: _impersonationLevel)!
@@ -66,12 +66,12 @@ extension SMB2 {
_impersonationLevel = newValue.rawValue
}
}
private let flags: UInt64
private let reserved: UInt64
fileprivate let flags: UInt64
fileprivate let reserved: UInt64
let access: FileAccessMask
let fileAttributes: FileAttributes
let shareAccess: ShareAccess
private var _desposition: UInt32
fileprivate var _desposition: UInt32
var desposition: CreateDisposition {
get {
return CreateDisposition(rawValue: _desposition)!
@@ -86,7 +86,7 @@ extension SMB2 {
var contextOffset: UInt32
var contextLength: UInt32
init(requestedOplockLevel: OplockLevel = .NONE, impersonationLevel: ImpersonationLevel = .Anonymous, access: FileAccessMask = [.GENERIC_ALL], fileAttributes: FileAttributes = [], shareAccess: ShareAccess = [.READ], desposition: CreateDisposition = .OPEN_IF, options: CreateOptions = []) {
init(requestedOplockLevel: OplockLevel = .NONE, impersonationLevel: ImpersonationLevel = .anonymous, access: FileAccessMask = [.GENERIC_ALL], fileAttributes: FileAttributes = [], shareAccess: ShareAccess = [.READ], desposition: CreateDisposition = .OPEN_IF, options: CreateOptions = []) {
self.size = 57
self.securityFlags = 0
self._requestedOplockLevel = requestedOplockLevel.rawValue
@@ -105,7 +105,7 @@ extension SMB2 {
}
}
struct CreateOptions: OptionSetType {
struct CreateOptions: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
@@ -125,14 +125,14 @@ extension SMB2 {
static let NO_COMPRESSION = CreateOptions(rawValue: 0x00008000)
static let OPEN_REPARSE_POINT = CreateOptions(rawValue: 0x00200000)
static let OPEN_NO_RECALL = CreateOptions(rawValue: 0x00400000)
private static let SYNCHRONOUS_IO_ALERT = CreateOptions(rawValue: 0x00000010)
private static let SYNCHRONOUS_IO_NONALERT = CreateOptions(rawValue: 0x00000020)
private static let COMPLETE_IF_OPLOCKED = CreateOptions(rawValue: 0x00000100)
private static let REMOTE_INSTANCE = CreateOptions(rawValue: 0x00000400)
private static let OPEN_FOR_FREE_SPACE_QUERY = CreateOptions(rawValue: 0x00800000)
private static let OPEN_REQUIRING_OPLOCK = CreateOptions(rawValue: 0x00010000)
private static let DISALLOW_EXCLUSIVE = CreateOptions(rawValue: 0x00020000)
private static let RESERVE_OPFILTER = CreateOptions(rawValue: 0x00100000)
fileprivate static let SYNCHRONOUS_IO_ALERT = CreateOptions(rawValue: 0x00000010)
fileprivate static let SYNCHRONOUS_IO_NONALERT = CreateOptions(rawValue: 0x00000020)
fileprivate static let COMPLETE_IF_OPLOCKED = CreateOptions(rawValue: 0x00000100)
fileprivate static let REMOTE_INSTANCE = CreateOptions(rawValue: 0x00000400)
fileprivate static let OPEN_FOR_FREE_SPACE_QUERY = CreateOptions(rawValue: 0x00800000)
fileprivate static let OPEN_REQUIRING_OPLOCK = CreateOptions(rawValue: 0x00010000)
fileprivate static let DISALLOW_EXCLUSIVE = CreateOptions(rawValue: 0x00020000)
fileprivate static let RESERVE_OPFILTER = CreateOptions(rawValue: 0x00100000)
}
enum CreateDisposition: UInt32 {
@@ -151,21 +151,21 @@ extension SMB2 {
}
enum ImpersonationLevel: UInt32 {
case Anonymous = 0x00000000
case Identification = 0x00000001
case Impersonation = 0x00000002
case Delegate = 0x00000003
case anonymous = 0x00000000
case identification = 0x00000001
case impersonation = 0x00000002
case delegate = 0x00000003
}
}
struct CreateResponse: SMBResponse {
struct CreateResponse: SMBResponseBody {
struct Header {
let size: UInt16
private let _oplockLevel: UInt8
fileprivate let _oplockLevel: UInt8
var oplockLevel: OplockLevel {
return OplockLevel(rawValue: _oplockLevel)!
}
private let reserved: UInt32
fileprivate let reserved: UInt32
let creationTime: SMBTime
let lastAccessTime: SMBTime
let lastWriteTime: SMBTime
@@ -173,7 +173,7 @@ extension SMB2 {
let allocationSize: UInt64
let endOfFile: UInt64
let fileAttributes: FileAttributes
private let reserved2: UInt32
fileprivate let reserved2: UInt32
let fileId: FileId
let contextsOffset: UInt32
let ContextsLength: UInt32
@@ -182,27 +182,22 @@ extension SMB2 {
let header: CreateResponse.Header
let contexts: [CreateContext]
init? (data: NSData) {
guard data.length >= sizeof(CreateResponse.Header.self) else {
init? (data: Data) {
guard data.count >= MemoryLayout<CreateResponse.Header>.size else {
return nil
}
self.header = decode(data)
self.header = data.scanValue()!
if self.header.contextsOffset > 0 {
var contexts = [CreateContext]()
var contextOffset = Int(self.header.contextsOffset) - sizeof(SMB2.Header.self)
var contextOffset = Int(self.header.contextsOffset) - MemoryLayout<SMB2.Header>.size
while contextOffset > 0 {
guard contextOffset < data.length else {
guard contextOffset < data.count else {
self.contexts = contexts
return
}
let contextDataHeader = data.subdataWithRange(NSRange(location: contextOffset, length: sizeof(CreateContext.Header.self)))
if let lastContextHeader = CreateContext(data: contextDataHeader) {
let lastContextLen = Int(lastContextHeader.header.dataOffset) + Int(lastContextHeader.header.dataLength) - contextOffset
let lastContextData = data.subdataWithRange(NSRange(location: contextOffset, length: lastContextLen))
if let newContext = CreateContext(data: lastContextData) {
contexts.append(newContext)
}
contextOffset = Int(lastContextHeader.header.next) - sizeof(SMB2.Header.self)
while contextOffset > 0, let context: CreateContext = data.scanValue(start: contextOffset) {
contexts.append(context)
contextOffset = Int(context.header.next) - MemoryLayout<SMB2.Header>.size
}
}
self.contexts = contexts
@@ -217,40 +212,39 @@ extension SMB2 {
var next: UInt32
let nameOffset: UInt16
let nameLength: UInt16
private let reserved: UInt16
fileprivate let reserved: UInt16
let dataOffset: UInt16
let dataLength: UInt32
}
var header: CreateContext.Header
let buffer: NSData
let buffer: Data
init(name: ContextNames, data: NSData) {
let nameData = NSMutableData(data: (name.rawValue).dataUsingEncoding(NSUTF8StringEncoding)!)
self.header = CreateContext.Header(next: 0, nameOffset: 32, nameLength: UInt16(nameData.length), reserved: 0, dataOffset: UInt16(nameData.length), dataLength: UInt32(data.length))
init(name: ContextNames, data: Data) {
let nameData = (name.rawValue).data(using: .utf16) ?? Data()
self.header = CreateContext.Header(next: 0, nameOffset: 32, nameLength: UInt16(nameData.count), reserved: 0, dataOffset: UInt16(nameData.count), dataLength: UInt32(data.count))
self.buffer = data
}
init(name: NSUUID, data: NSData) {
var uuid = uuid_t(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)
name.getUUIDBytes(&uuid.0)
let nameData = NSMutableData(bytes: &uuid, length: 16)
self.header = CreateContext.Header(next: 0, nameOffset: 32, nameLength: UInt16(nameData.length), reserved: 0, dataOffset: UInt16(nameData.length), dataLength: UInt32(data.length))
init(name: UUID, data: Data) {
let uuid = name.uuid
var nameData = Data(value: uuid)
self.header = CreateContext.Header(next: 0, nameOffset: 32, nameLength: UInt16(nameData.count), reserved: 0, dataOffset: UInt16(nameData.count), dataLength: UInt32(data.count))
self.buffer = data
}
init? (data: NSData) {
let headersize = sizeof(Header)
guard data.length > headersize else {
init? (data: Data) {
let headersize = MemoryLayout<Header>.size
guard data.count > headersize else {
return nil
}
self.header = decode(data)
self.buffer = data.subdataWithRange(NSRange(location: headersize, length: data.length - headersize))
self.header = data.scanValue()!
self.buffer = data.subdata(in: headersize..<data.count)
}
func data() -> NSData {
let result = NSMutableData(data: encode(header))
result.appendData(buffer)
func data() -> Data {
var result = Data(value: header)
result.append(buffer)
return result
}
@@ -284,7 +278,7 @@ extension SMB2 {
case LEASE = 0xFF
}
struct ShareAccess: OptionSetType {
struct ShareAccess: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
@@ -296,7 +290,7 @@ extension SMB2 {
static let DELETE = ShareAccess(rawValue: 0x00000004)
}
struct FileAccessMask: OptionSetType {
struct FileAccessMask: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
@@ -332,7 +326,7 @@ extension SMB2 {
static let GENERIC_READ = FileAccessMask(rawValue: 0x80000000)
}
struct FileAttributes: OptionSetType {
struct FileAttributes: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
@@ -363,10 +357,10 @@ extension SMB2 {
// MARK: SMB2 Close
struct CloseRequest: SMBRequest {
struct CloseRequest: SMBRequestBody {
let size: UInt16
let flags: CloseFlags
private let reserved2: UInt32
fileprivate let reserved2: UInt32
let filePersistantId: UInt64
let fileVolatileId: UInt64
@@ -377,16 +371,12 @@ extension SMB2 {
self.flags = []
self.reserved2 = 0
}
func data() -> NSData {
return encode(self)
}
}
struct CloseResponse: SMBResponse {
struct CloseResponse: SMBResponseBody {
let size: UInt16
let flags: CloseFlags
private let reserved: UInt32
fileprivate let reserved: UInt32
let creationTime: SMBTime
let lastAccessTime: SMBTime
let lastWriteTime: SMBTime
@@ -394,13 +384,9 @@ extension SMB2 {
let allocationSize: UInt64
let endOfFile: UInt64
let fileAttributes: FileAttributes
init? (data: NSData) {
self = decode(data)
}
}
struct CloseFlags: OptionSetType {
struct CloseFlags: OptionSet {
let rawValue: UInt16
init(rawValue: UInt16) {
@@ -412,10 +398,10 @@ extension SMB2 {
// MARK: SMB2 Flush
struct FlushRequest: SMBRequest {
struct FlushRequest: SMBRequestBody {
let size: UInt16
private let reserved: UInt16
private let reserved2: UInt32
fileprivate let reserved: UInt16
fileprivate let reserved2: UInt32
let filePersistantId: UInt64
let fileVolatileId: UInt64
@@ -426,13 +412,9 @@ extension SMB2 {
self.reserved = 0
self.reserved2 = 0
}
func data() -> NSData {
return encode(self)
}
}
struct FlushResponse: SMBResponse {
struct FlushResponse: SMBResponseBody {
let size: UInt16
let reserved: UInt16
@@ -440,9 +422,5 @@ extension SMB2 {
self.size = 4
self.reserved = 0
}
init? (data: NSData) {
self = decode(data)
}
}
}
}
+49 -72
View File
@@ -11,22 +11,22 @@ import Foundation
extension SMB2 {
// MARK: SMB2 Read
struct ReadRequest: SMBRequest {
struct ReadRequest: SMBRequestBody {
let size: UInt16
private let padding: UInt8
fileprivate let padding: UInt8
let flags: ReadRequest.Flags
let length: UInt32
let offset: UInt64
let fileId: FileId
let minimumLength: UInt32
private let _channel: UInt32
fileprivate let _channel: UInt32
var channel: Channel {
return Channel(rawValue: _channel) ?? .NONE
}
let remainingBytes: UInt32
private let channelInfoOffset: UInt16
private let channelInfoLength: UInt16
private let channelBuffer: UInt8
fileprivate let channelInfoOffset: UInt16
fileprivate let channelInfoLength: UInt16
fileprivate let channelBuffer: UInt8
init (fileId: FileId, offset: UInt64, length: UInt32, flags: ReadRequest.Flags = [], minimumLength: UInt32 = 0, remainingBytes: UInt32 = 0, channel: Channel = .NONE) {
self.size = 49
@@ -43,11 +43,7 @@ extension SMB2 {
self.channelBuffer = 0
}
func data() -> NSData {
return encode(read)
}
struct Flags: OptionSetType {
struct Flags: OptionSet {
let rawValue: UInt8
init(rawValue: UInt8) {
@@ -58,26 +54,26 @@ extension SMB2 {
}
}
struct ReadRespone: SMBResponse {
struct ReadRespone: SMBResponseBody {
struct Header {
let size: UInt16
let offset: UInt8
private let reserved: UInt8
fileprivate let reserved: UInt8
let length: UInt32
let remaining: UInt32
private let reserved2: UInt32
fileprivate let reserved2: UInt32
}
let header: ReadRespone.Header
let buffer: NSData
let buffer: Data
init?(data: NSData) {
guard data.length > 16 else {
init?(data: Data) {
guard data.count > 16 else {
return nil
}
self.header = decode(data)
let headersize = sizeof(Header)
self.buffer = data.subdataWithRange(NSRange(location: headersize, length: data.length - headersize))
self.header = data.scanValue()!
let headersize = MemoryLayout<Header>.size
self.buffer = data.subdata(in: headersize..<data.count)
}
}
@@ -89,10 +85,10 @@ extension SMB2 {
// MARK: SMB2 Write
struct WriteRequest: SMBRequest {
struct WriteRequest: SMBRequestBody {
let header: WriteRequest.Header
let channelInfo: ChannelInfo?
let fileData: NSData
let fileData: Data
struct Header {
let size: UInt16
@@ -100,7 +96,7 @@ extension SMB2 {
let length: UInt32
let offset: UInt64
let fileId: FileId
private let _channel: UInt32
fileprivate let _channel: UInt32
var channel: Channel {
return Channel(rawValue: _channel) ?? .NONE
}
@@ -110,29 +106,31 @@ extension SMB2 {
let flags: WriteRequest.Flags
}
init(fileId: FileId, offset: UInt64, remainingBytes: UInt32 = 0, data: NSData, channel: Channel = .NONE, channelInfo: ChannelInfo? = nil, flags: WriteRequest.Flags = []) {
// codebeat:disable[ARITY]
init(fileId: FileId, offset: UInt64, remainingBytes: UInt32 = 0, data: Data, channel: Channel = .NONE, channelInfo: ChannelInfo? = nil, flags: WriteRequest.Flags = []) {
var channelInfoOffset: UInt16 = 0
var channelInfoLength: UInt16 = 0
if channel != .NONE, let channelInfo = channelInfo {
channelInfoOffset = UInt16(sizeof(SMB2.Header.self) + sizeof(WriteRequest.Header.self))
channelInfoLength = UInt16(sizeof(channelInfo.dynamicType))
if channel != .NONE, let _ = channelInfo {
channelInfoOffset = UInt16(MemoryLayout<SMB2.Header>.size + MemoryLayout<WriteRequest.Header>.size)
channelInfoLength = UInt16(MemoryLayout<SMB2.ChannelInfo>.size)
}
let dataOffset = UInt16(sizeof(SMB2.Header.self) + sizeof(WriteRequest.Header.self)) + channelInfoLength
self.header = WriteRequest.Header(size: UInt16(49), dataOffset: dataOffset, length: UInt32(data.length), offset: offset, fileId: fileId, _channel: channel.rawValue, remainingBytes: remainingBytes, channelInfoOffset: channelInfoOffset, channelInfoLength: channelInfoLength, flags: flags)
let dataOffset = UInt16(MemoryLayout<SMB2.Header>.size + MemoryLayout<WriteRequest.Header>.size) + channelInfoLength
self.header = WriteRequest.Header(size: UInt16(49), dataOffset: dataOffset, length: UInt32(data.count), offset: offset, fileId: fileId, _channel: channel.rawValue, remainingBytes: remainingBytes, channelInfoOffset: channelInfoOffset, channelInfoLength: channelInfoLength, flags: flags)
self.channelInfo = channelInfo
self.fileData = data
}
// codebeat:enable[ARITY]
func data() -> NSData {
let result = NSMutableData(data: encode(self.header))
func data() -> Data {
var result = Data(value: self.header)
if let channelInfo = channelInfo {
result.appendData(channelInfo.data())
result.append(channelInfo.data())
}
result.appendData(fileData)
result.append(fileData)
return result
}
struct Flags: OptionSetType {
struct Flags: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
@@ -144,42 +142,30 @@ extension SMB2 {
}
}
struct WriteResponse: SMBResponse {
struct WriteResponse: SMBResponseBody {
let size: UInt16
private let reserved: UInt16
fileprivate let reserved: UInt16
let writtenBytes: UInt32
private let remaining: UInt32
private let channelInfoOffset: UInt16
private let channelInfoLength: UInt16
init?(data: NSData) {
self = decode(data)
}
fileprivate let remaining: UInt32
fileprivate let channelInfoOffset: UInt16
fileprivate let channelInfoLength: UInt16
}
struct ChannelInfo: SMBRequest {
struct ChannelInfo: SMBRequestBody {
let offset: UInt64
let token: UInt32
let length: UInt32
func data() -> NSData {
return encode(data)
}
}
// MARK: SMB2 Lock
struct LockElement: SMBRequest {
struct LockElement: SMBRequestBody {
let offset: UInt64
let length: UInt64
let flags: LockElement.Flags
private let reserved: UInt32
fileprivate let reserved: UInt32
func data() -> NSData {
return encode(self)
}
struct Flags: OptionSetType {
struct Flags: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
@@ -193,7 +179,7 @@ extension SMB2 {
}
}
struct LockRequest: SMBRequest {
struct LockRequest: SMBRequestBody {
let header: LockRequest.Header
let locks: [LockElement]
@@ -202,23 +188,23 @@ extension SMB2 {
self.locks = locks
}
func data() -> NSData {
let result = NSMutableData(data: encode(header))
func data() -> Data {
var result = Data(value: header)
for lock in locks {
result.appendData(encode(lock))
result.append(Data(value: lock))
}
return result
}
struct Header {
let size: UInt16
private let lockCount: UInt16
fileprivate let lockCount: UInt16
let lockSequence: UInt32
let fileId : FileId
}
}
struct LockResponse: SMBResponse {
struct LockResponse: SMBResponseBody {
let size: UInt16
let reserved: UInt16
@@ -226,15 +212,11 @@ extension SMB2 {
self.size = 4
self.reserved = 0
}
init? (data: NSData) {
self = decode(data)
}
}
// MARK: SMB2 Cancel
struct CancelRequest: SMBRequest {
struct CancelRequest: SMBRequestBody {
let size: UInt16
let reserved: UInt16
@@ -242,10 +224,5 @@ extension SMB2 {
self.size = 4
self.reserved = 0
}
func data() -> NSData {
return encode(self)
}
}
}
}
+54 -86
View File
@@ -15,28 +15,28 @@ extension SMB2 {
* IOCtl usage is usually limited in SMB to pipe requests and duplicating file inside server
*/
struct IOCtlRequest: SMBRequest {
struct IOCtlRequest: SMBRequestBody {
let header: Header
let requestData: IOCtlRequestProtocol?
init(fileId: FileId ,ctlCode: IOCtlCode, requestData: IOCtlRequestProtocol?, flags: IOCtlRequest.Flags = []) {
let offset = requestData != nil ? UInt32(sizeof(SMB2.Header.self) + sizeof(IOCtlRequest.Header.self)) : 0
self.header = Header(size: 57, reserved: 0, _ctlCode: ctlCode.rawValue, fileId: fileId, inputOffset: offset, inputCount: UInt32((requestData?.data().length ?? 0)), maxInputResponse: 0, outputOffset: offset, outputCount: 0, maxOutputResponse: UInt32(Int32.max), flags: flags, reserved2: 0)
let offset = requestData != nil ? UInt32(MemoryLayout<SMB2.Header>.size + MemoryLayout<IOCtlRequest.Header>.size) : 0
self.header = Header(size: 57, reserved: 0, _ctlCode: ctlCode.rawValue, fileId: fileId, inputOffset: offset, inputCount: UInt32((requestData?.data().count ?? 0)), maxInputResponse: 0, outputOffset: offset, outputCount: 0, maxOutputResponse: UInt32(Int32.max), flags: flags, reserved2: 0)
self.requestData = requestData
}
func data() -> NSData {
let result = NSMutableData(data: encode(self.header))
func data() -> Data {
var result = Data(value: self.header)
if let reqData = requestData?.data() {
result.appendData(reqData)
result.append(reqData)
}
return result
}
struct Header {
let size: UInt16
private let reserved: UInt16
private let _ctlCode: UInt32
fileprivate let reserved: UInt16
fileprivate let _ctlCode: UInt32
var ctlCode: IOCtlCode {
return IOCtlCode(rawValue: _ctlCode)!
}
@@ -48,10 +48,10 @@ extension SMB2 {
let outputCount: UInt32
let maxOutputResponse: UInt32
let flags: IOCtlRequest.Flags
private let reserved2: UInt32
fileprivate let reserved2: UInt32
}
struct Flags: OptionSetType {
struct Flags: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
@@ -63,14 +63,14 @@ extension SMB2 {
}
}
struct IOCtlResponse: SMBResponse {
struct IOCtlResponse: SMBResponseBody {
let header: Header
let responseData: IOCtlResponseProtocol?
init?(data: NSData) {
self.header = decode(data)
let responseRange = NSRange(location: Int(self.header.outputOffset - 64), length: Int(self.header.outputCount))
let response = data.subdataWithRange(responseRange)
init?(data: Data) {
self.header = data.scanValue()!
let endRange = Int(self.header.outputOffset - 64) + Int(self.header.outputCount)
let response = data.subdata(in: Int(self.header.outputOffset - 64)..<endRange)
switch self.header.ctlCode {
case .SRV_COPYCHUNK, .SRV_COPYCHUNK_WRITE:
self.responseData = IOCtlResponseData.SrvCopyChunk(data: response)
@@ -91,8 +91,8 @@ extension SMB2 {
struct Header {
let size: UInt16
private let reserved: UInt16
private let _ctlCode: UInt32
fileprivate let reserved: UInt16
fileprivate let _ctlCode: UInt32
var ctlCode: IOCtlCode {
return IOCtlCode(rawValue: _ctlCode)!
}
@@ -101,8 +101,8 @@ extension SMB2 {
let inputCount: UInt32
let outputOffset: UInt32
let outputCount: UInt32
private let flags: UInt32
private let reserved2: UInt32
fileprivate let flags: UInt32
fileprivate let reserved2: UInt32
}
}
@@ -139,23 +139,19 @@ extension SMB2 {
let chunkCount: UInt32
let chunks: [Chunk]
func data() -> NSData {
let result = NSMutableData(data: encode(sourceKey))
result.appendData(encode(chunkCount))
var reserved: UInt32 = 0
result.appendData(encode(&reserved))
return NSData()
func data() -> Data {
var result = Data(value: sourceKey)
result.append(Data(value: chunkCount))
let reserved: UInt32 = 0
result.append(Data(value: reserved))
return Data()
}
struct Chunk {
let sourceOffset: UInt64
let targetOffset: UInt64
let length: UInt32
private let reserved: UInt32
func data() -> NSData {
return encode(self)
}
fileprivate let reserved: UInt32
}
}
@@ -182,25 +178,17 @@ extension SMB2 {
self.length = length
self.offset = offset
}
func data() -> NSData {
return encode(self)
}
}
struct ResilencyRequest: IOCtlRequestProtocol {
let timeout: UInt32
private let reserved: UInt32
fileprivate let reserved: UInt32
/// The requested time the server holds the file open after a disconnect before releasing it. This time is in milliseconds.
init(timeout: UInt32) {
self.timeout = timeout
self.reserved = 0
}
func data() -> NSData {
return encode(self)
}
}
struct ValidateNegotiateInfo: IOCtlRequestProtocol {
@@ -212,9 +200,9 @@ extension SMB2 {
self.dialects = dialects
}
func data() -> NSData {
let result = NSMutableData(data: encode(self.header))
dialects.forEach { result.appendData(encode($0)) }
func data() -> Data {
var result = Data(value: self.header)
dialects.forEach { result.append(Data(value: $0)) }
return result
}
@@ -234,31 +222,29 @@ extension SMB2 {
let chunksCount: UInt32
let chunksBytesWritten: UInt32
let totalBytesWriiten: UInt32
init?(data: NSData) {
self = decode(data)
}
}
// SRV_ENUMERATE_SNAPSHOTS
struct SrvSnapshots: IOCtlResponseProtocol {
let count: UInt32
let returnedCount: UInt32
let snapshots: [SMBTime]
init?(data: NSData) {
self.count = decode(data)
let returnedCount: UInt32 = decode(data.subdataWithRange(NSRange(location: 4, length: 4)))
init?(data: Data) {
guard data.count > 8 else { return nil }
self.count = data.scanValue()!
self.returnedCount = data.scanValue(start: 4)!
//let size: UInt32 = decode(data.subdataWithRange(NSRange(location: 8, length: 4)))
var snapshots = [SMBTime]()
let dateFormatter = NSDateFormatter()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "'@GMT-'yyyy'.'MM'.'dd'-'HH'.'mm'.'ss"
for i in 0..<Int(returnedCount) {
let offset = 24 + i * 48
if data.length < offset + 48 {
if data.count < offset + 48 {
return nil
}
let datestring = String(data: data.subdataWithRange(NSRange(location: offset, length: 48)), encoding: NSUTF16StringEncoding)
if let datestring = datestring, let date = dateFormatter.dateFromString(datestring) {
let datestring = data.scanString(start: offset, length: 48, using: .utf16)
if let datestring = datestring, let date = dateFormatter.date(from: datestring) {
snapshots.append(SMBTime(date: date))
}
}
@@ -268,34 +254,23 @@ extension SMB2 {
struct ResumeKey: IOCtlResponseProtocol {
let key: (UInt64, UInt64, UInt64)
private let contextLength: UInt32
private let context: UInt32
init?(data: NSData) {
self = decode(data)
}
fileprivate let contextLength: UInt32
fileprivate let context: UInt32
}
struct ReadHash: IOCtlResponseProtocol {
// TODO: Implement IOCTL READ_HASH
init?(data: NSData) {
self = decode(data)
}
}
struct NetworkInterfaceInfo: IOCtlResponseProtocol {
let items: [NetworkInterfaceInfo.Item]
init?(data: NSData) {
let count = data.length / sizeof(Item)
guard count > 0 else {
return nil
}
init?(data: Data) {
var items = [Item]()
for i in 0..<count {
let itemdata = data.subdataWithRange(NSRange(location: i * sizeof(Item), length: sizeof(Item)))
items.append(decode(itemdata))
var offset = 0
while let item: Item = data.scanValue(start: offset) {
items.append(item)
offset += MemoryLayout<Item>.size
}
self.items = items
}
@@ -306,10 +281,11 @@ extension SMB2 {
/// specifies the network interface index.
let ifIndex: UInt32
let capability: IOCtlCapabilities
private let reserved: UInt32
fileprivate let reserved: UInt32
/// Speed of the network interface in bits per second
let linkSpeed: UInt64
private let sockaddrStorage: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
fileprivate let sockaddrStorage:
(UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
@@ -333,15 +309,11 @@ extension SMB2 {
static let ipv6: sa_family_t = 0x17
var sockaddr: sockaddr_in {
var sockaddrStorage = self.sockaddrStorage
let data = NSData(bytes: &sockaddrStorage, length: 16)
return decode(data)
return Data.mapMemory(from: self.sockaddrStorage)!
}
var sockaddr6: sockaddr_in6 {
var sockaddrStorage = self.sockaddrStorage
let data = NSData(bytes: &sockaddrStorage, length: 28)
return decode(data)
return Data.mapMemory(from: self.sockaddrStorage)!
}
}
}
@@ -350,18 +322,14 @@ extension SMB2 {
let capabilities: IOCtlCapabilities
let guid: uuid_t
let securityMode: UInt16
private let _dialect: UInt16
fileprivate let _dialect: UInt16
var dialect: (major: Int, minor: Int) {
return (major: Int(_dialect & 0xFF), minor: Int(_dialect >> 8))
}
init?(data: NSData) {
self = decode(data)
}
}
}
struct IOCtlCapabilities: OptionSetType {
struct IOCtlCapabilities: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
@@ -385,4 +353,4 @@ extension SMB2 {
case HASH_BASED = 0x00000001
case FILE_BASED = 0x00000002
}
}
}
+133
View File
@@ -0,0 +1,133 @@
//
// SMB2Notification.swift
// FileProvider
//
// Created by Amir Abbas Mousavian on 5/18/95.
//
//
import Foundation
extension SMB2 {
// MARK: SMB2 Change Notify
struct ChangeNotifyRequest: SMBRequestBody {
let size: UInt16
let flags: ChangeNotifyRequest.Flags
let outputBufferLength: UInt32
let fileId: FileId
let completionFilters: CompletionFilter
fileprivate let reserved: UInt32
init(fileId: FileId, completionFilters: CompletionFilter, flags: ChangeNotifyRequest.Flags = [], outputBufferLength: UInt32 = 65535) {
self.size = 32
self.flags = flags
self.outputBufferLength = outputBufferLength
self.fileId = fileId
self.completionFilters = completionFilters
self.reserved = 0
}
struct Flags: OptionSet {
let rawValue: UInt16
init(rawValue: UInt16) {
self.rawValue = rawValue
}
static let WATCH_TREE = Flags(rawValue: 0x0001)
}
struct CompletionFilter: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
/// The client is notified if a file-name changes.
static let FILE_NAME = CompletionFilter(rawValue: 0x00000001)
/// The client is notified if a directory name changes.
static let DIR_NAME = CompletionFilter(rawValue: 0x00000002)
/// The client is notified if a file's attributes change.
static let ATTRIBUTES = CompletionFilter(rawValue: 0x00000004)
/// The client is notified if a file's size changes.
static let SIZE = CompletionFilter(rawValue: 0x00000008)
/// The client is notified if the last write time of a file changes.
static let LAST_WRITE = CompletionFilter(rawValue: 0x00000010)
/// The client is notified if the last access time of a file changes.
static let LAST_ACCESS = CompletionFilter(rawValue: 0x00000020)
/// The client is notified if the creation time of a file changes.
static let CREATION = CompletionFilter(rawValue: 0x00000040)
/// The client is notified if a file's extended attributes (EAs) change.
static let EA = CompletionFilter(rawValue: 0x00000080)
/// The client is notified of a file's access control list (ACL) settings change.
static let SECURITY = CompletionFilter(rawValue: 0x00000100)
/// The client is notified if a named stream is added to a file.
static let STREAM_NAME = CompletionFilter(rawValue: 0x00000200)
/// The client is notified if the size of a named stream is changed.
static let STREAM_SIZE = CompletionFilter(rawValue: 0x00000400)
/// The client is notified if a named stream is modified.
static let STREAM_WRITE = Flags(rawValue: 0x00000800)
static let all = CompletionFilter(rawValue: 0x00000FFF)
static let list: CompletionFilter = [.FILE_NAME, .DIR_NAME]
}
}
struct ChangeNotifyResponse: SMBResponseBody {
let notifications: [(action: FileNotifyAction, fileName: String)]
init?(data: Data) {
let maxLoop = 1000
var i = 0
var result = [(action: FileNotifyAction, fileName: String)]()
var offset = 0
while i < maxLoop {
let nextOffset: UInt32 = data.scanValue(start: offset) ?? 0
let actionValue: UInt32 = data.scanValue(start: offset + 4) ?? 0
guard let action = FileNotifyAction(rawValue: actionValue) else {
continue
}
let fileNameLen = Int(data.scanValue(start: offset + 8) as UInt32? ?? 0)
let fileName = data.scanString(start: offset + 12, length: fileNameLen, using: .utf16) ?? ""
result.append((action: action, fileName: fileName))
offset += Int(nextOffset)
if nextOffset == 0 {
break
}
i += 1
}
self.notifications = result
}
}
enum FileNotifyAction: UInt32 {
/// The file was added to the directory.
case ADDED = 0x00000001
/// The file was removed from the directory.
case REMOVED = 0x00000002
/// The file was modified. This can be a change to the data or attributes of the file.
case MODIFIED = 0x00000003
/// The file was renamed, and this is the old name. If the new name resides outside of the directory being monitored, the client will not receive the FILE_ACTION_RENAMED_NEW_NAME bit value.
case RENAMED_OLD_NAME = 0x00000004
/// The file was renamed, and this is the new name. If the old name resides outside of the directory being monitored, the client will not receive the FILE_ACTION_RENAME_OLD_NAME bit value.
case RENAMED_NEW_NAME = 0x00000005
/// The file was added to a named stream.
case ADDED_STREAM = 0x00000006
/// The file was removed from the named stream.
case REMOVED_STREAM = 0x00000007
/// The file was modified. This can be a change to the data or attributes of the file.
case MODIFIED_STREAM = 0x00000008
/// An object ID was removed because the file the object ID referred to was deleted. This notification is only sent when the directory being monitored is the special directory "\$Extend\$ObjId:$O:$INDEX_ALLOCATION".
case REMOVED_BY_DELETE = 0x00000009
/// An attempt to tunnel object ID information to a file being created or renamed failed because the object ID is in use by another file on the same volume. This notification is only sent when the directory being monitored is the special directory "\$Extend\$ObjId:$O:$INDEX_ALLOCATION".
case NOT_TUNNELLED = 0x0000000A
/// An attempt to tunnel object ID information to a file being renamed failed because the file already has an object ID. This notification is only sent when the directory being monitored is the special directory "\$Extend\$ObjId:$O:$INDEX_ALLOCATION".
case TUNNELLED_ID_COLLISION = 0x0000000B
}
}
+309 -2
View File
@@ -11,8 +11,315 @@ import Foundation
extension SMB2 {
// MARK: SMB2 Query Directory
// MARK: SMB2 Change Notify
struct QueryDirectoryRequest: SMBRequestBody {
let header: QueryDirectoryRequest.Header
let searchPattern: String?
/// - **bufferLength:** maximum number of bytes the server is allowed to return which is the same as maxTransactSize returned by negotiation.
/// - **searchPattern:** can hold wildcards or be nil if all entries should be returned.
/// - **fileIndex:** The byte offset within the directory, indicating the position at which to resume the enumeration.
init(fileId: FileId, infoClass: FileInformationEnum, flags: Flags, bufferLength: UInt32 = 65535, searchPattern: String? = nil, fileIndex: UInt32 = 0) {
assert(FileInformationEnum.queryDirectory.contains(infoClass), "Invalid FileInformationClass used for QueryDirectoryRequest")
let searchPatternOffset = searchPattern != nil ? MemoryLayout<SMB2.Header>.size + MemoryLayout<QueryDirectoryRequest.Header>.size : 0
let nflags = flags.intersection(fileIndex > 0 ? [.INDEX_SPECIFIED] : [])
let searchPatternLength = searchPattern?.data(using: .utf16)?.count ?? 0
self.header = Header(size: 53, infoClass: infoClass, flags: nflags, fileIndex: fileIndex, fileId: fileId, searchPatternOffset: UInt8(searchPatternOffset), searchPatternLength: UInt8(searchPatternLength), bufferLength: bufferLength)
self.searchPattern = searchPattern
}
func data() -> Data {
var result = Data(value: header)
if let patternData = searchPattern?.data(using: .utf16) {
result.append(patternData)
}
return result
}
struct Header {
let size: UInt8
let infoClass: FileInformationEnum
let flags: QueryDirectoryRequest.Flags
let fileIndex: UInt32
let fileId: FileId
let searchPatternOffset: UInt8
let searchPatternLength: UInt8
let bufferLength: UInt32
}
struct Flags: OptionSet {
let rawValue: UInt8
init(rawValue: UInt8) {
self.rawValue = rawValue
}
static let RESTART_SCANS = Flags(rawValue: 0x01)
static let RETURN_SINGLE_ENTRY = Flags(rawValue: 0x02)
static let INDEX_SPECIFIED = Flags(rawValue: 0x04)
static let REOPEN = Flags(rawValue: 0x10)
}
}
struct QueryDirectoryResponse: SMBResponseBody {
let buffer: Data
func parseAs(type: FileInformationEnum) -> [(header: SMB2FilesInformationHeader, fileName: String)] {
var offset = 0
var result = [(header: SMB2FilesInformationHeader, fileName: String)]()
while true {
let header: SMB2FilesInformationHeader
switch type {
case .fileDirectoryInformation:
header = buffer.scanValue(start: offset) as FileDirectoryInformationHeader!
case .fileFullDirectoryInformation:
header = buffer.scanValue(start: offset) as FileFullDirectoryInformationHeader!
case .fileIdFullDirectoryInformation:
header = buffer.scanValue(start: offset) as FileIdFullDirectoryInformationHeader!
case .fileBothDirectoryInformation:
header = buffer.scanValue(start: offset) as FileBothDirectoryInformationHeader!
case .fileIdBothDirectoryInformation:
header = buffer.scanValue(start: offset) as FileIdBothDirectoryInformationHeader!
case .fileNamesInformation:
header = buffer.scanValue(start: offset) as FileNamesInformationHeader!
default:
return []
}
let headersize = MemoryLayout.size(ofValue: header)
let fileName = buffer.scanString(start: headersize, length: Int(header.fileNameLength), using: .utf16) ?? ""
result.append((header: header, fileName: fileName))
if header.nextEntryOffset == 0 {
break
}
offset += Int(header.nextEntryOffset)
}
return result
}
init? (data: Data) {
let offset = Int(data.scanValue(start: 2) as UInt16!)
let length = Int(data.scanValue(start: 4) as UInt32!)
guard data.count > offset + length else {
return nil
}
self.buffer = data.subdata(in: offset..<(offset + length))
}
}
// MARK: SMB2 Query Info
}
struct QueryInfoRequest: SMBRequestBody {
let header: Header
let buffer: Data?
init(fileId: FileId, infoClass: FileInformationEnum, outputBufferLength: UInt32 = 65535) {
self.header = Header(size: 41, infoType: 1, infoClass: infoClass.rawValue, outputBufferLength: outputBufferLength, inputBufferOffset: 0, reserved: 0, inputBufferLength: 0, additionalInformation: [], flags: [], fileId: fileId)
self.buffer = nil
}
init(fileId: FileId, extendedAttributes: [String], flags: Flags = [], outputBufferLength: UInt32 = 65535) {
var buffer = Data()
for ea in extendedAttributes {
guard let strData = ea.data(using: .ascii) else {
continue
}
let strLength = UInt8(strData.count)
let nextOffset = UInt32(4 + 1 + strData.count)
var data = Data(value: nextOffset)
data.append(Data(value: strLength))
data.append(strData)
data.count += 1
let padSize = (data.count) % 4
data.count += padSize
buffer.append(data as Data)
}
let bufferOffset = UInt16(MemoryLayout<SMB2.Header>.size + MemoryLayout<QueryInfoRequest.Header>.size)
self.header = Header(size: 41, infoType: 1, infoClass: FileInformationEnum.fileFullEaInformation.rawValue, outputBufferLength: outputBufferLength, inputBufferOffset: bufferOffset, reserved: 0, inputBufferLength: UInt32(buffer.count), additionalInformation: [], flags: flags, fileId: fileId)
self.buffer = buffer as Data
}
init(fileId: FileId, infoClass: FileSystemInformationEnum, outputBufferLength: UInt32 = 65535) {
self.header = Header(size: 41, infoType: 2, infoClass: infoClass.rawValue, outputBufferLength: outputBufferLength, inputBufferOffset: 0, reserved: 0, inputBufferLength: 0, additionalInformation: [], flags: [], fileId: fileId)
self.buffer = nil
}
init(fileId: FileId, securityInfo: FileSecurityInfo, outputBufferLength: UInt32 = 65535) {
self.header = Header(size: 41, infoType: 3, infoClass: 0, outputBufferLength: outputBufferLength, inputBufferOffset: 0, reserved: 0, inputBufferLength: 0, additionalInformation: securityInfo, flags: [], fileId: fileId)
self.buffer = nil
}
// TODO: Implement QUOTA_INFO init
func data() -> Data {
var result = Data(value: header)
if let buffer = buffer {
result.append(buffer)
}
return result
}
struct Header {
let size: UInt16
let infoType: UInt8
let infoClass: UInt8
let outputBufferLength: UInt32
let inputBufferOffset: UInt16
fileprivate let reserved: UInt16
let inputBufferLength: UInt32
let additionalInformation: FileSecurityInfo
let flags: QueryInfoRequest.Flags
let fileId: FileId
}
struct Flags: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
static let RESTART_SCAN = Flags(rawValue: 0x00000001)
static let RETURN_SINGLE_ENTRY = Flags(rawValue: 0x00000002)
static let INDEX_SPECIFIED = Flags(rawValue: 0x00000004)
}
}
struct QueryInfoResponse: SMBResponseBody {
let buffer: Data
init?(data: Data) {
let structSize: UInt16 = data.scanValue()!
guard structSize == 9 else {
return nil
}
/*let offsetData = data.subdataWithRange(NSRange(location: 2, length: 2))
let offset: UInt16 = decode(offsetData)*/
let length = Int(data.scanValue(start: 4) as UInt32!)
guard data.count >= 8 + length else {
return nil
}
self.buffer = data.subdata(in: 8..<(8 + length))
}
var asAccessInformation: FileAccessInformation {
return buffer.scanValue()!
}
var asAlignmentInformation: FileAlignmentInformation {
return buffer.scanValue()!
}
var asAllInformation: (header: FileAllInformationHeader, name: String) {
let header: FileAllInformationHeader = buffer.scanValue()!
let headersize = MemoryLayout<FileAllInformationHeader>.size
let name = buffer.scanString(start: headersize, length: Int(header.nameLength), using: .utf16) ?? ""
return (header, name)
}
var asAlternateNameInformation: String {
return buffer.scanString(start: 0, length: buffer.count, using: .utf16) ?? ""
}
var asAttributeTagInformation: FileAttributeTagInformation {
return buffer.scanValue()!
}
var asBasicInformation: FileBasicInformation {
return buffer.scanValue()!
}
var asCompressionInformation: FileCompressionInformation {
return buffer.scanValue()!
}
var asEaInformation: FileEaInformation {
return buffer.scanValue()!
}
var asFullEaInformation: FileFullEaInformation {
// TODO:
return FileFullEaInformation()
}
var asInternalInformation: FileInternalInformation {
return buffer.scanValue()!
}
var asModeInformation: FileModeInformation {
return buffer.scanValue()!
}
var asNetworkOpenInformation: FileNetworkOpenInformation {
return buffer.scanValue()!
}
var asPipeInformation: FilePipeInformation {
return buffer.scanValue()!
}
var asPipeLocalInformation: FilePipeLocalInformation {
return buffer.scanValue()!
}
var asPipeRemoteInformation: FilePipeRemoteInformation {
return buffer.scanValue()!
}
var asPositionInformation: FilePositionInformation {
return buffer.scanValue()!
}
var asStandardInformation: FileStandardInformation {
return buffer.scanValue()!
}
var asStreamInformation: (header: FileStreamInformationHeader, name: String) {
let header: FileStreamInformationHeader = buffer.scanValue()!
let headersize = MemoryLayout<FileStreamInformationHeader>.size
let name = buffer.scanString(start: headersize, length: Int(header.streamNameLength), using: .utf16) ?? ""
return (header, name)
}
var asFsVolumeInformation: (header: FileFsVolumeInformationHeader, name: String) {
let header: FileFsVolumeInformationHeader = buffer.scanValue()!
let headersize = MemoryLayout<FileFsVolumeInformationHeader>.size
let name = buffer.scanString(start: headersize, length: Int(header.labelLength), using: .utf16) ?? ""
return (header, name)
}
var asFsSizeInformation: FileFsSizeInformation {
return buffer.scanValue()!
}
var asFsDeviceInformation: FileFsDeviceInformation {
return buffer.scanValue()!
}
var asFsAttributeInformation: (header: FileFsAttributeInformationHeader, name: String) {
let header: FileFsAttributeInformationHeader = buffer.scanValue()!
let headersize = MemoryLayout<FileFsAttributeInformationHeader>.size
let name = buffer.scanString(start: headersize, length: Int(header.nameLength), using: .utf16) ?? ""
return (header, name)
}
var asFsControlInformation: FileFsControlInformation {
return buffer.scanValue()!
}
var asFsFullSizeInformation: FileFsFullSizeInformation {
return buffer.scanValue()!
}
var asFsObjectIdInformation: FileFsObjectIdInformation {
return buffer.scanValue()!
}
var asFsSectorSizeInformation: FileFsSectorSizeInformation {
return buffer.scanValue()!
}
}
}
+597
View File
@@ -0,0 +1,597 @@
//
// SMB2QueryTypes.swift
// FileProvider
//
// Created by Amir Abbas Mousavian on 5/19/95.
//
//
import Foundation
protocol SMB2FilesInformationHeader: SMBResponseBody {
var nextEntryOffset: UInt32 { get }
var fileIndex: UInt32 { get }
var fileNameLength : UInt32 { get }
}
extension SMB2 {
enum FileInformationEnum: UInt8 {
case none = 0x00
case fileDirectoryInformation = 0x01
case fileFullDirectoryInformation = 0x02
case fileBothDirectoryInformation = 0x03
case fileBasicInformation = 0x04
case fileStandardInformation = 0x05
case fileInternalInformation = 0x06
case fileEaInformation = 0x07
case fileAccessInformation = 0x08
case fileNameInformation = 0x09
case fileRenameInformation = 0x0A
case fileLinkInformation = 0x0B
case fileNamesInformation = 0x0C
case fileDispositionInformation = 0x0D
case filePositionInformation = 0x0E
case fileFullEaInformation = 0x0F
case fileModeInformation = 0x10
case fileAlignmentInformation = 0x11
case fileAllInformation = 0x12
case fileAllocationInformation = 0x13
case fileEndOfFileInformation = 0x14
case fileAlternateNameInformation = 0x15
case fileStreamInformation = 0x16
case filePipeInformation = 0x17
case filePipeLocalInformation = 0x18
case filePipeRemoteInformation = 0x19
case fileMailslotQueryInformation = 0x1A
case fileMailslotSetInformation = 0x1B
case fileCompressionInformation = 0x1C
case fileObjectIdInformation = 0x1D
case fileCompletionInformation = 0x1E
case fileMoveClusterInformation = 0x1F
case fileQuotaInformation = 0x20
case fileReparsePointInformation = 0x21
case fileNetworkOpenInformation = 0x22
case fileAttributeTagInformation = 0x23
case fileTrackingInformation = 0x24
case fileIdBothDirectoryInformation = 0x25
case fileIdFullDirectoryInformation = 0x26
case fileValidDataLengthInformation = 0x27
case fileShortNameInformation = 0x28
case fileIoCompletionNotificationInformation = 0x29
case fileIoStatusBlockRangeInformation = 0x2A
case fileIoPriorityHintInformation = 0x2B
case fileSfioReserveInformation = 0x2C
case fileSfioVolumeInformation = 0x2D
case fileHardLinkInformation = 0x2E
case fileProcessIdsUsingFileInformation = 0x2F
case fileNormalizedNameInformation = 0x30
case fileNetworkPhysicalNameInformation = 0x31
case fileIdGlobalTxDirectoryInformation = 0x32
case fileIsRemoteDeviceInformation = 0x33
case fileUnusedInformation = 0x34
case fileNumaNodeInformation = 0x35
case fileStandardLinkInformation = 0x36
case fileRemoteProtocolInformation = 0x37
case fileRenameInformationBypassAccessCheck = 0x38
case fileLinkInformationBypassAccessCheck = 0x39
case fileVolumeNameInformation = 0x3A
case fileIdInformation = 0x3B
case fileIdExtdDirectoryInformation = 0x3C
case fileReplaceCompletionInformation = 0x3D
case fileHardLinkFullIdInformation = 0x3E
case fileIdExtdBothDirectoryInformation = 0x3F
case fileMaximumInformation = 0x40
static let queryDirectory: [FileInformationEnum] = [.fileDirectoryInformation, .fileFullDirectoryInformation, .fileIdFullDirectoryInformation, .fileBothDirectoryInformation, .fileIdBothDirectoryInformation, .fileNamesInformation]
static let queryInfoFile: [FileInformationEnum] = [.fileAccessInformation, .fileAlignmentInformation, .fileAllInformation, .fileAlternateNameInformation, .fileAttributeTagInformation, .fileBasicInformation, .fileCompressionInformation, fileEaInformation, .fileFullEaInformation, .fileInternalInformation, .fileModeInformation, .fileNetworkOpenInformation, .filePipeInformation, .filePipeLocalInformation, .filePipeRemoteInformation, .filePositionInformation, .fileStandardInformation, .fileStreamInformation]
}
enum FileSystemInformationEnum: UInt8 {
case none = 0
case fileFsAttributeInformation
case fileFsControlInformation
case fileFsDeviceInformation
case fileFsFullSizeInformation
case fileFsObjectIdInformation
case fileFsSectorSizeInformation
case fileFsSizeInformation
case fileFsVolumeInformation
}
struct FileSecurityInfo: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
static let OWNER = FileSecurityInfo(rawValue: 0x00000001)
static let GROUP = FileSecurityInfo(rawValue: 0x00000002)
static let DACL = FileSecurityInfo(rawValue: 0x00000004)
static let SACL = FileSecurityInfo(rawValue: 0x00000008)
static let LABEL = FileSecurityInfo(rawValue: 0x00000010)
static let ATTRIBUTE = FileSecurityInfo(rawValue: 0x00000020)
static let SCOPE = FileSecurityInfo(rawValue: 0x00000040)
static let BACKUP = FileSecurityInfo(rawValue: 0x00010000)
}
struct FileDirectoryInformationHeader: SMB2FilesInformationHeader {
let nextEntryOffset: UInt32
let fileIndex: UInt32
let creationTime: SMBTime
let lastAccesTime: SMBTime
let lastWriteTime: SMBTime
let changeTime: SMBTime
let fileSize: UInt64
let allocationSize: UInt64
let fileAttributes: FileAttributes
let fileNameLength : UInt32
}
struct FileFullDirectoryInformationHeader: SMB2FilesInformationHeader {
let nextEntryOffset: UInt32
let fileIndex: UInt32
let creationTime: SMBTime
let lastAccesTime: SMBTime
let lastWriteTime: SMBTime
let changeTime: SMBTime
let fileSize: UInt64
let allocationSize: UInt64
let fileAttributes: FileAttributes
let fileNameLength : UInt32
let extendedAttributesSize: UInt32
}
struct FileIdFullDirectoryInformationHeader: SMB2FilesInformationHeader {
let nextEntryOffset: UInt32
let fileIndex: UInt32
let creationTime: SMBTime
let lastAccesTime: SMBTime
let lastWriteTime: SMBTime
let changeTime: SMBTime
let fileSize: UInt64
let allocationSize: UInt64
let fileAttributes: FileAttributes
let fileNameLength : UInt32
let extendedAttributesSize: UInt32
fileprivate let reserved: UInt32
let fileId: FileId
}
struct FileBothDirectoryInformationHeader: SMB2FilesInformationHeader {
let nextEntryOffset: UInt32
let fileIndex: UInt32
let creationTime: SMBTime
let lastAccesTime: SMBTime
let lastWriteTime: SMBTime
let changeTime: SMBTime
let fileSize: UInt64
let allocationSize: UInt64
let fileAttributes: FileAttributes
let fileNameLength : UInt32
let extendedAttributesSize: UInt32
fileprivate let shortNameLen: UInt8
fileprivate let reserved: UInt8
fileprivate let _shortName: FileShortNameType
var shortName: String? {
var data = Data(value: _shortName)
data.count = Int(shortNameLen)
return String(data: data, encoding: .utf16)
}
}
struct FileIdBothDirectoryInformationHeader: SMB2FilesInformationHeader {
let nextEntryOffset: UInt32
let fileIndex: UInt32
let creationTime: SMBTime
let lastAccesTime: SMBTime
let lastWriteTime: SMBTime
let changeTime: SMBTime
let fileSize: Int64
let allocationSize: Int64
let fileAttributes: FileAttributes
let fileNameLength : UInt32
let extendedAttributesSize: UInt32
fileprivate let shortNameLen: UInt8
fileprivate let reserved: UInt8
fileprivate let _shortName: FileShortNameType
var shortName: String? {
var data = Data(value: _shortName)
data.count = Int(shortNameLen)
return String(data: data, encoding: .utf16)
}
fileprivate let reserved2: UInt16
let fileId : FileId
}
struct FileNamesInformationHeader: SMB2FilesInformationHeader {
let nextEntryOffset: UInt32
let fileIndex: UInt32
let fileNameLength : UInt32
}
typealias FileShortNameType = (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)
struct FileAccessInformation {
let accessMask: FileAccessMask
}
struct FileAlignmentInformation {
fileprivate let _alignment: UInt32
var alignmentLength: UInt32 {
return _alignment + 1
}
}
struct FileAllInformationHeader {
let basic: FileBasicInformation
let standard: FileStandardInformation
let `internal`: FileInternalInformation
let ea: FileEaInformation
let access: FileAccessInformation
let position: FilePositionInformation
let mode: FileModeInformation
let alignment: FileAlignmentInformation
let nameLength: UInt32
}
struct FileAttributeTagInformation {
let fileAttributes: FileAttributes
let reparseTag: UInt32
}
struct FileBasicInformation {
let creationTime: SMBTime
let lastAccesTime: SMBTime
let lastWriteTime: SMBTime
let changeTime: SMBTime
let fileAttributes: FileAttributes
fileprivate let reserved: UInt32 = 0
}
struct FileCompressionInformation {
let compressedFileSize: Int64
let compressionFormat: UInt16
static let COMPRESSION_FORMAT_LZNT1 = 0x0002
let compressionUnitShift: UInt8
let chunkShift: UInt8
let clusterShift: UInt8
fileprivate let reserved: (UInt8, UInt16)
}
struct FileEaInformation {
let eaSize: UInt32
}
struct FileFullEaInformation {
// TODO
}
struct FileInternalInformation {
let indexNumber: UInt64
}
struct FileModeInformation {
let mode: Mode
struct Mode: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
static let FILE_WRITE_THROUGH = Mode(rawValue: 0x00000002)
static let FILE_SEQUENTIAL_ONLY = Mode(rawValue: 0x00000004)
static let FILE_NO_INTERMEDIATE_BUFFERING = Mode(rawValue: 0x00000008)
static let FILE_SYNCHRONOUS_IO_ALERT = Mode(rawValue: 0x00000010)
static let FILE_SYNCHRONOUS_IO_NONALERT = Mode(rawValue: 0x00000020)
static let FILE_DELETE_ON_CLOSE = Mode(rawValue: 0x00001000)
}
}
struct FileNetworkOpenInformation {
let creationTime: SMBTime
let lastAccesTime: SMBTime
let lastWriteTime: SMBTime
let changeTime: SMBTime
let fileAttributes: FileAttributes
fileprivate let reserved: UInt32
}
struct FilePipeInformation {
fileprivate let _readMode: UInt32
var readMode: ReadMode {
return ReadMode(rawValue: _readMode) ?? .BYTE_STREAM_MODE
}
fileprivate let _completionMode: UInt32
var completionMode: CompletionMode {
return CompletionMode(rawValue: _completionMode) ?? .QUEUE_OPERATION
}
enum ReadMode: UInt32 {
case BYTE_STREAM_MODE = 0x00000000
case MESSAGE_MODE = 0x00000001
}
enum CompletionMode: UInt32 {
case QUEUE_OPERATION = 0x00000000
case COMPLETE_OPERATION = 0x00000001
}
}
struct FilePipeLocalInformation {
fileprivate let _namedPipeType: UInt32
var namedPipeType: Type {
return Type(rawValue: _namedPipeType) ?? .BYTE_STREAM_TYPE
}
fileprivate let _namedPipeConfiguration: UInt32
var namedPipeConfiguration: Configuration {
return Configuration(rawValue: _namedPipeConfiguration) ?? .INBOUND
}
let maximumInstances: UInt32
let currentInstances: UInt32
let inboundQuota: UInt32
let readDataAvailable: UInt32
let outboundQuota: UInt32
let writeQuotaAvailable: UInt32
fileprivate let _namedPipeState: UInt32
var namedPipeState: State {
return State(rawValue: _namedPipeState) ?? .DISCONNECTED_STATE
}
fileprivate let _namedPipeEnd: UInt32
var namedPipeEnd: End {
return End(rawValue: _namedPipeEnd) ?? .CLIENT_END
}
enum `Type`: UInt32 {
case BYTE_STREAM_TYPE = 0x00000000
case MESSAGE_TYPE = 0x00000001
}
enum Configuration: UInt32 {
case INBOUND = 0x00000000
case OUTBOUND = 0x00000001
case FULL_DUPLEX = 0x00000002
}
enum State: UInt32 {
case DISCONNECTED_STATE = 0x00000001
case LISTENING_STATE = 0x00000002
case CONNECTED_STATE = 0x00000003
case CLOSING_STATE = 0x00000004
}
enum End: UInt32 {
case CLIENT_END = 0x00000000
case SERVER_END = 0x00000001
}
}
struct FilePipeRemoteInformation {
let collectDataTime: SMBTime
let maximumCollectionCount: UInt32
}
struct FilePositionInformation {
let currentByteOffset: Int64
}
struct FileStandardInformation {
let allocationSize: Int64
let fileSize: Int64
let numberOfLinks: UInt32
let deletePending: Bool
let directory: Bool
fileprivate let reserved: UInt16
}
struct FileStreamInformationHeader {
let nextEntryOffset: UInt32
let streamNameLength: UInt32
let streamSize: Int64
let streamAllocationSize: Int64
}
struct FileFsVolumeInformationHeader {
let creationTime: SMBTime
let serial: UInt32
let labelLength: UInt32
let supportObjects: Bool
let reserved: UInt8
}
struct FileFsSizeInformation {
let totalAllocationUnits: Int64
let availableAllocationUnits: Int64
let sectorsPerAllocationUnit: UInt32
let bytesPerSector: UInt32
}
struct FileFsDeviceInformation {
fileprivate let _deviceType: UInt32
var deviceType: DeviceType {
return DeviceType(rawValue: _deviceType) ?? .DISK
}
let charactristics: Charactristics
enum DeviceType: UInt32 {
case CD_ROM = 0x00000002
case DISK = 0x00000007
}
struct Charactristics: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
/// Storage device supports removable media. For example, drivers for JAZ drive devices specify this characteristic, but drivers for PCMCIA flash disks do not.
static let REMOVABLE_MEDIA = Charactristics(rawValue: 0x00000001)
/// Indicates that the device cannot be written to.
static let READ_ONLY_DEVICE = Charactristics(rawValue: 0x00000002)
/// Indicates that the device is a floppy disk device.
static let FLOPPY_DISKETTE = Charactristics(rawValue: 0x00000004)
/// Indicates that the device supports write-once media.
static let WRITE_ONCE_MEDIA = Charactristics(rawValue: 0x00000008)
/// ndicates that the volume is for a remote file system like SMB or CIFS.
static let REMOTE_DEVICE = Charactristics(rawValue: 0x00000010)
/// Indicates that a file system is mounted on the device.
static let DEVICE_IS_MOUNTED = Charactristics(rawValue: 0x00000020)
/// Indicates that the volume does not directly reside on storage media, but resides on some other type of media (memory for example).
static let VIRTUAL_VOLUME = Charactristics(rawValue: 0x00000040)
/// By default, volumes do not check the ACL associated with the volume, but instead use the ACLs associated with individual files on the volume. When this flag is set the volume ACL is also checked.
static let DEVICE_SECURE_OPEN = Charactristics(rawValue: 0x00000100)
/// Indicates that the device object is part of a Terminal Services device stack.
static let TS_DEVICE = Charactristics(rawValue: 0x00001000)
/// ndicates that a web-based Distributed Authoring and Versioning (WebDAV) file system is mounted on the device.
static let WEBDAV_DEVICE = Charactristics(rawValue: 0x00002000)
/// The IO Manager normally performs a full security check for traverse access on every file open when the client is an appcontainer. Setting of this flag bypasses this enforced traverse access check if the client token already has traverse privileges.
static let PORTABLE_DEVICE = Charactristics(rawValue: 0x0004000)
/// Indicates that the given device resides on a portable bus like USB or Firewire and that the entire device (not just the media) can be removed from the system.
static let DEVICE_ALLOW_APPCONTAINER_TRAVERSAL = Charactristics(rawValue: 0x00020000)
}
}
struct FileFsAttributeInformationHeader {
let attributes: Attributes
let maximumFileNameLength: Int32
let nameLength: UInt32
struct Attributes: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
/// The file system supports case-sensitive file names when looking up (searching for) file names in a directory.
static let CASE_SENSITIVE_SEARCH = Attributes(rawValue: 0x00000001)
/// The file system preserves the case of file names when it places a name on disk.
static let CASE_PRESERVED_NAMES = Attributes(rawValue: 0x00000002)
/// The file system supports Unicode in file and directory names. This flag applies only to file and directory names; the file system neither restricts nor interprets the bytes of data within a file.
static let UNICODE_ON_DISK = Attributes(rawValue: 0x00000004)
/// The file system preserves and enforces access control lists (ACLs).
static let PERSISTENT_ACLS = Attributes(rawValue: 0x00000008)
/// The file volume supports file-based compression. This flag is incompatible with the FILE_VOLUME_IS_COMPRESSED flag.
static let FILE_COMPRESSION = Attributes(rawValue: 0x00000010)
/// The file system supports per-user quotas.
static let VOLUME_QUOTAS = Attributes(rawValue: 0x00000020)
/// The file system supports sparse files.
static let SUPPORTS_SPARSE_FILES = Attributes(rawValue: 0x00000040)
/// The file system supports reparse points.
static let SUPPORTS_REPARSE_POINTS = Attributes(rawValue: 0x00000080)
/// The file system supports remote storage.
static let REMOTE_STORAGE = Attributes(rawValue: 0x00000100)
/// The specified volume is a compressed volume. This flag is incompatible with the FILE_FILE_COMPRESSION flag.
static let IS_COMPRESSED = Attributes(rawValue: 0x00008000)
/// The file system supports object identifiers.
static let OBJECT_IDS = Attributes(rawValue: 0x00010000)
/// The file system supports the Encrypted File System (EFS).
static let ENCRYPTION = Attributes(rawValue: 0x00020000)
/// The file system supports named streams. (aka. Resource Fork on MacOS)
static let NAMED_STREAMS = Attributes(rawValue: 0x00040000)
/// If set, the volume has been mounted in read-only mode.
static let READ_ONLY_VOLUME = Attributes(rawValue: 0x00080000)
/// The underlying volume is write once. (aka tapes)
static let SEQUENTIAL_WRITE_ONCE = Attributes(rawValue: 0x00100000)
/// The volume supports transactions.
static let SUPPORTS_TRANSACTIONS = Attributes(rawValue: 0x00200000)
/// The file system supports hard linking files.
static let SUPPORTS_HARD_LINKS = Attributes(rawValue: 0x00400000)
/// The file system persistently stores Extended Attribute information per file.
static let SUPPORTS_EXTENDED_ATTRIBUTES = Attributes(rawValue: 0x00800000)
/// The file system supports opening a file by FileID or ObjectID.
static let SUPPORTS_OPEN_BY_FILE_ID = Attributes(rawValue: 0x01000000)
/// The file system implements a USN change journal.
static let USN_JOURNAL = Attributes(rawValue: 0x02000000)
/// The file system supports integrity streams.
static let SUPPORT_INTEGRITY_STREAMS = Attributes(rawValue: 0x04000000)
/// The file system supports sharing logical clusters between files on the same volume. The file system reallocates on writes to shared clusters. Indicates that FSCTL_DUPLICATE_EXTENTS_TO_FILE is a supported operation.
static let SUPPORTS_BLOCK_REFCOUNTING = Attributes(rawValue: 0x08000000)
/// The file system tracks whether each cluster of a file contains valid data (either from explicit file writes or automatic zeros) or invalid data (has not yet been written to or zeroed).
static let SUPPORTS_SPARSE_VDL = Attributes(rawValue: 0x10000000)
}
}
struct FileFsControlInformation {
let freeSpaceStartFiltering: Int64
let freeSpaceThreshold: Int64
let freeSpaceStopFiltering: Int64
let defaultQuotaThreshold: UInt64
let defaultQuotaLimit: UInt64
let flags: Flags
fileprivate let padding: UInt32 = 0
struct Flags: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
/// Quotas are tracked on the volume, but they are not enforced. Tracked quotas enable reporting on the file system space used by system users. If both this flag and FILE_VC_QUOTA_ENFORCE are specified, FILE_VC_QUOTA_ENFORCE is ignored.
static let QUOTA_TRACK = Flags(rawValue: 0x00000001)
/// Quotas are tracked and enforced on the volume.
static let QUOTA_ENFORCE = Flags(rawValue: 0x00000002)
/// Content indexing is disabled.
static let CONTENT_INDEX_DISABLED = Flags(rawValue: 0x00000008)
/// An event log entry will be created when the user exceeds his or her assigned quota warning threshold.
static let LOG_QUOTA_THRESHOLD = Flags(rawValue: 0x00000010)
/// An event log entry will be created when the user exceeds the assigned disk quota limit.
static let LOG_QUOTA_LIMIT = Flags(rawValue: 0x00000020)
/// An event log entry will be created when the volume's free space threshold is exceeded.
static let LOG_VOLUME_THRESHOLD = Flags(rawValue: 0x00000040)
/// An event log entry will be created when the volume's free space limit is exceeded.
static let LOG_VOLUME_LIMIT = Flags(rawValue: 0x00000080)
/// The quota information for the volume is incomplete because it is corrupt, or the system is in the process of rebuilding the quota information.
static let QUOTAS_INCOMPLETE = Flags(rawValue: 0x00000100)
/// The file system is rebuilding the quota information for the volume.
static let QUOTAS_REBUILDING = Flags(rawValue: 0x00000200)
}
}
struct FileFsFullSizeInformation {
let totalAllocationUnits: Int64
let callerAvailableAllocationUnits: Int64
let actualAvailableAllocationUnits: Int64
let sectorsPerAllocationUnit: UInt32
let bytesPerSector: UInt32
}
struct FileFsObjectIdInformation {
let objectId: uuid_t
let extendedInfo: (UInt64, UInt64, UInt64, UInt64, UInt64, UInt64)
}
struct FileFsSectorSizeInformation {
let logicalBytesPerSector: UInt32
let physicalBytesPerSectorForAtomicity: UInt32
let physicalBytesPerSectorForPerformance: UInt32
let effectivePhysicalBytesPerSectorForAtomicity: UInt32
let flags: Flags
let byteOffsetForSectorAlignment: UInt32
let byteOffsetForPartitionAlignment: UInt32
struct Flags: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
self.rawValue = rawValue
}
/// When set, this flag indicates that the first physical sector of the device is aligned with the first logical sector. When not set, the first physical sector of the device is misaligned with the first logical sector.
static let ALIGNED_DEVICE = Flags(rawValue: 0x00000001)
/// When set, this flag indicates that the partition is aligned to physical sector boundaries on the storage device.
static let PARTITION_ALIGNED_ON_DEVICE = Flags(rawValue: 0x00000002)
/// When set, the device reports that it does not incur a seek penalty (this typically indicates that the device does not have rotating media, such as flash-based disks).
static let NO_SEEK_PENALTY = Flags(rawValue: 0x00000008)
/// When set, the device supports TRIM operations, either T13 (ATA) TRIM or T10 (SCSI/SAS) UNMAP.
static let TRIM_ENABLED = Flags(rawValue: 0x00000010)
}
}
}
+80 -84
View File
@@ -11,66 +11,73 @@ import Foundation
extension SMB2 {
// MARK: SMB2 Negotiating
struct NegotiateRequest: SMBRequest {
let request: NegotiateRequest.Header
struct NegotiateRequest: SMBRequestBody {
let header: NegotiateRequest.Header
let dialects: [UInt16]
let contexts: [(type: NegotiateContextType, data: NSData)]
let contexts: [(type: NegotiateContextType, data: Data)]
init(request: NegotiateRequest.Header, dialects: [UInt16] = [0x0202], contexts: [(type: NegotiateContextType, data: NSData)] = []) {
self.request = request
init(header: NegotiateRequest.Header, dialects: [UInt16] = [0x0202], contexts: [(type: NegotiateContextType, data: Data)] = []) {
self.header = header
self.dialects = dialects
self.contexts = contexts
}
func data() -> NSData {
var request = self.request
request.dialectCount = UInt16(dialects.count)
let dialectData = NSMutableData()
init(dialects: [UInt16] = [0x0202], contexts: [(type: NegotiateContextType, data: Data)] = [],capabilities: GlobalCapabilities = [], clientStartTime: SMBTime? = nil, guid: uuid_t? = nil, signing: NegotiateSinging = [.ENABLED]) {
self.header = Header(capabilities: capabilities, clientStartTime: clientStartTime, guid: guid, signing: signing)
self.dialects = dialects
self.contexts = contexts
}
func data() -> Data {
var header = self.header
header.dialectCount = UInt16(dialects.count)
var dialectData = Data()
for dialect in dialects {
var dialect = dialect
dialectData.appendBytes(&dialect, length: 2)
dialectData.append(UnsafeBufferPointer(start: &dialect, count: 2))
}
let pad = ((1024 - dialectData.length) % 8)
dialectData.increaseLengthBy(pad)
request.contextOffset = UInt32(sizeof(request.dynamicType.self)) + UInt32(dialectData.length)
request.contextCount = UInt16(contexts.count)
let pad = ((1024 - dialectData.count) % 8)
dialectData.count += pad
header.contextOffset = UInt32(MemoryLayout<NegotiateRequest.Header>.size) + UInt32(dialectData.count)
header.contextCount = UInt16(contexts.count)
let contextData = NSMutableData()
var contextData = Data()
for context in contexts {
var contextType = context.type.rawValue
contextData.appendBytes(&contextType, length: 2)
var dataLen = UInt16(context.data.length)
contextData.increaseLengthBy(4)
contextData.appendBytes(&dataLen, length: 2)
contextData.append(Data(value: context.type.rawValue))
contextData.count += 4
contextData.append(Data(value: UInt16(context.data.count)))
}
let result = NSMutableData(data: encode(&request))
result.appendData(dialectData)
result.appendData(contextData)
var result = Data(value: header)
result.append(dialectData as Data)
result.append(contextData as Data)
return result
}
struct Header {
var size: UInt16
var dialectCount: UInt16
let singing: NegotiateSinging
private let reserved: UInt16
let signing: NegotiateSinging
fileprivate let reserved: UInt16
let capabilities: GlobalCapabilities
let guid: uuid_t
var contextOffset: UInt32
var contextCount: UInt16
private let reserved2: UInt16
fileprivate let reserved2: UInt16
var clientStartTime: SMBTime {
let time = UInt64(contextOffset) + (UInt64(contextCount) << 32) + (UInt64(contextCount) << 48)
let lo = Int64(contextOffset)
let hi1 = Int64(contextCount) << 32
let hi2 = Int64(contextCount) << 48
let time: Int64 = lo + hi1 + hi2
return SMBTime(time: time)
}
init(singing: NegotiateSinging = [.ENABLED], capabilities: GlobalCapabilities, guid: uuid_t? = nil, clientStartTime: SMBTime? = nil) {
init(capabilities: GlobalCapabilities, clientStartTime: SMBTime? = nil, guid: uuid_t? = nil, signing: NegotiateSinging = [.ENABLED]) {
self.size = 36
self.dialectCount = 0
self.singing = singing
self.signing = signing
self.reserved = 0
self.capabilities = capabilities
self.guid = guid ?? (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
self.guid = guid ?? (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)
if let clientStartTime = clientStartTime {
let time = clientStartTime.time
self.contextOffset = UInt32(time & 0xffffffff)
@@ -85,28 +92,28 @@ extension SMB2 {
}
}
struct NegotiateResponse: SMBResponse {
struct NegotiateResponse: SMBResponseBody {
let header: NegotiateResponse.Header
let buffer: NSData?
let contexts: [(type: NegotiateContextType, data: NSData)]
let buffer: Data?
let contexts: [(type: NegotiateContextType, data: Data)]
init? (data: NSData) {
if data.length < 64 {
init? (data: Data) {
guard data.count >= 64 else {
return nil
}
self.header = decode(data)
self.header = data.scanValue()!
if Int(header.size) != 65 {
return nil
}
let bufOffset = Int(self.header.bufferOffset) - sizeof(SMB2.Header.self)
let bufOffset = Int(self.header.bufferOffset) - MemoryLayout<SMB2.Header>.size
let bufLen = Int(self.header.bufferLength)
if bufOffset > 0 && bufLen > 0 && data.length >= bufOffset + bufLen {
self.buffer = data.subdataWithRange(NSRange(location: bufOffset, length: bufLen))
if bufOffset > 0 && bufLen > 0 && data.count >= bufOffset + bufLen {
self.buffer = data.subdata(in: bufOffset..<(bufOffset + bufLen))
} else {
self.buffer = nil
}
let contextCount = Int(self.header.contextCount)
let contextOffset = Int(self.header.contextOffset) - sizeof(SMB2.Header.self)
let contextOffset = Int(self.header.contextOffset) - MemoryLayout<SMB2.Header>.size
if contextCount > 0 && contextOffset > 0 {
// TODO: NegotiateResponse context support for SMB3
self.contexts = []
@@ -133,7 +140,7 @@ extension SMB2 {
}
}
struct NegotiateSinging: OptionSetType {
struct NegotiateSinging: OptionSet {
let rawValue: UInt16
init(rawValue: UInt16) {
@@ -143,7 +150,7 @@ extension SMB2 {
static let REQUIRED = NegotiateSinging(rawValue: 0x0002)
}
struct NegotiateContextType: OptionSetType {
struct NegotiateContextType: OptionSet {
let rawValue: UInt16
init(rawValue: UInt16) {
@@ -153,7 +160,7 @@ extension SMB2 {
static let ENCRYPTION_CAPABILITIES = NegotiateContextType(rawValue: 0x0002)
}
struct GlobalCapabilities: OptionSetType {
struct GlobalCapabilities: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
@@ -170,22 +177,27 @@ extension SMB2 {
// MARK: SMB2 Session Setup
struct SessionSetupRequest: SMBRequest {
struct SessionSetupRequest: SMBRequestBody {
let header: SessionSetupRequest.Header
let buffer: NSData?
let buffer: Data?
init(header: SessionSetupRequest.Header, buffer: NSData) {
init(header: SessionSetupRequest.Header, buffer: Data) {
self.header = header
self.buffer = buffer
}
func data() -> NSData {
init(sessionId: UInt64 = 0, flags: SessionSetupRequest.Flags = [], singing: SessionSetupSinging = [.ENABLED], capabilities: GlobalCapabilities = [], securityData: Data? = nil) {
self.header = Header(sessionId: sessionId, flags: flags, singing: singing, capabilities: capabilities)
self.buffer = securityData
}
func data() -> Data {
var header = self.header
header.bufferOffset = UInt16(sizeof(SMB2.Header.self) + sizeof(SessionSetupRequest.Header.self))
header.bufferLength = UInt16(buffer?.length ?? 0)
let result = NSMutableData(data: encode(&header))
header.bufferOffset = UInt16(MemoryLayout<SMB2.Header>.size + MemoryLayout<SessionSetupRequest.Header>.size)
header.bufferLength = UInt16(buffer?.count ?? 0)
var result = Data(value: header)
if let buffer = self.buffer {
result.appendData(buffer)
result.append(buffer)
}
return result
}
@@ -195,7 +207,7 @@ extension SMB2 {
let flags: SessionSetupRequest.Flags
let signing: SessionSetupSinging
let capabilities: GlobalCapabilities
private let channel: UInt32
fileprivate let channel: UInt32
var bufferOffset: UInt16
var bufferLength: UInt16
let sessionId: UInt64
@@ -213,7 +225,7 @@ extension SMB2 {
}
/// Works the client implements the SMB 3.x dialect family
struct Flags: OptionSetType {
struct Flags: OptionSet {
let rawValue: UInt8
init(rawValue: UInt8) {
@@ -224,22 +236,22 @@ extension SMB2 {
}
}
struct SessionSetupResponse: SMBResponse {
struct SessionSetupResponse: SMBResponseBody {
let header: SessionSetupResponse.Header
let buffer: NSData?
let buffer: Data?
init? (data: NSData) {
if data.length < 64 {
init? (data: Data) {
guard data.count >= 64 else {
return nil
}
self.header = decode(data)
self.header = data.scanValue()!
if Int(header.size) != 9 {
return nil
}
let bufOffset = Int(self.header.bufferOffset) - sizeof(SMB2.Header.self)
let bufOffset = Int(self.header.bufferOffset) - MemoryLayout<SMB2.Header>.size
let bufLen = Int(self.header.bufferLength)
if bufOffset > 0 && bufLen > 0 && data.length >= bufOffset + bufLen {
self.buffer = data.subdataWithRange(NSRange(location: bufOffset, length: bufLen))
if bufOffset > 0 && bufLen > 0 && data.count >= bufOffset + bufLen {
self.buffer = data.subdata(in: bufOffset..<(bufOffset + bufLen))
} else {
self.buffer = nil
}
@@ -252,7 +264,7 @@ extension SMB2 {
let bufferLength: UInt16
}
struct Flags: OptionSetType {
struct Flags: OptionSet {
let rawValue: UInt16
init(rawValue: UInt16) {
@@ -265,20 +277,20 @@ extension SMB2 {
}
}
struct SessionSetupSinging: OptionSetType {
struct SessionSetupSinging: OptionSet {
let rawValue: UInt8
init(rawValue: UInt8) {
self.rawValue = rawValue
}
static let ENABLED = NegotiateSinging(rawValue: 0x01)
static let REQUIRED = NegotiateSinging(rawValue: 0x02)
static let ENABLED = SessionSetupSinging(rawValue: 0x01)
static let REQUIRED = SessionSetupSinging(rawValue: 0x02)
}
// MARK: SMB2 Log off
struct LogOff: SMBRequest, SMBResponse {
struct LogOff: SMBRequestBody, SMBResponseBody {
let size: UInt16
let reserved: UInt16
@@ -286,19 +298,11 @@ extension SMB2 {
self.size = 4
self.reserved = 0
}
init? (data: NSData) {
self = decode(data)
}
func data() -> NSData {
return encode(self)
}
}
// MARK: SMB2 Echo
struct Echo: SMBRequest, SMBResponse {
struct Echo: SMBRequestBody, SMBResponseBody {
let size: UInt16
let reserved: UInt16
@@ -306,13 +310,5 @@ extension SMB2 {
self.size = 4
self.reserved = 0
}
init? (data: NSData) {
self = decode(data)
}
func data() -> NSData {
return encode(self)
}
}
}
}
+29 -1
View File
@@ -10,5 +10,33 @@ import Foundation
extension SMB2 {
// MARK: SMB2 Set Info
struct SetInfoRequest: SMBRequestBody {
let header: Header
let buffer: Data?
func data() -> Data {
var result = Data(value: header)
result.append(buffer ?? Data())
return result
}
struct Header {
let size: UInt16 = 33
let infoType: UInt8
fileprivate let infoClass: UInt8
let bufferLength: UInt32
let bufferOffset: UInt16
fileprivate let reserved: UInt16
let securityInfo: FileSecurityInfo
let fileId: FileId
}
}
}
struct SetInfoResponse: SMBResponseBody {
let size: UInt16
init() {
self.size = 2
}
}
}
+18 -33
View File
@@ -11,9 +11,9 @@ import Foundation
extension SMB2 {
// MARK: SMB2 Tree Connect
struct TreeConnectRequest: SMBRequest {
struct TreeConnectRequest: SMBRequestBody {
let header: TreeConnectRequest.Header
let buffer: NSData?
let buffer: Data?
var path: String {
return ""
}
@@ -22,21 +22,21 @@ extension SMB2 {
}
init? (header: TreeConnectRequest.Header, host: String, share: String) {
guard !host.containsString("/") && !host.containsString("/") && !share.containsString("/") && !share.containsString("/") else {
guard !host.contains("/") && !share.contains("/") else {
return nil
}
self.header = header
let path = "\\\\\(host)\\\(share)"
self.buffer = path.dataUsingEncoding(NSUTF8StringEncoding)
let path = "\\\\" + host + "\\" + share
self.buffer = path.data(using: .utf16)
}
func data() -> NSData {
func data() -> Data {
var header = self.header
header.pathOffset = UInt16(sizeof(SMB2.Header.self) + sizeof(TreeConnectRequest.Header.self))
header.pathLength = UInt16(buffer?.length ?? 0)
let result = NSMutableData(data: encode(&header))
header.pathOffset = UInt16(MemoryLayout<SMB2.Header>.size + MemoryLayout<TreeConnectRequest.Header>.size)
header.pathLength = UInt16(buffer?.count ?? 0)
var result = Data(value: header)
if let buffer = self.buffer {
result.appendData(buffer)
result.append(buffer)
}
return result
}
@@ -55,7 +55,7 @@ extension SMB2 {
}
}
struct Flags: OptionSetType {
struct Flags: OptionSet {
let rawValue: UInt16
init(rawValue: UInt16) {
@@ -66,24 +66,17 @@ extension SMB2 {
}
}
struct TreeConnectResponse: SMBResponse {
struct TreeConnectResponse: SMBResponseBody {
let size: UInt16 // = 16
private let _type: UInt8
fileprivate let _type: UInt8
var type: ShareType {
return ShareType(rawValue: _type) ?? .UNKNOWN
}
private let reserved: UInt8
fileprivate let reserved: UInt8
let flags: TreeConnectResponse.ShareFlags
let capabilities: TreeConnectResponse.Capabilities
let maximalAccess: FileAccessMask
init? (data: NSData) {
if data.length != 16 {
return nil
}
self = decode(data)
}
enum ShareType: UInt8 {
case UNKNOWN = 0x00
case DISK = 0x01
@@ -91,7 +84,7 @@ extension SMB2 {
case PRINT = 0x03
}
struct ShareFlags: OptionSetType {
struct ShareFlags: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
@@ -114,7 +107,7 @@ extension SMB2 {
static let ENCRYPT_DATA = ShareFlags(rawValue: 0x00008000)
}
struct Capabilities: OptionSetType {
struct Capabilities: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) {
@@ -131,7 +124,7 @@ extension SMB2 {
// MARK: SMB2 Tree Disconnect
struct TreeDisconnect: SMBRequest, SMBResponse {
struct TreeDisconnect: SMBRequestBody, SMBResponseBody {
let size: UInt16
let reserved: UInt16
@@ -139,13 +132,5 @@ extension SMB2 {
self.size = 4
self.reserved = 0
}
init? (data: NSData) {
self = decode(data)
}
func data() -> NSData {
return encode(self)
}
}
}
}
+15 -7
View File
@@ -8,6 +8,11 @@
import Foundation
protocol FileProviderSMBHeader {
var protocolID: UInt32 { get }
static var protocolConst: UInt32 { get }
}
// SMB2 Types
struct SMB2 {
struct Header: FileProviderSMBHeader { // 64 bytes
@@ -19,13 +24,13 @@ struct SMB2 {
// error messages from the server to the client
let status: UInt32
enum StatusSeverity: UInt8 {
case Success = 0, Information, Warning, Error
case success = 0, information, warning, error
}
var statusDetails: (severity: StatusSeverity, customer: Bool, facility: UInt16, code: UInt16) {
let severity = StatusSeverity(rawValue: UInt8(status >> 30))!
return (severity, status & 0x20000000 != 0, UInt16((status & 0x0FFF0000) >> 16), UInt16(status & 0x0000FFFF))
}
private let _command: UInt16
fileprivate let _command: UInt16
var command: Command {
get {
return Command(rawValue: _command) ?? .INVALID
@@ -35,7 +40,7 @@ struct SMB2 {
let flags: Flags
var nextCommand: UInt32
let messageId: UInt64
private let reserved: UInt32
fileprivate let reserved: UInt32
let treeId: UInt32
var asyncId: UInt64 {
get {
@@ -45,8 +50,9 @@ struct SMB2 {
let sessionId: UInt64
let signature: (UInt64, UInt64)
// codebeat:disable[ARITY]
init(command: Command, status: NTStatus = .SUCCESS, creditCharge: UInt16 = 0, creditRequestResponse: UInt16, flags: Flags = [], nextCommand: UInt32 = 0, messageId: UInt64, treeId: UInt32, sessionId: UInt64, signature: (UInt64, UInt64) = (0, 0)) {
self.protocolID = self.dynamicType.protocolConst
self.protocolID = type(of: self).protocolConst
self.size = 64
self.status = status.rawValue
self._command = command.rawValue
@@ -62,7 +68,7 @@ struct SMB2 {
}
init(asyncCommand: Command, status: NTStatus = .SUCCESS, creditCharge: UInt16 = 0, creditRequestResponse: UInt16, flags: Flags = [.ASYNC_COMMAND], nextCommand: UInt32 = 0, messageId: UInt64, asyncId: UInt64, sessionId: UInt64, signature: (UInt64, UInt64) = (0, 0)) {
self.protocolID = self.dynamicType.protocolConst
self.protocolID = type(of: self).protocolConst
self.size = 64
self.status = status.rawValue
self._command = asyncCommand.rawValue
@@ -76,9 +82,10 @@ struct SMB2 {
self.sessionId = sessionId
self.signature = signature
}
// codebeat:enable[ARITY]
}
struct Flags: OptionSetType {
struct Flags: OptionSet {
var rawValue: UInt32
init(rawValue: UInt32) {
@@ -98,7 +105,7 @@ struct SMB2 {
static let ASYNC_COMMAND = Flags(rawValue: 0x00000002)
static let RELATED_OPERATIONS = Flags(rawValue: 0x00000004)
static let SIGNED = Flags(rawValue: 0x00000008)
private static let PRIORITY_MASK = Flags(rawValue: 0x00000070)
fileprivate static let PRIORITY_MASK = Flags(rawValue: 0x00000070)
static let DFS_OPERATIONS = Flags(rawValue: 0x10000000)
static let REPLAY_OPERATION = Flags(rawValue: 0x20000000)
}
@@ -128,4 +135,5 @@ struct SMB2 {
// MARK: SMB2 Oplock Break
}
+36 -36
View File
@@ -10,7 +10,7 @@ import Foundation
/// Error Types and Description
public enum NTStatus: UInt32, ErrorType, CustomStringConvertible {
enum NTStatus: UInt32, Error, CustomStringConvertible {
case SUCCESS = 0x00000000
case NOT_IMPLEMENTED = 0xC0000002
case INVALID_DEVICE_REQUEST = 0xC0000010
@@ -133,76 +133,76 @@ public enum NTStatus: UInt32, ErrorType, CustomStringConvertible {
public var description: String {
switch self {
case NOT_IMPLEMENTED, INVALID_DEVICE_REQUEST, ILLEGAL_FUNCTION:
case .NOT_IMPLEMENTED, .INVALID_DEVICE_REQUEST, .ILLEGAL_FUNCTION:
return "Invalid Function."
case NO_SUCH_FILE, NO_SUCH_DEVICE, OBJECT_NAME_NOT_FOUND:
case .NO_SUCH_FILE, .NO_SUCH_DEVICE, .OBJECT_NAME_NOT_FOUND:
return "File not found."
case OBJECT_PATH_INVALID, OBJECT_PATH_NOT_FOUND, OBJECT_PATH_SYNTAX_BAD, DFS_EXIT_PATH_FOUND, REDIRECTOR_NOT_STARTED:
case .OBJECT_PATH_INVALID, .OBJECT_PATH_NOT_FOUND, .OBJECT_PATH_SYNTAX_BAD, .DFS_EXIT_PATH_FOUND, .REDIRECTOR_NOT_STARTED:
return "A component in the path prefix is not a directory."
case TOO_MANY_OPENED_FILES:
case .TOO_MANY_OPENED_FILES:
return "Too many open files. No FIDs are available."
case ACCESS_DENIED, INVALID_LOCK_SEQUENCE, INVALID_VIEW_SIZE, ALREADY_COMMITTED, PORT_CONNECTION_REFUSED, THREAD_IS_TERMINATING, DELETE_PENDING, PRIVILEGE_NOT_HELD, LOGON_FAILURE, FILE_IS_A_DIRECTORY, FILE_RENAMED, PROCESS_IS_TERMINATING, CANNOT_DELETE, FILE_DELETED:
case .ACCESS_DENIED, .INVALID_LOCK_SEQUENCE, .INVALID_VIEW_SIZE, .ALREADY_COMMITTED, .PORT_CONNECTION_REFUSED, .THREAD_IS_TERMINATING, .DELETE_PENDING, .PRIVILEGE_NOT_HELD, .LOGON_FAILURE, .FILE_IS_A_DIRECTORY, .FILE_RENAMED, .PROCESS_IS_TERMINATING, .CANNOT_DELETE, .FILE_DELETED:
return "Access denied."
case SMB_BAD_FID, INVALID_HANDLE, OBJECT_TYPE_MISMATCH, PORT_DISCONNECTED, INVALID_PORT_HANDLE, FILE_CLOSED, HANDLE_NOT_CLOSABLE:
case .SMB_BAD_FID, .INVALID_HANDLE, .OBJECT_TYPE_MISMATCH, .PORT_DISCONNECTED, .INVALID_PORT_HANDLE, .FILE_CLOSED, .HANDLE_NOT_CLOSABLE:
return "Invalid FID."
case SECTION_TOO_BIG, TOO_MANY_PAGING_FILES, INSUFF_SERVER_RESOURCES:
case .SECTION_TOO_BIG, .TOO_MANY_PAGING_FILES, .INSUFF_SERVER_RESOURCES:
return "Insufficient server memory to perform the requested operation."
case OS2_INVALID_ACCESS:
case .OS2_INVALID_ACCESS:
return "Invalid open mode."
case DATA_ERROR:
case .DATA_ERROR:
return "Bad data. (May be generated by IOCTL calls on the server.)"
case DIRECTORY_NOT_EMPTY:
case .DIRECTORY_NOT_EMPTY:
return "Remove of directory failed because it was not empty."
case NOT_SAME_DEVICE:
case .NOT_SAME_DEVICE:
return "A file system operation (such as a rename) across two devices was attempted."
case NO_MORE_FILES:
case .NO_MORE_FILES:
return "No (more) files found following a file search command."
case UNSUCCESSFUL:
case .UNSUCCESSFUL:
return "General error."
case SHARING_VIOLATION:
case .SHARING_VIOLATION:
return "Sharing violation. A requested open mode conflicts with the sharing mode of an existing file handle."
case FILE_LOCK_CONFLICT, LOCK_NOT_GRANTED:
case .FILE_LOCK_CONFLICT, .LOCK_NOT_GRANTED:
return "A lock request specified an invalid locking mode, or conflicted with an existing file lock."
case END_OF_FILE:
case .END_OF_FILE:
return "Attempted to read beyond the end of the file."
case NOT_SUPPORTED:
case .NOT_SUPPORTED:
return "This command is not supported by the server."
case OBJECT_NAME_COLLISION:
case .OBJECT_NAME_COLLISION:
return "An attempt to create a file or directory failed because an object with the same pathname already exists."
case INVALID_PARAMETER:
case .INVALID_PARAMETER:
return "A parameter supplied with the message is invalid."
case OS2_INVALID_LEVEL:
case .OS2_INVALID_LEVEL:
return "Invalid information level."
case OS2_NEGATIVE_SEEK:
case .OS2_NEGATIVE_SEEK:
return "An attempt was made to seek to a negative absolute offset within a file."
case RANGE_NOT_LOCKED:
case .RANGE_NOT_LOCKED:
return "The byte range specified in an unlock request was not locked."
case OS2_NO_MORE_SIDS:
case .OS2_NO_MORE_SIDS:
return "Maximum number of searches has been exhausted."
case OS2_CANCEL_VIOLATION:
case .OS2_CANCEL_VIOLATION:
return "No lock request was outstanding for the supplied cancel region."
case OS2_ATOMIC_LOCKS_NOT_SUPPORTED:
case .OS2_ATOMIC_LOCKS_NOT_SUPPORTED:
return "The file system does not support atomic changes to the lock type."
case INVALID_INFO_CLASS, INVALID_PIPE_STATE, INVALID_READ_MODE:
case .INVALID_INFO_CLASS, .INVALID_PIPE_STATE, .INVALID_READ_MODE:
return "Invalid named pipe."
case OS2_CANNOT_COPY:
case .OS2_CANNOT_COPY:
return "The copy functions cannot be used."
case INSTANCE_NOT_AVAILABLE, PIPE_NOT_AVAILABLE, PIPE_BUSY:
case .INSTANCE_NOT_AVAILABLE, .PIPE_NOT_AVAILABLE, .PIPE_BUSY:
return "All instances of the designated named pipe are busy."
case PIPE_CLOSING, PIPE_EMPTY:
case .PIPE_CLOSING, .PIPE_EMPTY:
return "The designated named pipe is in the process of being closed."
case PIPE_DISCONNECTED:
case .PIPE_DISCONNECTED:
return "The designated named pipe exists, but there is no server process listening on the server side."
case BUFFER_OVERFLOW, MORE_PROCESSING_REQUIRED:
case .BUFFER_OVERFLOW, .MORE_PROCESSING_REQUIRED:
return "There is more data available to read on the designated named pipe."
case EA_TOO_LARGE, OS2_EAS_DIDNT_FIT:
case .EA_TOO_LARGE, .OS2_EAS_DIDNT_FIT:
return "Either there are no extended attributes, or the available extended attributes did not fit into the response."
case EAS_NOT_SUPPORTED:
case .EAS_NOT_SUPPORTED:
return "The server file system does not support Extended Attributes."
case OS2_EA_ACCESS_DENIED:
case .OS2_EA_ACCESS_DENIED:
return "Access to the extended attribute was denied."
default:
return ""
}
}
}
}
-249
View File
@@ -1,249 +0,0 @@
//
// SocketTransmitter.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2016 Mousavian. Distributed under MIT license.
//
import Foundation
public class TCPSocketClient: NSObject, NSStreamDelegate {
public static let ports = ["http": 80,
"https": 443,
"smb": 445,
"ftp": 21,
"sftp": 22,
"sftp": 2121,
"telnet": 23,
"pop": 110,
"smtp": 25,
"imap": 143]
public static let securePorts = ["https": 443,
"smb": 445,
"sftp": 22,
"sftp": 2121,
"telnet": 992,
"pop": 995,
"smtp": 465,
"imap": 993]
private var inputStream: NSInputStream?
private var outputStream: NSOutputStream?
private var dataToBeSent: NSMutableData = NSMutableData()
/// holds data received from server
public let dataReceived: NSMutableData = NSMutableData()
/// a url with valid scheme, dns or ip host and ports path and query sections will be neglected
public let baseURL: NSURL
/// a url with valid scheme, dns or ip host and ports path and query sections will be neglected
public let secureConnection: Bool
/// server's ports which is value between 1 to 65535
private let port: UInt32
private var open = false
/**
* - parameter baseURL: a url with valid scheme, dns or ip host and ports
* path and query sections will be neglected
*
* **Note** Call `connect()` to establish connection
* - parameter secure: establishing connection using an SSL/TLS connection
*/
public init?(baseURL: NSURL, secure: Bool = false) {
self.baseURL = baseURL
self.secureConnection = secure
let scheme = baseURL.uw_scheme.lowercaseString
let defaultPort = secure ? UInt32(TCPSocketClient.securePorts[scheme] ?? 0) : UInt32(TCPSocketClient.ports[scheme] ?? 0)
self.port = baseURL.port?.unsignedIntValue ?? defaultPort
if self.port == 0 {
return nil
}
}
deinit {
disconnect()
}
/**
* Establshes a connection to desired server
* - returns: A bool value which indicated there where no system error during
* creating connection
*/
public func connect() -> Bool {
guard let hostStr = baseURL.host else {
return false
}
var readStream : Unmanaged<CFReadStream>?
var writeStream : Unmanaged<CFWriteStream>?
let host : CFString = NSString(string: hostStr)
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, host, self.port, &readStream, &writeStream)
inputStream = readStream?.takeRetainedValue()
outputStream = writeStream?.takeRetainedValue()
guard let inputStream = inputStream, outputStream = outputStream else {
return false
}
inputStream.delegate = self
outputStream.delegate = self
inputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
outputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
if secureConnection {
inputStream.setProperty(NSStreamSocketSecurityLevelNegotiatedSSL, forKey: NSStreamSocketSecurityLevelKey)
outputStream.setProperty(NSStreamSocketSecurityLevelNegotiatedSSL, forKey: NSStreamSocketSecurityLevelKey)
}
inputStream.open()
outputStream.open()
return true
}
/**
* Terminates connection to the server
*/
public func disconnect() {
inputStream?.setValue(kCFBooleanTrue, forKey: kCFStreamPropertyShouldCloseNativeSocket as String)
outputStream?.setValue(kCFBooleanTrue, forKey: kCFStreamPropertyShouldCloseNativeSocket as String)
self.inputStream?.close()
self.outputStream?.close()
self.inputStream?.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
self.outputStream?.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
self.inputStream?.delegate = nil
self.outputStream?.delegate = nil
self.inputStream = nil
self.outputStream = nil
}
public func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent) {
switch (eventCode) {
case NSStreamEvent.ErrorOccurred:
open = false
case NSStreamEvent.EndEncountered:
break
case NSStreamEvent.None:
break
case NSStreamEvent.OpenCompleted:
let activeStatus: [NSStreamStatus] = [.Open, .Reading, .Writing, .AtEnd]
open = activeStatus.contains(inputStream?.streamStatus ?? .NotOpen) && activeStatus.contains(outputStream?.streamStatus ?? .NotOpen)
case NSStreamEvent.HasBytesAvailable:
var buffer = [UInt8](count: 2048, repeatedValue: 0)
if ( aStream == inputStream) {
while (inputStream!.hasBytesAvailable ?? false) {
let len = inputStream!.read(&buffer, maxLength: buffer.count)
if len > 0 {
dataReceived.appendBytes(&buffer, length: len)
}
}
}
case NSStreamEvent.HasSpaceAvailable:
if aStream == outputStream {
do {
try send(data: nil)
} catch _ {
NSLog("Sending error")
}
}
default:
break
}
}
/**
* Sends data to server
* - parameter data: data which is intended to be sent to server
* - throws: NSURLError.NetworkConnectionLost in case of server disconnects disgracefully
*/
public func send(data data: NSData?) throws {
guard let outputStream = outputStream else {
return
}
if outputStream.hasSpaceAvailable ?? false {
if let data = data {
dataToBeSent.appendData(data)
}
if dataToBeSent.length > 0 {
let bytesWritten = outputStream.write(UnsafePointer(dataToBeSent.bytes), maxLength: dataToBeSent.length) ?? -1
if bytesWritten > 0 {
let range = NSRange(location: 0, length: bytesWritten)
dataToBeSent.replaceBytesInRange(range, withBytes: nil, length: 0)
} else {
throw NSError(domain: NSURLErrorDomain, code: NSURLError.NetworkConnectionLost.rawValue, userInfo: nil)
}
}
//println("Sent the following")
} else { //steam busy
if let data = data {
dataToBeSent.appendData(data)
}
}
}
/**
* Clears entire send and receive buffer
*/
public func flush() {
dataToBeSent.length = 0
dataReceived.length = 0
}
/**
* Put's thread in sleep until all data is sent
* **Note:** Don't call this method from main thread
*/
internal func waitUntillDataSent() {
if NSThread.isMainThread() {
assert(false, "waitUntillDataSent() method can't be called from main thread")
}
while true {
if dataToBeSent.length == 0 {
break
}
NSRunLoop.currentRunLoop().runUntilDate(NSDate(timeIntervalSinceNow: 0.1));
NSThread.sleepForTimeInterval(0.1)
}
}
/**
* Put's thread in sleep until all response from server is loaded into tcp stack
* server response can be retrieved by `dataReceived` property
* **Note:** Don't call this method from main thread
* - returns: A Bool value indicates all response loaded from server successfullt
*/
internal func waitUntilResponse() -> Bool {
if NSThread.isMainThread() {
assert(false, "waitUntilResponse() method can't be called from main thread")
}
var finished = false
while !finished {
switch inputStream?.streamStatus ?? .Error {
case .AtEnd:
finished = true
return true
case .Closed, .Error:
return false
default:
finished = false
}
NSRunLoop.currentRunLoop().runUntilDate(NSDate(timeIntervalSinceNow: 0.1));
NSThread.sleepForTimeInterval(0.1)
}
return false
}
}
File diff suppressed because it is too large Load Diff
+302
View File
@@ -0,0 +1,302 @@
//
// FilesProviderTests.swift
// FilesProviderTests
//
// Created by Amir Abbas on 8/11/1396 AP.
//
import XCTest
import FilesProvider
class FilesProviderTests: XCTestCase {
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func testLocal() {
let provider = LocalFileProvider()
addTeardownBlock {
self.testRemoveFile(provider, filePath: self.testFolderName)
}
testBasic(provider)
testOperations(provider)
}
func testWebDav() {
guard let urlStr = ProcessInfo.processInfo.environment["webdav_url"] else { return }
let url = URL(string: urlStr)!
let cred: URLCredential?
if let user = ProcessInfo.processInfo.environment["webdav_user"], let pass = ProcessInfo.processInfo.environment["webdav_password"] {
cred = URLCredential(user: user, password: pass, persistence: .forSession)
} else {
cred = nil
}
let provider = WebDAVFileProvider(baseURL: url, credential: cred)!
addTeardownBlock {
self.testRemoveFile(provider, filePath: self.testFolderName)
}
testOperations(provider)
}
func testDropbox() {
guard let pass = ProcessInfo.processInfo.environment["dropbox_token"] else {
return
}
let cred = URLCredential(user: "testuser", password: pass, persistence: .forSession)
let provider = DropboxFileProvider(credential: cred)
addTeardownBlock {
self.testRemoveFile(provider, filePath: self.testFolderName)
}
testOperations(provider)
}
func testOneDrive() {
guard let pass = ProcessInfo.processInfo.environment["onedrive_token"] else {
return
}
let cred = URLCredential(user: "testuser", password: pass, persistence: .forSession)
let provider = OneDriveFileProvider(credential: cred)
addTeardownBlock {
self.testRemoveFile(provider, filePath: self.testFolderName)
}
testOperations(provider)
}
/*
func testPerformanceExample() {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}
*/
let timeout: Double = 20.0
let testFolderName = "Test"
let textFilePath = "/Test/file.txt"
let renamedFilePath = "/Test/renamed.txt"
let uploadFilePath = "/Test/uploaded.dat"
let sampleText = "Hello world!"
fileprivate func testCreateFolder(_ provider: FileProvider, folderName: String) {
let desc = "Creating folder at root in \(provider.type)"
let expectation = XCTestExpectation(description: desc)
provider.create(folder: folderName, at: "/") { (error) in
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout)
}
fileprivate func testContentsOfDirectory(_ provider: FileProvider) {
let desc = "Enumerating files list in \(provider.type)"
let expectation = XCTestExpectation(description: desc)
provider.contentsOfDirectory(path: "/") { (files, error) in
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
XCTAssertGreaterThan(files.count, 0, "list is empty")
let testFolder = files.filter({ $0.name == self.testFolderName }).first
XCTAssertNotNil(testFolder, "Test folder didn't listed")
guard testFolder != nil else { return }
XCTAssertTrue(testFolder!.isDirectory, "Test entry is not a folder")
XCTAssertLessThanOrEqual(testFolder!.size, 0, "folder size is not -1")
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout)
}
fileprivate func testAttributesOfFile(_ provider: FileProvider, filePath: String) {
let desc = "Attrubutes of file in \(provider.type)"
let expectation = XCTestExpectation(description: desc)
provider.attributesOfItem(path: filePath) { (fileObject, error) in
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
XCTAssertNotNil(fileObject, "file '\(filePath)' didn't exist")
guard fileObject != nil else { return }
XCTAssertEqual(fileObject!.path, filePath, "file path is different from '\(filePath)'")
XCTAssertEqual(fileObject!.type, URLFileResourceType.regular, "file '\(filePath)' is not a regular file")
XCTAssertGreaterThan(fileObject!.size, 0, "file '\(filePath)' is empty")
XCTAssertNotNil(fileObject!.modifiedDate, "file '\(filePath)' has no modification date")
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout)
}
fileprivate func testCreateFile(_ provider: FileProvider, filePath: String) {
let desc = "Creating file in \(provider.type)"
let expectation = XCTestExpectation(description: desc)
let data = sampleText.data(using: .ascii)
provider.writeContents(path: filePath, contents: data, overwrite: true) { (error) in
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout)
}
fileprivate func testContentsFile(_ provider: FileProvider, filePath: String, hasSampleText: Bool = true) {
let desc = "Reading file in \(provider.type)"
let expectation = XCTestExpectation(description: desc)
provider.contents(path: filePath) { (data, error) in
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
XCTAssertNotNil(data, "no data for test file")
if data != nil && hasSampleText {
let str = String(data: data!, encoding: .ascii)
XCTAssertNotNil(str, "test file data not readable")
XCTAssertEqual(str, self.sampleText, "test file data didn't matched")
}
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout)
}
fileprivate func testRenameFile(_ provider: FileProvider, filePath: String, to toPath: String) {
let desc = "Renaming file in \(provider.type)"
let expectation = XCTestExpectation(description: desc)
provider.moveItem(path: filePath, to: toPath, overwrite: true) { (error) in
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout)
}
fileprivate func testCopyFile(_ provider: FileProvider, filePath: String, to toPath: String) {
let desc = "Copying file in \(provider.type)"
let expectation = XCTestExpectation(description: desc)
provider.copyItem(path: filePath, to: toPath, overwrite: true) { (error) in
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout)
}
fileprivate func testRemoveFile(_ provider: FileProvider, filePath: String) {
let desc = "Deleting file in \(provider.type)"
let expectation = XCTestExpectation(description: desc)
provider.removeItem(path: filePath) { (error) in
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout)
}
private func randomData(size: Int = 262144) -> Data {
var keyData = Data(count: size)
let result = keyData.withUnsafeMutableBytes {
SecRandomCopyBytes(kSecRandomDefault, keyData.count, $0)
}
if result == errSecSuccess {
return keyData
} else {
fatalError("Problem generating random bytes")
}
}
fileprivate func dummyFile() -> URL {
let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("dummyfile.dat")
if !FileManager.default.fileExists(atPath: url.path) {
let data = randomData()
try! data.write(to: url)
}
return url
}
fileprivate func testUploadFile(_ provider: FileProvider, filePath: String) {
// test Upload/Download
let url = dummyFile()
let desc = "Uploading file in \(provider.type)"
let expectation = XCTestExpectation(description: desc)
let dummy = dummyFile()
provider.copyItem(localFile: dummy, to: filePath) { (error) in
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
// TODO: check file existance of server
try? FileManager.default.removeItem(at: url)
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout * 3)
}
fileprivate func testDownloadFile(_ provider: FileProvider, filePath: String) {
let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("downloadedfile.dat")
let desc = "Downloading file in \(provider.type)"
let expectation = XCTestExpectation(description: desc)
provider.copyItem(path: filePath, toLocalURL: url) { (error) in
XCTAssertNil(error, "\(desc) failed: \(error?.localizedDescription ?? "no error desc")")
XCTAssertTrue(FileManager.default.fileExists(atPath: url.path), "downloaded file doesn't exist")
let size = (try? FileManager.default.attributesOfItem(atPath: url.path))?[FileAttributeKey.size] as? Int64
XCTAssertEqual(size, 262144, "downloaded file size is unexpected")
try? FileManager.default.removeItem(at: url)
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout * 3)
}
fileprivate func testStorageProperties(_ provider: FileProvider, isExpected: Bool) {
let desc = "Querying volume in \(provider.type)"
let expectation = XCTestExpectation(description: desc)
provider.storageProperties { (volume) in
if !isExpected {
XCTAssertNotNil(volume, "volume information is nil")
guard volume != nil else { return }
XCTAssertGreaterThan(volume!.totalCapacity, 0, "capacity must be greater than 0")
XCTAssertGreaterThan(volume!.availableCapacity, 0, "available capacity must be greater than 0")
XCTAssertEqual(volume!.totalCapacity, volume!.availableCapacity + volume!.usage, "total capacity is not equal to usage + available")
} else {
XCTAssertNil(volume, "volume information must be nil")
}
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout)
}
fileprivate func testReachability(_ provider: FileProvider) {
// Test file operations
let desc = "Reachability of \(provider.type)"
let expectation = XCTestExpectation(description: desc)
provider.isReachable { (status) in
XCTAssertTrue(status, "\(provider.type) not reachable")
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout)
}
fileprivate func testBasic(_ provider: FileProvider) {
let filepath = "/test/file.txt"
let fileurl = provider.url(of: filepath)
let composedfilepath = provider.relativePathOf(url: fileurl)
XCTAssertEqual(composedfilepath, "test/file.txt", "file url synthesis error")
let dirpath = "/test/"
let dirurl = provider.url(of: dirpath)
let composeddirpath = provider.relativePathOf(url: dirurl)
XCTAssertEqual(composeddirpath, "test", "directory url synthesis error")
let rooturl1 = provider.url(of: "")
let rooturl2 = provider.url(of: "/")
XCTAssertEqual(rooturl1, rooturl2, "root url synthesis error")
}
fileprivate func testOperations(_ provider: FileProvider) {
testReachability(provider)
testCreateFolder(provider, folderName: testFolderName)
testContentsOfDirectory(provider)
testCreateFile(provider, filePath: textFilePath)
testAttributesOfFile(provider, filePath: textFilePath)
testContentsFile(provider, filePath: textFilePath)
testRenameFile(provider, filePath: textFilePath, to: renamedFilePath)
testCopyFile(provider, filePath: renamedFilePath, to: textFilePath)
testRemoveFile(provider, filePath: textFilePath)
// TODO: Test search
// TODO: Test provider delegate
// Test upload/download
testUploadFile(provider, filePath: uploadFilePath)
testDownloadFile(provider, filePath: uploadFilePath)
}
}
+22
View File
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB