Skip to content

Commit

Permalink
Added robust file Rename function for moving files between filesystem…
Browse files Browse the repository at this point in the history
  • Loading branch information
twystd committed Jul 31, 2023
1 parent 57e68f2 commit 9e3fc2a
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### Added
1. `ActivateKeypads` API function for REST and MQTT `activate-keypads` command.
2. Robust file rename that falls back to copying the file to be renamed if the
OS relink failed.

### Updated
1. Added _card.format_ to the configuration to facilitate support for card formats other
Expand Down
3 changes: 2 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# TODO

- [x] Add card formats to configuration (cf. https://github.com/uhppoted/uhppote-cli/issues/12)
- [x] Implement `activate-keypads` (cf. https://github.com/uhppoted/uhppoted/issues/35)
- [x] Implement `activate-keypads` (cf. https://github.com/uhppoted/uhppoted/issues/35)\
- [ ] Implement robust Rename (cf. https://github.com/uhppoted/uhppoted-httpd/issues/20)

## TODO

Expand Down
72 changes: 72 additions & 0 deletions os/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package os

import (
"io"
sys "os"
"path/filepath"
"strings"
)

/**
* Replacement implementation for os.Rename that copies the file if os.Rename
* fails with an error.
*
* Use os.Rename to 'move' a file between filesystems on different filesystems fails
* with an 'invalid cross-device link' error. The replacement implementation first
* attempts a 'rename' and if that fails, creates a temporary file adjacent to the
* destination file, copies the source to the temporary file and then does an
* os.Rename(...) on the temporary file to overwrite the destination file.
*
* The temporary file and original file are deleted.
*
* NB: this is very much an application specific implementation - errors deleting the
* temporary or original file are discarded on the basis that this operation is
* typically used to copy create an updated 'working file' from a temporary file
* and the application can and will use the updated working file even if the original
* file is not deleted.
*
* Ref. https://github.com/uhppoted/uhppoted-httpd/issues/20
*
* (interim implementation pending resolution of https://github.com/golang/go/issues/41487)
*/
func Rename(oldpath, newpath string) error {
if err := sys.Rename(oldpath, newpath); err == nil {
return nil
}

dir := filepath.Dir(newpath)
ext := filepath.Ext(newpath)
base := filepath.Base(newpath)
tmpfile := strings.TrimSuffix(base, ext)

tmp, err := sys.CreateTemp(dir, tmpfile+"*")
if err != nil {
return err
} else {
defer sys.Remove(tmp.Name())
}

src, err := sys.Open(oldpath)
if err != nil {
return err
} else {
defer src.Close()
}

if _, err := io.Copy(tmp, src); err != nil {
return err
} else if err := tmp.Sync(); err != nil {
return err
}

src.Close()
tmp.Close()

if err := sys.Rename(tmp.Name(), newpath); err != nil {
return err
}

sys.Remove(oldpath)

return nil
}

0 comments on commit 9e3fc2a

Please sign in to comment.