This repository was archived by the owner on Feb 24, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathFileManagerExtension.swift
117 lines (104 loc) · 4.74 KB
/
FileManagerExtension.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
//
// FileManagerExtension.swift
//
// Copyright © 2021 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Common
import Foundation
import os.log
extension FileManager {
@discardableResult
func moveItem(at srcURL: URL, to destURL: URL, incrementingIndexIfExists flag: Bool, pathExtension: String? = nil) throws -> URL {
guard srcURL != destURL else { return destURL }
guard flag else {
try moveItem(at: srcURL, to: destURL)
return destURL
}
return try withNonExistentUrl(for: destURL, incrementingIndexIfExistsUpTo: 10000, pathExtension: pathExtension) { url in
try moveItem(at: srcURL, to: url)
return url
}
}
@discardableResult
func copyItem(at srcURL: URL, to destURL: URL, incrementingIndexIfExists flag: Bool, pathExtension: String? = nil) throws -> URL {
guard srcURL != destURL else { return destURL }
guard flag else {
try moveItem(at: srcURL, to: destURL)
return destURL
}
return try withNonExistentUrl(for: destURL, incrementingIndexIfExistsUpTo: flag ? 10000 : 0, pathExtension: pathExtension) { url in
try copyItem(at: srcURL, to: url)
return url
}
}
func withNonExistentUrl<T>(for desiredURL: URL,
incrementingIndexIfExistsUpTo limit: UInt,
pathExtension: String? = nil,
continueOn shouldContinue: (Error) -> Bool = { ($0 as? CocoaError)?.code == .fileWriteFileExists },
perform operation: (URL) throws -> T) throws -> T {
var suffix = pathExtension ?? desiredURL.pathExtension
if !suffix.hasPrefix(".") {
suffix = "." + suffix
}
if !desiredURL.pathExtension.isEmpty {
if !desiredURL.path.hasSuffix(suffix) {
suffix = "." + desiredURL.pathExtension
}
} else {
suffix = ""
}
let ownerDirectory = desiredURL.deletingLastPathComponent()
let fileNameWithoutExtension = desiredURL.lastPathComponent.dropping(suffix: suffix)
var index: UInt = 0
repeat {
let desiredURL: URL = {
// Zero means we haven't tried anything yet, so use the suggested name.
// Otherwise, simply append the file name with the copy number.
guard index > 0 else { return desiredURL }
return ownerDirectory.appendingPathComponent("\(fileNameWithoutExtension) \(index)\(suffix)")
}()
if !self.fileExists(atPath: desiredURL.path) {
do {
return try operation(desiredURL)
} catch {
guard shouldContinue(error) else { throw error }
// This is expected, as moveItem throws an error if the file already exists
index += 1
}
}
index += 1
} while index <= limit
// If it gets beyond the limit then chances are something else is wrong
Logger.general.error("Failed to move file to \(desiredURL.deletingLastPathComponent().path), attempt: \(index)")
throw CocoaError(.fileWriteFileExists)
}
func isInTrash(_ url: URL) -> Bool {
let resolvedUrl = url.resolvingSymlinksInPath()
guard let trashUrl = (try? self.url(for: .trashDirectory, in: .allDomainsMask, appropriateFor: resolvedUrl, create: false))
?? urls(for: .trashDirectory, in: .userDomainMask).first else { return false }
return resolvedUrl.path.hasPrefix(trashUrl.path)
}
/// Check if location pointed by the URL is writable by writing an empty data to it and removing the file if write succeeds
/// - Throws error if writing to the location fails
func checkWritability(_ url: URL) throws {
if fileExists(atPath: url.path), isWritableFile(atPath: url.path) {
return // we can write
} else {
// either we can‘t write or there‘s no file at the url – try writing throwing access error if no permission
try Data().write(to: url)
try removeItem(at: url)
}
}
}