Files
FileProvider/Sources/Extensions/FoundationExtensions.swift
T

479 lines
19 KiB
Swift
Executable File

//
// 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 {
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")
}
public extension ProgressUserInfoKey {
/// **FileProvider** returns associated `FileProviderOperationType`
public static let fileProvderOperationTypeKey = ProgressUserInfoKey("FilesProviderOperationTypeKey")
/// **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
}
}
struct Quality<T> {
let value: T
let quality: Float
var stringifed: String {
var representaion = String(describing: value)
let quality = min(1, max(self.quality, 0))
if let value = value as? Locale {
representaion = "\(value.identifier.replacingOccurrences(of: "_", with: "-"))"
}
if let value = value as? String.Encoding {
let cfEncoding = CFStringConvertNSStringEncodingToEncoding(value.rawValue)
representaion = CFStringConvertEncodingToIANACharSetName(cfEncoding) as String? ?? "*"
}
let qualityDesc = String(format: "%.1f", quality)
return "\(representaion); q=\(qualityDesc)"
}
}
internal extension URLRequest {
mutating func set(httpAuthentication 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 set(httpAcceptCharset acceptCharset: String.Encoding) {
let cfEncoding = CFStringConvertNSStringEncodingToEncoding(acceptCharset.rawValue)
if let charsetString = CFStringConvertEncodingToIANACharSetName(cfEncoding) as String? {
self.addValue(charsetString, forHTTPHeaderField: "Accept-Charset")
}
}
mutating func set(httpAcceptCharset acceptCharset: Quality<String.Encoding>) {
self.addValue(acceptCharset.stringifed, forHTTPHeaderField: "Accept-Charset")
}
mutating func set(httpAcceptCharsets acceptCharsets: [String.Encoding]) {
self.setValue(nil, forHTTPHeaderField: "Accept-Charset")
for charset in acceptCharsets {
let cfEncoding = CFStringConvertNSStringEncodingToEncoding(charset.rawValue)
if let charsetString = CFStringConvertEncodingToIANACharSetName(cfEncoding) as String? {
self.addValue(charsetString, forHTTPHeaderField: "Accept-Charset")
}
}
}
mutating func set(httpAcceptCharsets acceptCharsets: [Quality<String.Encoding>]) {
self.setValue(nil, forHTTPHeaderField: "Accept-Charset")
for charset in acceptCharsets.sorted(by: { $0.quality > $1.quality }) {
self.addValue(charset.stringifed, forHTTPHeaderField: "Accept-Charset")
}
}
enum Encoding: String {
case all = "*"
case identity
case gzip
case deflate
}
mutating func set(httpAcceptEncoding acceptEncoding: Encoding) {
self.addValue(acceptEncoding.rawValue, forHTTPHeaderField: "Accept-Encoding")
}
mutating func set(httpAcceptEncoding acceptEncoding: Quality<Encoding>) {
self.addValue(acceptEncoding.stringifed, forHTTPHeaderField: "Accept-Encoding")
}
mutating func set(httpAcceptEncodings acceptEncodings: [Encoding]) {
self.setValue(nil, forHTTPHeaderField: "Accept-Encoding")
for encoding in acceptEncodings {
self.addValue(encoding.rawValue, forHTTPHeaderField: "Accept-Encoding")
}
}
mutating func set(httpAcceptEncodings acceptEncodings: [Quality<Encoding>]) {
self.setValue(nil, forHTTPHeaderField: "Accept-Encoding")
for encoding in acceptEncodings.sorted(by: { $0.quality > $1.quality }) {
self.addValue(encoding.stringifed, forHTTPHeaderField: "Accept-Encoding")
}
}
mutating func set(httpAcceptLanguage acceptLanguage: Locale) {
let langCode = acceptLanguage.identifier.replacingOccurrences(of: "_", with: "-")
self.addValue(langCode, forHTTPHeaderField: "Accept-Language")
}
mutating func set(httpAcceptLanguage acceptLanguage: Quality<Locale>) {
self.addValue(acceptLanguage.stringifed, forHTTPHeaderField: "Accept-Language")
}
mutating func set(httpAcceptLanguages acceptLanguages: [Locale]) {
self.setValue(nil, forHTTPHeaderField: "Accept-Language")
for lang in acceptLanguages {
let langCode = lang.identifier.replacingOccurrences(of: "_", with: "-")
self.addValue(langCode, forHTTPHeaderField: "Accept-Language")
}
}
mutating func set(httpAcceptLanguages acceptLanguages: [Quality<Locale>]) {
self.setValue(nil, forHTTPHeaderField: "Accept-Language")
for lang in acceptLanguages.sorted(by: { $0.quality > $1.quality} ) {
self.addValue(lang.stringifed, forHTTPHeaderField: "Accept-Language")
}
}
mutating func set(httpRangeWithOffset 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 set(httpRange 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")
}
}
struct ContentMIMEType: RawRepresentable {
public var rawValue: String
public typealias RawValue = String
public init(rawValue: String) {
self.rawValue = rawValue
}
static let javascript = ContentMIMEType(rawValue: "application/javascript")
static let json = ContentMIMEType(rawValue: "application/json")
static let pdf = ContentMIMEType(rawValue: "application/pdf")
static let stream = ContentMIMEType(rawValue: "application/octet-stream")
static let zip = ContentMIMEType(rawValue: "application/zip")
// Texts
static let css = ContentMIMEType(rawValue: "text/css")
static let html = ContentMIMEType(rawValue: "text/html")
static let plainText = ContentMIMEType(rawValue: "text/plain")
static let xml = ContentMIMEType(rawValue: "text/xml")
// Images
static let gif = ContentMIMEType(rawValue: "image/gif")
static let jpeg = ContentMIMEType(rawValue: "image/jpeg")
static let png = ContentMIMEType(rawValue: "image/png")
}
mutating func set(httpContentType 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 set(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
}
}
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 []
}
}
}
extension URLError.Code: FoundationErrorEnum {}
extension CocoaError.Code: FoundationErrorEnum {}