@@ -62,6 +62,45 @@ extension _FileManagerImpl {
6262 var temporaryDirectory : URL {
6363 URL ( filePath: String . temporaryDirectoryPath, directoryHint: . isDirectory)
6464 }
65+
66+ private func itemReplacementDirectory( appropriateFor reference: URL ) throws -> URL {
67+ guard reference. isFileURL else {
68+ throw CocoaError . errorWithFilePath ( . fileWriteUnsupportedScheme, reference)
69+ }
70+
71+ let temporaryItemsDirectory = temporaryDirectory. appending ( component: " TemporaryItems " , directoryHint: . isDirectory)
72+ let referenceDirectory = reference. deletingPathExtension ( )
73+ let useTemporaryDirectory : Bool
74+ if let temporaryVolumeIdentifier = try ? temporaryDirectory. resourceValues ( forKeys: [ . volumeIdentifierKey] ) . volumeIdentifier,
75+ let referenceVolumeIdentifier = try ? reference. resourceValues ( forKeys: [ . volumeIdentifierKey] ) . volumeIdentifier {
76+ useTemporaryDirectory = temporaryVolumeIdentifier. isEqual ( referenceVolumeIdentifier)
77+ } else {
78+ useTemporaryDirectory = !fileManager. isWritableFile ( atPath: referenceDirectory. path)
79+ }
80+
81+ let containerDirectory = useTemporaryDirectory ? temporaryItemsDirectory : referenceDirectory
82+ try fileManager. createDirectory ( at: containerDirectory, withIntermediateDirectories: true , attributes: [ . posixPermissions: 0o700 ] )
83+
84+ var attempt = 0
85+ while true {
86+ let replacementDirectory = containerDirectory. appending ( component: itemReplacementDirectoryName ( forAttempt: attempt) , directoryHint: . isDirectory)
87+ do {
88+ try fileManager. createDirectory ( at: replacementDirectory, withIntermediateDirectories: false )
89+ return replacementDirectory
90+ } catch let error as CocoaError where error. code == . fileWriteFileExists {
91+ attempt += 1
92+ }
93+ }
94+ }
95+
96+ private func itemReplacementDirectoryName( forAttempt attempt: Int ) -> String {
97+ let processName = ProcessInfo . processInfo. processName. filter { $0. isLetter || $0. isNumber }
98+ let sanitizedProcessName = processName. isEmpty ? " Process " : processName
99+ if attempt == 0 {
100+ return " (A Document Being Saved By \( sanitizedProcessName) ) "
101+ }
102+ return " (A Document Being Saved By \( sanitizedProcessName) \( attempt + 1 ) ) "
103+ }
65104
66105 func url(
67106 for directory: FileManager . SearchPathDirectory ,
@@ -85,6 +124,10 @@ extension _FileManagerImpl {
85124 domain = . _partitionedSystemDomainMask
86125 }
87126 #endif
127+
128+ if let url, domain == . userDomainMask, directory == . itemReplacementDirectory {
129+ return try itemReplacementDirectory ( appropriateFor: url)
130+ }
88131
89132 let urls = Array ( _SearchPathURLs ( for: directory, in: domain, expandTilde: true ) )
90133 #if FOUNDATION_FRAMEWORK
0 commit comments