Unable to Write to FolderItem on Catalina

I ran into a similar issue with Arbed 1.9 when built with Xojo 2019r3. I was trying to use replaceItemAtURL, like Sam suggested, but it was tricky because I needed a true exchange (swap) operation.

I came up with this code, including a way for Windows (though that’s not thoroughly tested). It requires a little bit of MBS but could also be solved by using NSURL from the good olde macOSLib. The use of fileSystemRepresentation may be replaced by NativePath.

(Update: I’ve fixed a little code issue - now the FolderItems are cloned first, even for macOS)

[code]Protected Function ExchangeFileContents(destFile as FolderItem, tempFile as FolderItem) As Boolean
// use clones so that the caller’s don’t get mixed up
destFile = new FolderItem(destFile)
tempFile = new FolderItem(tempFile)

#if TargetMacOS
dim urlDest as new NSURLMBS (destFile)
dim urlTemp as new NSURLMBS (tempFile)

if urlDest.VolumeIdentifier = urlTemp.VolumeIdentifier then
  const CocoaLib = "Cocoa"
  declare function objc_getClass lib CocoaLib (aClassName as CString) as Ptr
  declare function defaultManager lib CocoaLib selector "defaultManager" (obj as Ptr) as Ptr
  static defaultMgr as Ptr = defaultManager (objc_getClass("NSFileManager"))
  
  declare function replaceItem lib CocoaLib _
        selector "replaceItemAtURL:withItemAtURL:backupItemName:options:resultingItemURL:error:" _
        (obj as Ptr, url1 as Integer, url2 as Integer, backupName as CFStringRef, opts as Integer, _
        ByRef result as Integer, err as Ptr) as Boolean
  
  // we need this to get a swap effect; otherwise we'd lose the tempFile:
  const NSFileManagerItemReplacementWithoutDeletingBackupItem = 2
  dim tempName as String = tempFile.Name
  dim backupName as String = tempName+"_"
  
  dim result as Integer
  if replaceItem (defaultMgr, urlDest.Handle, urlTemp.Handle, backupName, _
              NSFileManagerItemReplacementWithoutDeletingBackupItem, result, nil) then
    dim newTemp as FolderItem = tempFile.Parent.TrueChild(backupName)
    newTemp.Name = tempName
    dim url_res as NSURLMBS = NSURLMBS.URLWithHandle (result)
    if StrComp (url_res.fileSystemRepresentation, urlDest.fileSystemRepresentation, 0) = 0 then
      return true
    end if
  end
  break
end if

#elseif TargetWin32
Soft Declare Function ReplaceFileW Lib “Kernel32” ( destination as WString, source as WString, _
backup as Integer, flags as Integer, reserved1 as Integer, reserved2 as Integer ) as Boolean

if System.IsFunctionAvailable( "ReplaceFileW", "Kernel32" ) then
  Const REPLACEFILE_WRITE_THROUGH = &h1
  dim success as Boolean = ReplaceFileW( destFile.AbsolutePath, tempFile.AbsolutePath, _
              0, REPLACEFILE_WRITE_THROUGH, 0, 0 )
  if success then
    destFile.Visible = true
    return true
  end
end if

#endif

return SimpleSwapFiles (destFile, tempFile)
End Function

Private Function SimpleSwapFiles(f as FolderItem, g as FolderItem) As Boolean
dim temp as FolderItem = f.Parent.TrueChild("."+f.Name+"-"+Format(Rnd()*10000,"#")+".tmp")
if temp.Exists then
temp.Delete
end if
f.MoveFileTo temp
If f.LastErrorCode <> 0 or f.Exists or NOT temp.Exists then
return false
End if
g.MoveFileTo f
If g.LastErrorCode <> 0 or g.Exists or NOT f.Exists then
return false
End if
temp.MoveFileTo g
If temp.LastErrorCode <> 0 or temp.Exists or NOT g.Exists then
return false
End if

return true
End Function
[/code]

Could this be related to the change in the file system APIs in some not that long ago Xojo version? That caused me quite a bit of pain as I figured out what the problem was.

In that case a folder item object no longer follows the actual file after you do that exchange. (it also wont follow the file anymore if any parent folder changes its name or moves like it used to, it’s basically just the path under the hood, it is not an actual link to the file)

so if you have

dim mainFile as folderitem = something
dim tempFile as folderitem = something else

and then do the swap, they are not swapped. the mainFile now points to the what used to be the tempFile and the tempFile now points to what used to be the mainFile. If you’re doing something like swapping them and then deleting the mainFile thinking it’s still pointing to the old file you wanted to replace what you’re actually doing is deleting the new file you just swapped into that path.

I don’t know if it’s related, but my blood pressure is going up again just thinking about the frustration in figuring all that out :wink:

Hi James, yes. And I just had to update my code after realizing that it would still confuse the FolderItems if I don’t clone them first.

And I also believe that the tracking behavior did change between 2012r2 and now. That’s why I do the cloning.

Oh my, I’m now getting the weirdest effects:

  1. After swapping the file with the above code of mine, Finder Aliases to the original file break in a way that Xojo can’t resolve them any more (but old RS still can). Jeez!

  2. My code that’s supposed to notice when an opened file got updated in the background is not working any more: It detect a change after the save, even though the code is supposed to store the new saved date so that it doesn’t see the modified file on disk as changed any more. I guess this is another result of the FolderItem caching behavioral change. Oh my, all these changes causing so much trouble, thanks to Xojo’s changes that I have no control over.

The code I use is available as part of the Ohanaware App Kit, which is sold in the 2020 Omegabundle (http://omegabundle.net).

absolutely did since 2012 would be carbon based and newer versions would be cocoa based
2012 would use the file manager calls from carbon and newer uses nsurl or something

[quote=494503:@Norman Palardy]absolutely did since 2012 would be carbon based and newer versions would be cocoa based
2012 would use the file manager calls from carbon and newer uses nsurl or something[/quote]
Yes, but that’s just an implementation detail that should not change behavior. Other than performance (such as that indexed access may become slower since the new macOS API has no calls for that).

What we’re seeing here, though, is that suddenly values are cached in a way that they do not update when the actual file’s properties have changed, and there’s not even an “UpdateProperties” function for triggering it. So, the person who made this change seems to not even have been aware of the consequences.

The work-around is to create a new FolderItem from the existing one (f = new FolderItem (f)) - that will then update its properties with the current on-disk values.