-
-
Notifications
You must be signed in to change notification settings - Fork 83
Description
On Windows, removing a directory on OneDrive fails with a java.nio.file.AccessDeniedException, even if the folder was created by os-lib.
os.makeDir.all(os.home / "OneDrive" / "Documents" / "foo") // or move an existing folder there
// later:
os.remove.all(os.home / "OneDrive" / "Documents" / "foo") // throws java.nio.file.AccessDeniedExceptionIf the folder contains files, they are deleted successfully, but the folder remains. The folder can still be renamed or moved, but not deleted.
(The error is presumably triggered by ERROR_ACCESS_DENIED (5) of RemoveDirectoryW (or GetFileAttributesEx), called by the native JDK implementation.)
OneDrive seems to set a read-only attribute for the folder. End users cannot clear it for folders (only files) using the folder properties in Windows Explorer. As an application author, it's difficult to know which paths might be affected.
Proposal:
Add an optional force: Boolean argument to os.remove and os.remove.all which clears the attributes ReadOnly, Hidden and System if needed. This is also what Powershell does (MIT license). It would also be in the spirit of the os.remove ScalaDocs which state:
Roughly equivalent to bash's
rm -rf.
For example, I use this function as a workaround, the force=true case:
def removeAllForcibly(target: os.Path): Unit = {
import java.nio.file.{Files, LinkOption}
require(target.segmentCount != 0, s"Cannot remove a root directory: $target")
if (Files.exists(target.wrapped, LinkOption.NOFOLLOW_LINKS)) {
val paths =
if (Files.isDirectory(target.wrapped, LinkOption.NOFOLLOW_LINKS))
os.walk.stream(target, preOrder = false) ++ Seq(target)
else
geny.Generator[os.Path](target)
for (p <- paths) {
try {
val _ = Files.deleteIfExists(p.wrapped)
} catch { case e: java.io.IOException => // typically java.nio.file.AccessDeniedException when folders are read-only, e.g. on OneDrive on Windows
try {
// Attempt to clear problematic Windows flags
val dos = Files.getFileAttributeView(p.wrapped, classOf[java.nio.file.attribute.DosFileAttributeView], LinkOption.NOFOLLOW_LINKS)
if (dos != null) {
dos.setReadOnly(false)
try dos.setHidden(false) catch { case _: Throwable => /*ignore*/ }
val _ = Files.deleteIfExists(p.wrapped) // second attempt
} else {
throw e
}
} catch { case ignore: Throwable => throw e }
}
}
}
}As this problem is also present in the underlying java.nio, YMMV, so feel free to disregard. There's surprisingly little info about this problem on the web though, so perhaps the workaround is useful for someone.