From 19ea14ebc1bf8f0a053d266cfaadd084cf4fdd06 Mon Sep 17 00:00:00 2001 From: Amir Abbas Date: Mon, 12 Dec 2016 01:57:34 +0330 Subject: [PATCH] Added `FileObjectSorting` to sort FileObjects conveniently --- FileProvider.xcodeproj/project.pbxproj | 8 + Sources/FileObject.swift | 261 +++++++++++++++++++++++++ Sources/FileProvider.swift | 151 -------------- Sources/SMBTypes/SMB2DataTypes.swift | 29 +++ Sources/SMBTypes/SMB2Types.swift | 29 --- 5 files changed, 298 insertions(+), 180 deletions(-) create mode 100644 Sources/FileObject.swift diff --git a/FileProvider.xcodeproj/project.pbxproj b/FileProvider.xcodeproj/project.pbxproj index f60441e..2380c30 100644 --- a/FileProvider.xcodeproj/project.pbxproj +++ b/FileProvider.xcodeproj/project.pbxproj @@ -92,6 +92,9 @@ 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 */; }; + 79F5745B1DFDB10B00179ABF /* FileObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79F5745A1DFDB10A00179ABF /* FileObject.swift */; }; + 79F5745C1DFDB10B00179ABF /* FileObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79F5745A1DFDB10A00179ABF /* FileObject.swift */; }; + 79F5745D1DFDB10B00179ABF /* FileObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79F5745A1DFDB10A00179ABF /* FileObject.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -131,6 +134,7 @@ 799396A31D48C02300086753 /* SMB2Types.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2Types.swift; sourceTree = ""; }; 799396A41D48C02300086753 /* SMBErrorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMBErrorType.swift; sourceTree = ""; }; 799396A61D48C02300086753 /* WebDAVFileProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebDAVFileProvider.swift; sourceTree = ""; }; + 79F5745A1DFDB10A00179ABF /* FileObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileObject.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -218,6 +222,7 @@ 794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */, 799396941D48C02300086753 /* FileProvider.h */, 799396951D48C02300086753 /* FileProvider.swift */, + 79F5745A1DFDB10A00179ABF /* FileObject.swift */, 799396961D48C02300086753 /* LocalFileProvider.swift */, 792572401DF23BDA006A1526 /* LocalHelper.swift */, 7902C0851D61B56D00564440 /* RemoteSession.swift */, @@ -402,6 +407,7 @@ 799396D41D48C02300086753 /* SMB2Tree.swift in Sources */, 7924B1A21D89DAE000589DB7 /* Options.swift in Sources */, 792572411DF23BDA006A1526 /* LocalHelper.swift in Sources */, + 79F5745B1DFDB10B00179ABF /* FileObject.swift in Sources */, 7924B1991D89DAE000589DB7 /* Element.swift in Sources */, 799396C81D48C02300086753 /* SMB2IOCtl.swift in Sources */, 799396D71D48C02300086753 /* SMB2Types.swift in Sources */, @@ -437,6 +443,7 @@ 799396D51D48C02300086753 /* SMB2Tree.swift in Sources */, 7924B1A31D89DAE000589DB7 /* Options.swift in Sources */, 792572421DF23BDA006A1526 /* LocalHelper.swift in Sources */, + 79F5745C1DFDB10B00179ABF /* FileObject.swift in Sources */, 7924B1B01D89F7DE00589DB7 /* FPSStreamTask.swift in Sources */, 7924B19A1D89DAE000589DB7 /* Element.swift in Sources */, 799396C91D48C02300086753 /* SMB2IOCtl.swift in Sources */, @@ -472,6 +479,7 @@ 799396D61D48C02300086753 /* SMB2Tree.swift in Sources */, 7924B1A41D89DAE000589DB7 /* Options.swift in Sources */, 792572431DF23BDA006A1526 /* LocalHelper.swift in Sources */, + 79F5745D1DFDB10B00179ABF /* FileObject.swift in Sources */, 7924B1B11D89F7DF00589DB7 /* FPSStreamTask.swift in Sources */, 7924B19B1D89DAE000589DB7 /* Element.swift in Sources */, 799396CA1D48C02300086753 /* SMB2IOCtl.swift in Sources */, diff --git a/Sources/FileObject.swift b/Sources/FileObject.swift new file mode 100644 index 0000000..7cb7b14 --- /dev/null +++ b/Sources/FileObject.swift @@ -0,0 +1,261 @@ +// +// FileProvider.swift +// FileProvider +// +// Created by Amir Abbas Mousavian. +// Copyright © 2016 Mousavian. Distributed under MIT license. +// + +import Foundation + +/// Containts path and attributes of a file or resource. +open class FileObject { + open internal(set) var allValues: [String: Any] + + internal init(allValues: [String: Any]) { + self.allValues = allValues + } + + internal init(absoluteURL: URL? = nil, name: String, path: String) { + self.allValues = [String: Any]() + self.absoluteURL = absoluteURL + self.name = name + self.path = path + } + + /// url to access the resource, not supported by Dropbox provider + open internal(set) var absoluteURL: URL? { + get { + return allValues["NSURLAbsoluteURLKey"] as? URL + } + set { + allValues["NSURLAbsoluteURLKey"] = newValue + } + } + + /// Name of the file, usually equals with the last path component + open internal(set) var name: String { + get { + return allValues[URLResourceKey.nameKey.rawValue] as! String + } + set { + allValues[URLResourceKey.nameKey.rawValue] = newValue + } + } + + /// Relative path of file object + open internal(set) var path: String { + get { + return allValues[URLResourceKey.pathKey.rawValue] as! String + } + set { + allValues[URLResourceKey.pathKey.rawValue] = newValue + } + } + + /// Size of file on disk, return -1 for directories. + open internal(set) var size: Int64 { + get { + return allValues[URLResourceKey.fileSizeKey.rawValue] as? Int64 ?? -1 + } + set { + allValues[URLResourceKey.fileSizeKey.rawValue] = Int(exactly: newValue) ?? Int.max + } + } + + /// The time contents of file has been created, returns nil if not set + open internal(set) var creationDate: Date? { + get { + return allValues[URLResourceKey.creationDateKey.rawValue] as? Date + } + set { + allValues[URLResourceKey.creationDateKey.rawValue] = newValue + } + } + + /// The time contents of file has been modified, returns nil if not set + open internal(set) var modifiedDate: Date? { + get { + return allValues[URLResourceKey.contentModificationDateKey.rawValue] as? Date + } + set { + allValues[URLResourceKey.contentModificationDateKey.rawValue] = newValue + } + } + + /// return resource type of file, usually directory, regular or symLink + open internal(set) var type: URLFileResourceType? { + get { + return allValues[URLResourceKey.fileResourceTypeKey.rawValue] as? URLFileResourceType + } + set { + allValues[URLResourceKey.fileResourceTypeKey.rawValue] = newValue + } + } + + @available(*, deprecated, message: "Use FileObject.type property instead.") + open var fileType: URLFileResourceType? { + return self.type + } + + /// File is hidden either because begining with dot or filesystem flags + /// Setting this value on a file begining with dot has no effect + open internal(set) var isHidden: Bool { + get { + return allValues[URLResourceKey.isHiddenKey.rawValue] as? Bool ?? false + } + set { + allValues[URLResourceKey.isHiddenKey.rawValue] = newValue + } + } + + /// File can not be written + open internal(set) var isReadOnly: Bool { + get { + return !(allValues[URLResourceKey.isWritableKey.rawValue] as? Bool ?? true) + } + set { + allValues[URLResourceKey.isWritableKey.rawValue] = !newValue + } + } + + /// File is a Directory + open var isDirectory: Bool { + return self.type == .directory + } + + /// File is a normal file + open var isRegularFile: Bool { + return self.type == .regular + } + + /// File is a Symbolic link + open var isSymLink: Bool { + return self.type == .symbolicLink + } +} + +/// Sorting FileObject array by given criteria, not thread-safe +public struct FileObjectSorting { + + /// Determines sort kind by which item of File object + public enum SortType { + /// Sorting by default Finder (case-insensitive) behavior + case name + /// Sorting by case-sensitive form of file name + case nameCaseSensitive + /// Sorting by case-in sensitive form of file name + case nameCaseInsensitive + /// Sorting by file type + case `extension` + /// Sorting by file modified date + case modifiedDate + /// Sorting by file creation date + case creationDate + /// Sorting by file modified date + case size + + /// all sort types + static var allItems: [SortType] { + return [.name, .nameCaseSensitive, .nameCaseInsensitive, .extension, + .modifiedDate,.creationDate, .size] + } + } + + public let sortType: SortType + /// puts A before Z, default is true + public let ascending: Bool + /// puts directories on top, regardless of other attributes, default is false + public let isDirectoriesFirst: Bool + + public static let nameAscending = FileObjectSorting(type: .name, ascending: true) + public static let nameDesceding = FileObjectSorting(type: .name, ascending: false) + public static let sizeAscending = FileObjectSorting(type: .size, ascending: true) + public static let sizeDesceding = FileObjectSorting(type: .size, ascending: false) + public static let extensionAscending = FileObjectSorting(type: .extension, ascending: true) + public static let extensionDesceding = FileObjectSorting(type: .extension, ascending: false) + public static let modifiedAscending = FileObjectSorting(type: .modifiedDate, ascending: true) + public static let modifiedDesceding = FileObjectSorting(type: .modifiedDate, ascending: false) + public static let createdAscending = FileObjectSorting(type: .creationDate, ascending: true) + public static let createdDesceding = FileObjectSorting(type: .creationDate, ascending: false) + + public init (type: SortType, ascending: Bool = true, isDirectoriesFirst: Bool = false) { + self.sortType = type + self.ascending = ascending + self.isDirectoriesFirst = isDirectoriesFirst + } + + /// Sorts array of FileObjects by criterias set in properties + public func sort(_ files: [FileObject]) -> [FileObject] { + return files.sorted { + if isDirectoriesFirst { + if ($0.isDirectory) && !($1.isDirectory) { + return true + } + if !($0.isDirectory) && ($1.isDirectory) { + return false + } + } + switch sortType { + case .name: + return ($0.name).localizedStandardCompare($1.name) == (ascending ? .orderedAscending : .orderedDescending) + case .nameCaseSensitive: + return ($0.name).localizedCompare($1.name) == (ascending ? .orderedAscending : .orderedDescending) + case .nameCaseInsensitive: + return ($0.name).localizedCaseInsensitiveCompare($1.name) == (ascending ? .orderedAscending : .orderedDescending) + case .extension: + let kind1 = $0.isDirectory ? "folder" : ($0.path as NSString).pathExtension + let kind2 = $1.isDirectory ? "folder" : ($1.path as NSString).pathExtension + return kind1.localizedCaseInsensitiveCompare(kind2) == (ascending ? .orderedAscending : .orderedDescending) + case .modifiedDate: + let fileMod1 = $0.modifiedDate ?? Date.distantPast + let fileMod2 = $1.modifiedDate ?? Date.distantPast + return ascending ? fileMod1 < fileMod2 : fileMod1 > fileMod2 + case .creationDate: + let fileCreation1 = $0.creationDate ?? Date.distantPast + let fileCreation2 = $1.creationDate ?? Date.distantPast + return ascending ? fileCreation1 < fileCreation2 : fileCreation1 > fileCreation2 + case .size: + return ascending ? $0.size < $1.size : $0.size > $1.size + } + } + } +} + +extension URLFileResourceType { + public init(fileTypeValue: FileAttributeType) { + switch fileTypeValue { + case FileAttributeType.typeCharacterSpecial: self = .characterSpecial + case FileAttributeType.typeDirectory: self = .directory + case FileAttributeType.typeBlockSpecial: self = .blockSpecial + case FileAttributeType.typeRegular: self = .regular + case FileAttributeType.typeSymbolicLink: self = .symbolicLink + case FileAttributeType.typeSocket: self = .socket + case FileAttributeType.typeUnknown: self = .unknown + default: self = .unknown + } + } +} + +internal extension URL { + var uw_scheme: String { + return self.scheme ?? "" + } +} + +internal func jsonToDictionary(_ jsonString: String) -> [String: AnyObject]? { + guard let data = jsonString.data(using: .utf8) else { + return nil + } + if let dic = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions()) as? [String: AnyObject] { + return dic + } + return nil +} + +internal func dictionaryToJSON(_ dictionary: [String: AnyObject]) -> String? { + if let data = try? JSONSerialization.data(withJSONObject: dictionary, options: JSONSerialization.WritingOptions()) { + return String(data: data, encoding: .utf8) + } + return nil +} diff --git a/Sources/FileProvider.swift b/Sources/FileProvider.swift index 6246bb9..efa2b4a 100644 --- a/Sources/FileProvider.swift +++ b/Sources/FileProvider.swift @@ -360,118 +360,6 @@ public enum FileOperationType: CustomStringConvertible { } } -open class FileObject { - open internal(set) var allValues: [String: Any] - - internal init(allValues: [String: Any]) { - self.allValues = allValues - } - - internal init(absoluteURL: URL? = nil, name: String, path: String) { - self.allValues = [String: Any]() - self.absoluteURL = absoluteURL - self.name = name - self.path = path - } - - open internal(set) var absoluteURL: URL? { - get { - return allValues["NSURLAbsoluteURLKey"] as? URL - } - set { - allValues["NSURLAbsoluteURLKey"] = newValue - } - } - - open internal(set) var name: String { - get { - return allValues[URLResourceKey.nameKey.rawValue] as! String - } - set { - allValues[URLResourceKey.nameKey.rawValue] = newValue - } - } - - open internal(set) var path: String { - get { - return allValues[URLResourceKey.pathKey.rawValue] as! String - } - set { - allValues[URLResourceKey.pathKey.rawValue] = newValue - } - } - - open internal(set) var size: Int64 { - get { - return allValues[URLResourceKey.fileSizeKey.rawValue] as? Int64 ?? -1 - } - set { - allValues[URLResourceKey.fileSizeKey.rawValue] = Int(exactly: newValue) ?? Int.max - } - } - - open internal(set) var creationDate: Date? { - get { - return allValues[URLResourceKey.creationDateKey.rawValue] as? Date - } - set { - allValues[URLResourceKey.creationDateKey.rawValue] = newValue - } - } - - open internal(set) var modifiedDate: Date? { - get { - return allValues[URLResourceKey.contentModificationDateKey.rawValue] as? Date - } - set { - allValues[URLResourceKey.contentModificationDateKey.rawValue] = newValue - } - } - - open internal(set) var type: URLFileResourceType? { - get { - return allValues[URLResourceKey.fileResourceTypeKey.rawValue] as? URLFileResourceType - } - set { - allValues[URLResourceKey.fileResourceTypeKey.rawValue] = newValue - } - } - - @available(*, deprecated, message: "Use FileObject.type property instead.") - open var fileType: URLFileResourceType? { - return self.type - } - - open internal(set) var isHidden: Bool { - get { - return allValues[URLResourceKey.isHiddenKey.rawValue] as? Bool ?? false - } - set { - allValues[URLResourceKey.isHiddenKey.rawValue] = newValue - } - } - - open internal(set) var isReadOnly: Bool { - get { - return !(allValues[URLResourceKey.isWritableKey.rawValue] as? Bool ?? true) - } - set { - allValues[URLResourceKey.isWritableKey.rawValue] = !newValue - } - } - - open var isDirectory: Bool { - return self.type == .directory - } - - open var isRegularFile: Bool { - return self.type == .regular - } - - open var isSymLink: Bool { - return self.type == .symbolicLink - } -} public protocol OperationHandle { var operationType: FileOperationType { get } @@ -505,13 +393,6 @@ public protocol FileOperationDelegate: class { func fileProvider(_ fileProvider: FileProviderOperations, shouldProceedAfterError error: Error, operation: FileOperationType) -> Bool } -// THESE ARE METHODS TO PROVIDE COMPATIBILITY WITH SWIFT 2.3 SIMOULTANIOUSLY! -internal extension URL { - var uw_scheme: String { - return self.scheme ?? "" - } -} - internal class Weak { weak var value : T? init (_ value: T) { @@ -519,21 +400,6 @@ internal class Weak { } } -extension URLFileResourceType { - public init(fileTypeValue: FileAttributeType) { - switch fileTypeValue { - case FileAttributeType.typeCharacterSpecial: self = .characterSpecial - case FileAttributeType.typeDirectory: self = .directory - case FileAttributeType.typeBlockSpecial: self = .blockSpecial - case FileAttributeType.typeRegular: self = .regular - case FileAttributeType.typeSymbolicLink: self = .symbolicLink - case FileAttributeType.typeSocket: self = .socket - case FileAttributeType.typeUnknown: self = .unknown - default: self = .unknown - } - } -} - public protocol FoundationErrorEnum { init? (rawValue: Int) var rawValue: Int { get } @@ -541,20 +407,3 @@ public protocol FoundationErrorEnum { extension URLError.Code: FoundationErrorEnum {} extension CocoaError.Code: FoundationErrorEnum {} - -internal func jsonToDictionary(_ jsonString: String) -> [String: AnyObject]? { - guard let data = jsonString.data(using: .utf8) else { - return nil - } - if let dic = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions()) as? [String: AnyObject] { - return dic - } - return nil -} - -internal func dictionaryToJSON(_ dictionary: [String: AnyObject]) -> String? { - if let data = try? JSONSerialization.data(withJSONObject: dictionary, options: JSONSerialization.WritingOptions()) { - return String(data: data, encoding: .utf8) - } - return nil -} diff --git a/Sources/SMBTypes/SMB2DataTypes.swift b/Sources/SMBTypes/SMB2DataTypes.swift index 309151e..4f30499 100644 --- a/Sources/SMBTypes/SMB2DataTypes.swift +++ b/Sources/SMBTypes/SMB2DataTypes.swift @@ -66,3 +66,32 @@ struct SMBTime { return Date(timeIntervalSince1970: Double(self.time) / 10000000 - 11644473600) } } + +extension Data { + init(value: T) { + var value = value + self = Data(buffer: UnsafeBufferPointer(start: &value, count: 1)) + } + + func scanValue() -> T? { + guard MemoryLayout.size <= self.count else { return nil } + return self.withUnsafeBytes { $0.pointee } + } + + func scanValue(start: Int) -> T? { + let length = MemoryLayout.size + guard self.count >= start + length else { return nil } + return self.subdata(in: start.. String? { + guard self.count >= start + length else { return nil } + return String(data: self.subdata(in: start..(from: T) -> U? { + guard MemoryLayout.size >= MemoryLayout.size else { return nil } + let data = Data(value: from) + return data.scanValue() + } +} diff --git a/Sources/SMBTypes/SMB2Types.swift b/Sources/SMBTypes/SMB2Types.swift index ff1064a..d6f94c7 100644 --- a/Sources/SMBTypes/SMB2Types.swift +++ b/Sources/SMBTypes/SMB2Types.swift @@ -8,35 +8,6 @@ import Foundation -extension Data { - init(value: T) { - var value = value - self = Data(buffer: UnsafeBufferPointer(start: &value, count: 1)) - } - - func scanValue() -> T? { - guard MemoryLayout.size <= self.count else { return nil } - return self.withUnsafeBytes { $0.pointee } - } - - func scanValue(start: Int) -> T? { - let length = MemoryLayout.size - guard self.count >= start + length else { return nil } - return self.subdata(in: start.. String? { - guard self.count >= start + length else { return nil } - return String(data: self.subdata(in: start..(from: T) -> U? { - guard MemoryLayout.size >= MemoryLayout.size else { return nil } - let data = Data(value: from) - return data.scanValue() - } -} - protocol FileProviderSMBHeader { var protocolID: UInt32 { get } static var protocolConst: UInt32 { get }