aboutsummaryrefslogtreecommitdiff
path: root/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'main.go')
-rw-r--r--main.go167
1 files changed, 97 insertions, 70 deletions
diff --git a/main.go b/main.go
index 546e3ff..db57896 100644
--- a/main.go
+++ b/main.go
@@ -1545,7 +1545,7 @@ func syncDirectory(c *syncContext, dbParent int64, fsPath string) error {
return nil
}
-func syncRoot(c *syncContext, fsPath string) error {
+func syncRoot(c *syncContext, dbPath []string, fsPath string) error {
// TODO: Support synchronizing individual files.
// This can only be treated as 0 → F, F → F, or D → F, that is,
// a variation on current syncEnqueue(), but dbParent must be nullable.
@@ -1554,8 +1554,7 @@ func syncRoot(c *syncContext, fsPath string) error {
// also because we don't know yet if the argument is a directory).
//
// Synchronizing F → D or * → F are special cases not worth implementing.
- crumbs := decodeWebPath(filepath.ToSlash(fsPath))
- dbParent, err := idForDirectoryPath(c.tx, crumbs, true)
+ dbParent, err := idForDirectoryPath(c.tx, dbPath, true)
if err != nil {
return err
}
@@ -1610,6 +1609,60 @@ func syncRoot(c *syncContext, fsPath string) error {
}
}
+type syncPath struct {
+ db []string // database path, in terms of nodes
+ fs string // normalized filesystem path
+}
+
+// syncResolveRoots normalizes filesystem paths given in command line arguments,
+// and figures out a database path for each. Duplicates are skipped or rejected.
+func syncResolveRoots(args []string, fullpaths bool) (
+ roots []*syncPath, err error) {
+ for i := range args {
+ fs, err := filepath.Abs(filepath.Clean(args[i]))
+ if err != nil {
+ return nil, err
+ }
+
+ roots = append(roots,
+ &syncPath{decodeWebPath(filepath.ToSlash(fs)), fs})
+ }
+
+ if fullpaths {
+ // Filter out duplicates. In this case, they're just duplicated work.
+ slices.SortFunc(roots, func(a, b *syncPath) int {
+ return strings.Compare(a.fs, b.fs)
+ })
+ roots = slices.CompactFunc(roots, func(a, b *syncPath) bool {
+ if a.fs != b.fs && !strings.HasPrefix(b.fs, a.fs+"/") {
+ return false
+ }
+ log.Printf("asking to sync path twice: %s\n", b.fs)
+ return true
+ })
+ } else {
+ // Keep just the basenames.
+ for _, path := range roots {
+ if len(path.db) > 0 {
+ path.db = path.db[len(path.db)-1:]
+ }
+ }
+
+ // Different filesystem paths mapping to the same DB location
+ // are definitely a problem we would like to avoid,
+ // otherwise we don't care.
+ slices.SortFunc(roots, func(a, b *syncPath) int {
+ return slices.Compare(a.db, b.db)
+ })
+ for i := 1; i < len(roots); i++ {
+ if slices.Equal(roots[i-1].db, roots[i].db) {
+ return nil, fmt.Errorf("duplicate root: %v", roots[i].db)
+ }
+ }
+ }
+ return
+}
+
const disposeCTE = `WITH RECURSIVE
root(id, sha1, parent, path) AS (
SELECT id, sha1, parent, name FROM node WHERE id = ?
@@ -1636,11 +1689,48 @@ const disposeCTE = `WITH RECURSIVE
HAVING count = total
)`
-func syncRun(ctx context.Context, tx *sql.Tx, roots []string) error {
+// cmdSync ensures the given (sub)roots are accurately reflected
+// in the database.
+func cmdSync(fs *flag.FlagSet, args []string) error {
+ fullpaths := fs.Bool("fullpaths", false, "don't basename arguments")
+ if err := fs.Parse(args); err != nil {
+ return err
+ }
+ if fs.NArg() < 2 {
+ return errWrongUsage
+ }
+ if err := openDB(fs.Arg(0)); err != nil {
+ return err
+ }
+
+ roots, err := syncResolveRoots(fs.Args()[1:], *fullpaths)
+ if err != nil {
+ return err
+ }
+
+ ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
+ defer stop()
+
+ // In case of a failure during processing, the only retained side effects
+ // on the filesystem tree are:
+ // - Fixing dead symlinks to images.
+ // - Creating symlinks to images that aren't used by anything.
+ tx, err := db.BeginTx(ctx, nil)
+ if err != nil {
+ return err
+ }
+ defer tx.Rollback()
+
+ // Mild hack: upgrade the transaction to a write one straight away,
+ // in order to rule out deadlocks (preventable failure).
+ if _, err := tx.Exec(`END TRANSACTION;
+ BEGIN IMMEDIATE TRANSACTION`); err != nil {
+ return err
+ }
+
c := syncContext{ctx: ctx, tx: tx, pb: newProgressBar(-1)}
defer c.pb.Stop()
- var err error
if c.stmtOrphan, err = c.tx.Prepare(disposeCTE + `
INSERT OR IGNORE INTO orphan(sha1, path)
SELECT sha1, path FROM orphaned`); err != nil {
@@ -1678,74 +1768,11 @@ func syncRun(ctx context.Context, tx *sql.Tx, roots []string) error {
// i.e., it is for the result of the task that syncEnqueue() spawns.
c.info = make(chan syncFileInfo, cap(taskSemaphore)+1)
- for _, path := range roots {
- if err := syncRoot(&c, path); err != nil {
+ for _, root := range roots {
+ if err := syncRoot(&c, root.db, root.fs); err != nil {
return err
}
}
- return nil
-}
-
-// cmdSync ensures the given (sub)roots are accurately reflected
-// in the database.
-func cmdSync(fs *flag.FlagSet, args []string) error {
- fullpaths := fs.Bool("fullpaths", false, "don't basename the arguments")
- if err := fs.Parse(args); err != nil {
- return err
- }
- if fs.NArg() < 2 {
- return errWrongUsage
- }
- if err := openDB(fs.Arg(0)); err != nil {
- return err
- }
-
- ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
- defer stop()
-
- // In case of a failure during processing, the only retained side effects
- // on the filesystem tree are:
- // - Fixing dead symlinks to images.
- // - Creating symlinks to images that aren't used by anything.
- tx, err := db.BeginTx(ctx, nil)
- if err != nil {
- return err
- }
- defer tx.Rollback()
-
- // Mild hack: upgrade the transaction to a write one straight away,
- // in order to rule out deadlocks (preventable failure).
- if _, err := tx.Exec(`END TRANSACTION;
- BEGIN IMMEDIATE TRANSACTION`); err != nil {
- return err
- }
-
- // TODO: Handle sensibly.
- _ = fullpaths
-
- // Normalize arguments.
- // At least for now, turn all roots into absolute paths.
- roots := fs.Args()[1:]
- for i := range roots {
- roots[i], err = filepath.Abs(filepath.Clean(roots[i]))
- if err != nil {
- return err
- }
- }
-
- // Filter out duplicates.
- sort.Strings(roots)
- roots = slices.CompactFunc(roots, func(a, b string) bool {
- if a != b && !strings.HasPrefix(b, a+"/") {
- return false
- }
- log.Printf("asking to sync path twice: %s\n", b)
- return true
- })
-
- if err := syncRun(ctx, tx, roots); err != nil {
- return err
- }
return tx.Commit()
}