mirror of
https://github.com/derfenix/photocatalog.git
synced 2026-03-12 06:34:57 +03:00
Compare commits
58 Commits
bdd3eee69f
...
v2.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
| f0bef5fa2d | |||
| 065a56e621 | |||
| e71bb647b8 | |||
| 3ab4a04da0 | |||
| ffe9bfe466 | |||
| a1cafdfb8b | |||
| d20a64206d | |||
| 7fd6d66a12 | |||
| ba2f5da3ac | |||
| d8dffed10a | |||
| 8b3e7bf289 | |||
| d5a88cdad6 | |||
| f4f10bb8fa | |||
| a247c7e77a | |||
| a3754d901d | |||
| 471f17e546 | |||
| 4f050bcae7 | |||
| cb6492dfab | |||
| 8e9e075e39 | |||
| 3258452b5b | |||
| cfc318e14b | |||
| a650096ced | |||
| bd32223557 | |||
| 7793eb9dec | |||
| 6724d80c15 | |||
| b32c74d058 | |||
| 4b2d705906 | |||
| 4f19d006d0 | |||
| 08feb65d86 | |||
| 66a198cf91 | |||
| d2db0a66ad | |||
| ae55a0b71a | |||
| 5e383bb8df | |||
| 2526c0f0cb | |||
| 5de7ba17d8 | |||
| 6a7f2d04f0 | |||
| 9b9129d5fc | |||
| d53b050966 | |||
| 80b4942d0e | |||
| d179279d26 | |||
| bce5d42ac1 | |||
| 87e82d13c8 | |||
| 882d596aa7 | |||
| b0cd8bdefa | |||
| a8b2a94b09 | |||
| 83202087a5 | |||
| ee44aaceab | |||
| db015efba6 | |||
| bd3619137f | |||
| ff22423496 | |||
| 56e826e580 | |||
| e1670934a1 | |||
| 62daa023f5 | |||
| df4f2ebd99 | |||
| bcaeba5ea6 | |||
| 9248a9a84d | |||
| f0a8abb380 | |||
| fb1ab2f8b5 |
38
README.md
38
README.md
@@ -6,9 +6,10 @@ A simple tool to organize your photos, videos, or other files by copying or hard
|
|||||||
|
|
||||||
## TL;DR
|
## TL;DR
|
||||||
|
|
||||||
I use a smartphone and Syncthing to automatically sync my photos to my PC. However, if I clean up my phone's memory, the synced photos on my PC are deleted as well. To avoid this, I needed a solution to back up and organize my photos without manual effort.
|
I use a smartphone and Syncthing to automatically sync my photos to my PC. However, if I clean up my phone's memory, the synced photos on my PC are deleted as well.
|
||||||
|
Dumping everything into one folder wasn't an option — finding anything later would be a nightmare.
|
||||||
|
|
||||||
Dumping everything into one folder wasn't an option — finding anything later would be a nightmare. So, I built this tool in one evening to solve the problem. It has worked flawlessly for me and might help you too. If you encounter any issues, feel free to open a ticket — I'll do my best to assist.
|
To avoid this, I needed a solution to back up and organize my photos without manual effort. So, I built this tool in one evening to solve the problem. It has worked flawlessly for me and might help you too. If you encounter any issues, feel free to open a ticket — I'll do my best to assist.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -28,8 +29,9 @@ sudo cp ${GOPATH}/bin/photocatalog /usr/local/bin/photocatalog
|
|||||||
|
|
||||||
The tool supports the following organization modes:
|
The tool supports the following organization modes:
|
||||||
|
|
||||||
- **copy** — Copies files to the target directory. If the filesystem supports it, uses Copy-on-Write (COW) for efficiency.
|
- **copy** — Copies files to the target directory. If the filesystem supports it, uses Copy-on-Write (COW) for efficiency (via FICLONE ioctl call).
|
||||||
- **hardlink** — Creates hardlinks to the source files, saving disk space. Ideal if the source and target are on the same partition, though file permissions remain linked to the original.
|
- **hardlink** — Creates hardlinks to the source files, saving disk space. Ideal (and usable only) if the source and target are on the same partition,
|
||||||
|
though file permissions remain linked to the original. Fallback to copy on fail.
|
||||||
- **move** — Moves files from the source to the target directory.
|
- **move** — Moves files from the source to the target directory.
|
||||||
- **symlink** — Creates symbolic links at the target pointing to the source files.
|
- **symlink** — Creates symbolic links at the target pointing to the source files.
|
||||||
|
|
||||||
@@ -44,6 +46,34 @@ Currently, the timestamp format is not customizable. Let me know if support for
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
```
|
||||||
|
-dir-mode string
|
||||||
|
Mode bits for directories can be created while syncing (default "0777")
|
||||||
|
-file-mode string
|
||||||
|
Mode bits for files created while syncing (not applicable for hardlink mode) (default "0644")
|
||||||
|
-mode string
|
||||||
|
Organazing mode (default "hardlink")
|
||||||
|
-overwrite
|
||||||
|
Overwrite existing files
|
||||||
|
-skip-full-sync
|
||||||
|
Skip full sync at startup
|
||||||
|
-source string
|
||||||
|
Source directory
|
||||||
|
-target string
|
||||||
|
Target directory
|
||||||
|
-watch
|
||||||
|
Watch for changes in the source directory (default true)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
`-skip-full-sync` and `-watch` are not compatible.
|
||||||
|
|
||||||
|
`-source` and `-target` are required.
|
||||||
|
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
### One-Time Run
|
### One-Time Run
|
||||||
|
|
||||||
#### Copy Files
|
#### Copy Files
|
||||||
|
|||||||
@@ -43,24 +43,24 @@ func (a *Application) Start(ctx context.Context, wg *sync.WaitGroup) error {
|
|||||||
WithDirMode(os.FileMode(a.config.DirMode)).
|
WithDirMode(os.FileMode(a.config.DirMode)).
|
||||||
WithFileMode(os.FileMode(a.config.FileMode)).
|
WithFileMode(os.FileMode(a.config.FileMode)).
|
||||||
WithErrLogger(func(err error) {
|
WithErrLogger(func(err error) {
|
||||||
log.Println(err)
|
log.Println("ERROR:", err.Error())
|
||||||
})
|
})
|
||||||
|
|
||||||
if a.config.Overwrite {
|
if a.config.Overwrite {
|
||||||
org = org.WithOverwrite()
|
org = org.WithOverwrite()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !a.config.SkipFullSync {
|
|
||||||
if err := org.FullSync(ctx); err != nil {
|
|
||||||
return fmt.Errorf("full sync: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.config.Watch {
|
if a.config.Watch {
|
||||||
if err := org.Watch(ctx, wg); err != nil {
|
if err := org.Watch(ctx, wg); err != nil {
|
||||||
return fmt.Errorf("initialize watch: %w", err)
|
return fmt.Errorf("initialize watch: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !a.config.SkipFullSync {
|
||||||
|
if err := org.FullSync(ctx); err != nil {
|
||||||
|
return fmt.Errorf("full sync: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ const (
|
|||||||
ModeMove Mode = "move"
|
ModeMove Mode = "move"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var SupportedModes = []Mode{ModeHardlink, ModeSymlink, ModeMove, ModeCopy}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
SourceDir string
|
SourceDir string
|
||||||
TargetDir string
|
TargetDir string
|
||||||
@@ -34,8 +36,8 @@ 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, ModeCopy}, c.Mode) {
|
if !slices.Contains(SupportedModes, c.Mode) {
|
||||||
return fmt.Errorf("invalid mode %s", c.Mode)
|
return fmt.Errorf("invalid mode %s, supported modes: %s", c.Mode, SupportedModes)
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.SkipFullSync && !c.Watch {
|
if c.SkipFullSync && !c.Watch {
|
||||||
|
|||||||
26
flake.lock
generated
Normal file
26
flake.lock
generated
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1736061677,
|
||||||
|
"narHash": "sha256-DjkQPnkAfd7eB522PwnkGhOMuT9QVCZspDpJJYyOj60=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "cbd8ec4de4469333c82ff40d057350c30e9f7d36",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixpkgs",
|
||||||
|
"ref": "nixos-24.11",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
129
flake.nix
Normal file
129
flake.nix
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
{
|
||||||
|
description = "Photo/video organization tool";
|
||||||
|
|
||||||
|
inputs.nixpkgs.url = "nixpkgs/nixos-24.11";
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs }:
|
||||||
|
let
|
||||||
|
lastModifiedDate = self.lastModifiedDate or self.lastModified or "19700101";
|
||||||
|
version = "2.0.0";
|
||||||
|
supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ];
|
||||||
|
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
|
||||||
|
nixpkgsFor = forAllSystems (system: import nixpkgs { inherit system; });
|
||||||
|
in
|
||||||
|
{
|
||||||
|
packages = forAllSystems (system:
|
||||||
|
let
|
||||||
|
pkgs = nixpkgsFor.${system};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
photocatalog = pkgs.buildGoModule {
|
||||||
|
pname = "photocatalog";
|
||||||
|
inherit version;
|
||||||
|
src = ./.;
|
||||||
|
vendorHash = "sha256-dj11SRRoB8ZbkcQs75HPI0DpW4c5jzY0N8MD1wKpw+4=";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
nixosModules.photocatalog = { config, lib, pkgs, ... }:
|
||||||
|
with lib;
|
||||||
|
{
|
||||||
|
options.photocatalog = {
|
||||||
|
enable = lib.mkEnableOption "Enable photocatalog";
|
||||||
|
|
||||||
|
syncs = mkOption {
|
||||||
|
default = {};
|
||||||
|
description = ''
|
||||||
|
Organization paths with its own params.
|
||||||
|
'';
|
||||||
|
example = {
|
||||||
|
|
||||||
|
};
|
||||||
|
type = types.attrsOf (types.submodule ({ name, ... }: {
|
||||||
|
# freeformType = settingsFormat.type;
|
||||||
|
options = {
|
||||||
|
source = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = name;
|
||||||
|
description = ''
|
||||||
|
Source folder path.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
target = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = ''
|
||||||
|
Target folder path.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
overwrite = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Overwrite files, existing in target.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
watch = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Watch for new files in source path.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
skipFullSync = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Do not make full sync.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
mode = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "hardlink";
|
||||||
|
description = ''
|
||||||
|
Organization mode, one of [ hardlink symlink move copy ].
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf config.photocatalog.enable {
|
||||||
|
environment.systemPackages = [ self.packages.${pkgs.system}.photocatalog ];
|
||||||
|
systemd.user.services = lib.mapAttrs' (name: sync: nameValuePair
|
||||||
|
("photocatalog${lib.replaceStrings ["/"] ["-"] sync.source}")
|
||||||
|
{
|
||||||
|
after = [ "local-fs.target" ];
|
||||||
|
path = [
|
||||||
|
self.packages.${pkgs.system}.photocatalog
|
||||||
|
];
|
||||||
|
wantedBy = [
|
||||||
|
"default.target"
|
||||||
|
];
|
||||||
|
preStart = if !sync.skipFullSync then ''
|
||||||
|
mkdir -p ${sync.target}
|
||||||
|
photocatalog -source ${sync.source} -target ${sync.target} -mode ${sync.mode}
|
||||||
|
'' else null;
|
||||||
|
script = "photocatalog -source ${sync.source} -target ${sync.target} -skip-full-sync -watch -mode ${sync.mode}";
|
||||||
|
serviceConfig = {
|
||||||
|
Type="simple";
|
||||||
|
Restart="no";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
) config.photocatalog.syncs;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
devShells = forAllSystems (system:
|
||||||
|
let
|
||||||
|
pkgs = nixpkgsFor.${system};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
default = pkgs.mkShell {
|
||||||
|
buildInputs = with pkgs; [ go gopls gotools go-tools ];
|
||||||
|
};
|
||||||
|
});
|
||||||
|
defaultPackage = forAllSystems (system: self.packages.${system}.photocatalog);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -7,5 +7,5 @@ WantedBy=default.target
|
|||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
EnvironmentFile=/home/%u/.config/photocatalog
|
EnvironmentFile=/home/%u/.config/photocatalog
|
||||||
ExecStart=photocatalog -mode $MODE -target $TARGET -monitor $MONITOR -update_mtime $UPDATECTIME
|
ExecStart=photocatalog -mode $MODE -target $TARGET -watch -source $MONITOR -skip-full-sync
|
||||||
ExecStartPre=photocatalog -mode $MODE -target $TARGET ${MONITOR}
|
ExecStartPre=photocatalog -mode $MODE -target $TARGET -source ${MONITOR}
|
||||||
|
|||||||
@@ -1,21 +1,38 @@
|
|||||||
package modes
|
package modes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"sync/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var hardLinkNotSupported = atomic.Bool{}
|
||||||
|
|
||||||
type HardLink struct {
|
type HardLink struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h HardLink) PlaceIt(sourcePath, targetPath string, mode os.FileMode) error {
|
func (h HardLink) PlaceIt(sourcePath, targetPath string, mode os.FileMode) error {
|
||||||
|
if hardLinkNotSupported.Load() {
|
||||||
|
return h.fallBack(sourcePath, targetPath, mode)
|
||||||
|
}
|
||||||
|
|
||||||
if err := os.Link(sourcePath, targetPath); err != nil {
|
if err := os.Link(sourcePath, targetPath); err != nil {
|
||||||
if os.IsExist(err) {
|
if os.IsExist(err) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("create hard link: %w", err)
|
log.Println("Create hardlink failed:", err.Error())
|
||||||
|
hardLinkNotSupported.Store(true)
|
||||||
|
|
||||||
|
return h.fallBack(sourcePath, targetPath, mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h HardLink) fallBack(sourcePath string, targetPath string, mode os.FileMode) error {
|
||||||
|
if copyErr := (Copy{}).PlaceIt(sourcePath, targetPath, mode); copyErr != nil {
|
||||||
|
return copyErr
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultDirMode = 0o777
|
defaultDirMode = 0o774
|
||||||
defaultFileMode = 0o644
|
defaultFileMode = 0o644
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -130,6 +131,8 @@ func (o *Organizer) Watch(ctx context.Context, wg *sync.WaitGroup) error {
|
|||||||
if err := watcher.Close(); err != nil {
|
if err := watcher.Close(); err != nil {
|
||||||
o.logErr(fmt.Errorf("close watcher: %w", err))
|
o.logErr(fmt.Errorf("close watcher: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
syscall.Sync()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@@ -149,15 +152,17 @@ func (o *Organizer) Watch(ctx context.Context, wg *sync.WaitGroup) error {
|
|||||||
// Add new directories to the watcher.
|
// Add new directories to the watcher.
|
||||||
if stat.IsDir() {
|
if stat.IsDir() {
|
||||||
if err := watcher.Add(event.Name); err != nil {
|
if err := watcher.Add(event.Name); err != nil {
|
||||||
o.logErr(fmt.Errorf("watch dir: %w", err))
|
o.logErr(fmt.Errorf("add the directory %s to watcher: %w", event.Name, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := o.processFile(event.Name); err != nil {
|
go func() {
|
||||||
o.logErr(fmt.Errorf("process file %s: %w", event.Name, err))
|
if err := o.processFile(event.Name); err != nil {
|
||||||
}
|
o.logErr(fmt.Errorf("process file %s: %w", event.Name, err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
@@ -180,7 +185,9 @@ func (o *Organizer) FullSync(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := o.processFile(path); err != nil {
|
if err := o.processFile(path); err != nil {
|
||||||
return err
|
log.Printf("Process file `%s` failed: %s", path, err.Error())
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -203,7 +210,7 @@ func (o *Organizer) getMetaForPath(fp string) (metadata.Metadata, error) {
|
|||||||
|
|
||||||
meta, err := o.getMetadata(fp, file)
|
meta, err := o.getMetadata(fp, file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return metadata.Metadata{}, fmt.Errorf("get metadata: %w", err)
|
return metadata.Metadata{}, fmt.Errorf("get metadatas: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return meta, nil
|
return meta, nil
|
||||||
@@ -246,7 +253,7 @@ func (o *Organizer) processFile(sourcePath string) error {
|
|||||||
return fmt.Errorf("build target path: %w", err)
|
return fmt.Errorf("build target path: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if pathExists(targetPath) && !o.overwrite {
|
if o.pathExists(targetPath) && !o.overwrite {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,7 +288,7 @@ func (o *Organizer) BuildTargetPath(sourcePath string, meta metadata.Metadata) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o *Organizer) ensureTargetPath(targetPath string) error {
|
func (o *Organizer) ensureTargetPath(targetPath string) error {
|
||||||
if pathExists(targetPath) {
|
if o.pathExists(targetPath) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,13 +310,15 @@ func (o *Organizer) ensureTargetPath(targetPath string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func pathExists(path string) bool {
|
func (o *Organizer) pathExists(path string) bool {
|
||||||
_, err := os.Stat(path)
|
_, err := os.Stat(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
o.logErr(fmt.Errorf("pathExists stat %s: %w", path, err))
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
65
main.go
65
main.go
@@ -3,8 +3,10 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
@@ -33,36 +35,61 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func loadCfg() application.Config {
|
func loadCfg() application.Config {
|
||||||
cfg := application.Config{}
|
cfg := application.Config{
|
||||||
|
DirMode: 0774,
|
||||||
|
FileMode: 0644,
|
||||||
|
}
|
||||||
|
|
||||||
flag.StringVar(&cfg.SourceDir, "source", "", "Source directory")
|
flag.StringVar(&cfg.SourceDir, "source", "", "Source directory")
|
||||||
flag.StringVar(&cfg.TargetDir, "target", "", "Target directory")
|
flag.StringVar(&cfg.TargetDir, "target", "", "Target directory")
|
||||||
flag.BoolVar(&cfg.Overwrite, "overwrite", false, "Overwrite existing files")
|
flag.BoolVar(&cfg.Overwrite, "overwrite", false, "Overwrite existing files")
|
||||||
flag.BoolVar(&cfg.Watch, "watch", true, "Watch for changes in the source directory")
|
flag.BoolVar(&cfg.Watch, "watch", false, "Watch for changes in the source directory")
|
||||||
|
flag.BoolVar(&cfg.Watch, "monitor", false, "Watch for changes in the source directory") // Legacy option
|
||||||
flag.BoolVar(&cfg.SkipFullSync, "skip-full-sync", false, "Skip full sync at startup")
|
flag.BoolVar(&cfg.SkipFullSync, "skip-full-sync", false, "Skip full sync at startup")
|
||||||
|
|
||||||
var dirMode string
|
flag.Func("dir-mode", "Mode bits for directories can be created while syncing", func(s string) error {
|
||||||
var fileMode string
|
var err error
|
||||||
flag.StringVar(&dirMode, "dir-mode", "0777", "Mode bits for directories can be created while syncing")
|
|
||||||
flag.StringVar(&fileMode, "file-mode", "0644", "Mode bits for files created while syncing (not applicable for hardlink mode)")
|
|
||||||
|
|
||||||
var mode string
|
cfg.DirMode, err = strconv.ParseUint(s, 8, 32)
|
||||||
flag.StringVar(&mode, "mode", "hardlink", "Mode")
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
flag.Func("file-mode", "Mode bits for files created while syncing (not applicable for hardlink mode)", func(s string) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
cfg.FileMode, err = strconv.ParseUint(s, 8, 32)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
flag.Func("mode", "Organizing mode", func(s string) error {
|
||||||
|
if s == "" {
|
||||||
|
cfg.Mode = application.ModeHardlink
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.Mode = application.Mode(s)
|
||||||
|
|
||||||
|
if !slices.Contains(application.SupportedModes, cfg.Mode) {
|
||||||
|
return fmt.Errorf("invalid mode, supported modes: %s", application.SupportedModes)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
cfg.Mode = application.Mode(mode)
|
// Legacy fallback
|
||||||
|
if cfg.SourceDir == "" {
|
||||||
|
log.Println("Source directory not specified. May be using old systemd unit file.")
|
||||||
|
|
||||||
var err error
|
cfg.SourceDir = flag.Arg(0)
|
||||||
|
|
||||||
cfg.DirMode, err = strconv.ParseUint(dirMode, 8, 32)
|
|
||||||
if err != nil {
|
|
||||||
cfg.DirMode = 0o777
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg.FileMode, err = strconv.ParseUint(fileMode, 8, 32)
|
|
||||||
if err != nil {
|
|
||||||
cfg.DirMode = 0o644
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return cfg
|
return cfg
|
||||||
|
|||||||
Reference in New Issue
Block a user