2 Commits

Author SHA1 Message Date
62ca9f2378 Update README.md 2025-01-04 02:31:44 +03:00
f73d612666 Restore COW on copy for linux 2025-01-04 02:28:26 +03:00
4 changed files with 34 additions and 12 deletions

View File

@@ -19,7 +19,7 @@ structure for that files.
## Installing ## Installing
```bash ```bash
go install github.com/derfenix/photocatalog@latest go install github.com/derfenix/photocatalog/v2@latest
``` ```
Optionally you could copy created binary from the GO's bin path to Optionally you could copy created binary from the GO's bin path to
system or user $PATH, e.g. /usr/local/bin/. system or user $PATH, e.g. /usr/local/bin/.
@@ -27,44 +27,48 @@ system or user $PATH, e.g. /usr/local/bin/.
sudo cp ${GOPATH}/bin/photocatalog /usr/local/bin/photocatalog sudo cp ${GOPATH}/bin/photocatalog /usr/local/bin/photocatalog
``` ```
## Migrating from v0.*
TODO
## Supported formats ## Supported formats
At this moment supported jpeg files with filled exif data or any other At this moment supported jpeg files with filled exif data or any other
files but with names matching pattern `yyymmdd_HHMMSS.ext`. Such files but with names matching pattern `yyymmdd_HHMMSS.ext`. Such
names format applied by android's camera software (I guess all cams names format applied by android's camera software (I guess all cams
use this format, fix me if I'm wrong). use this format, fix me if I'm wrong).
There is no support for changing names format without modifying source code There is no support for changing names format without modifying source code
at this time. at this time.
## Usage ## Usage
### One-shot ### One-shot
#### Copy files (make a COW if fs supports it) #### Copy files (make a COW if fs supports it)
```bash ```bash
photocalog -mode copy -target ./photos/ ./sync/photos/* photocalog -mode copy -target ./photos/ -source ./sync/photos/
``` ```
#### Create hardlinks (only withing one disk partition) #### Create hardlinks (only withing one disk partition)
```bash ```bash
photocalog -mode hardlink -target ./photos/ ./sync/photos/* photocalog -mode hardlink -target ./photos/ -source ./sync/photos/
``` ```
or or
```bash ```bash
photocalog -target ./photos/ ./sync/photos/* photocalog -target ./photos/ -source ./sync/photos/*
``` ```
### Monitor ### Watch mode
#### Copy files (make a COW if fs supports it) #### Copy files (make a COW if fs supports it)
```bash ```bash
photocalog -mode copy -target ./photos -monitor ./sync/photos/* photocalog -mode copy -target ./photos -watch -source ./sync/photos/
``` ```
#### Create hardlinks (only withing one disk partition) #### Create hardlinks (only withing one disk partition)
```bash ```bash
photocalog -mode hardlink -target ./photos/ -monitor ./sync/photos/ photocalog -mode hardlink -target ./photos/ -watch -source ./sync/photos/
``` ```
or or
```bash ```bash
photocalog -target ./photos/ -monitor ./sync/photos/ photocalog -target ./photos/ -watch -source ./sync/photos/
``` ```
## Install and run monitor service ## Install and run monitor service
@@ -89,6 +93,6 @@ under corresponding sub-dir.
### Why this tool was created if there is awesome XXX tool? ### Why this tool was created if there is awesome XXX tool?
I had two good reasons: I had two good reasons:
1. I wanted 1. I wish
2. I can 2. I can

View File

@@ -33,7 +33,7 @@ func (c *Config) Validate() error {
return fmt.Errorf("target dir is required") return fmt.Errorf("target dir is required")
} }
if !slices.Contains([]Mode{ModeHardlink, ModeSymlink, ModeMove, ModeSymlink}, c.Mode) { if !slices.Contains([]Mode{ModeHardlink, ModeSymlink, ModeMove, ModeCopy}, c.Mode) {
return fmt.Errorf("invalid mode %s", c.Mode) return fmt.Errorf("invalid mode %s", c.Mode)
} }

View File

@@ -5,12 +5,18 @@ import (
"io" "io"
"log" "log"
"os" "os"
"runtime"
"sync/atomic"
"golang.org/x/sys/unix"
) )
var cowDisabled = atomic.Bool{}
type Copy struct{} type Copy struct{}
func (c Copy) PlaceIt(sourcePath, targetPath string, mode os.FileMode) error { func (c Copy) PlaceIt(sourcePath, targetPath string, mode os.FileMode) error {
targetFile, err := os.OpenFile(targetPath, os.O_TRUNC|os.O_RDWR|os.O_CREATE, mode) targetFile, err := os.OpenFile(targetPath, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, mode)
if err != nil { if err != nil {
return fmt.Errorf("open target file: %w", err) return fmt.Errorf("open target file: %w", err)
} }
@@ -28,6 +34,17 @@ func (c Copy) PlaceIt(sourcePath, targetPath string, mode os.FileMode) error {
_ = sourceFile.Close() _ = sourceFile.Close()
}() }()
// Try to do a COW.
if runtime.GOOS == "linux" && !cowDisabled.Load() {
if err := unix.IoctlFileClone(int(targetFile.Fd()+1), int(sourceFile.Fd())); err == nil {
return nil
} else {
log.Println(fmt.Errorf("COW attempt for %s failed: %w", targetPath, err))
log.Println("Disabling COW until restart")
cowDisabled.Store(true)
}
}
copySize, err := io.Copy(targetFile, sourceFile) copySize, err := io.Copy(targetFile, sourceFile)
if err != nil { if err != nil {
return fmt.Errorf("copy source file: %w", err) return fmt.Errorf("copy source file: %w", err)

View File

@@ -268,6 +268,7 @@ func (o *Organizer) BuildTargetPath(sourcePath string, meta metadata.Metadata) (
o.targetDir, o.targetDir,
strconv.Itoa(meta.Created.Year()), strconv.Itoa(meta.Created.Year()),
strconv.Itoa(int(meta.Created.Month())), strconv.Itoa(int(meta.Created.Month())),
strconv.Itoa(meta.Created.Day()),
sourcePath, sourcePath,
) )