diff options
-rw-r--r-- | main.go | 79 |
1 files changed, 57 insertions, 22 deletions
@@ -134,6 +134,11 @@ func (pb *progressBar) Stop() { } func (pb *progressBar) update() { + if pb.target < 0 { + fmt.Printf("\r%d/?", pb.current) + return + } + var fraction int if pb.target != 0 { fraction = int(float32(pb.current) / float32(pb.target) * 100) @@ -806,6 +811,7 @@ type syncContext struct { ctx context.Context tx *sql.Tx info chan syncFileInfo + pb *progressBar } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -915,17 +921,12 @@ func syncDequeue(c *syncContext) error { } } -// TODO: [[B]]: A function to collect references. -// -// - This is almost always combined with [[A1]] or [[A2]], -// so perhaps combine these. +// TODO: Implement. // // - When collecting node subtrees, we need to delete bottom-up // because of foreign key constraints, // so maybe in reverse order of recursive CTE results. // -// - Orphans will keep their GD/thumbs file, as evidence. -// // - Sadly, this can't be done with a DB trigger. (What and why?) // // - One of the inputs needs to be the FS path, for the orphan table. @@ -933,20 +934,51 @@ func syncDequeue(c *syncContext) error { // syncDispose creates orphan records for the entire subtree given by nodeID // as appropriate, then deletes all nodes within the subtree. The subtree root // node is not deleted if "keepNode" is true. +// +// Orphans keep their thumbnail files, as evidence. func syncDispose(c *syncContext, nodeID int64, keepNode bool) error { return nil } +func syncImage(c *syncContext, info syncFileInfo) error { + if _, err := c.tx.Exec(`INSERT INTO image(sha1, width, height) + VALUES (?, ?, ?) ON CONFLICT(sha1) DO NOTHING`, + info.sha1, info.width, info.height); err != nil { + return err + } + + // Fast path: it may already there, and not be a dead symlink. + path := imagePath(info.sha1) + if _, err := os.Stat(path); err == nil { + return nil + } + + dirname, _ := filepath.Split(path) + if err := os.MkdirAll(dirname, 0755); err != nil { + return err + } + + for { + err := os.Symlink(info.fsPath, path) + if !errors.Is(err, fs.ErrExist) { + return err + } + + // Try to remove anything standing in the way, and try again. + if err = os.Remove(path); err != nil { + return err + } + } +} + func syncPostProcess(c *syncContext, info syncFileInfo) error { - // TODO: - // - When replacing a file, which needn't become orphaned, - // we could offer copying all tags, but this needs another table - // to track it (if it's equivalent enough, the dhash will stay the same, - // so user can resolve this through the duplicates feature). - // - When creating or updating a node, - // try inserting an image record first as well. - // And test that we have a symlink, and that it works. - // (Just do os.Stat() on it, which fails on both dead and missing links.) + defer c.pb.Step() + + // TODO: When replacing an image node (whether it has or doesn't have + // other links to keep it alive), we could offer copying all tags, + // though this needs another table to track it. + // (If it's equivalent enough, the dhash will stay the same, + // so user can resolve this through the duplicates feature.) switch { case info.err != nil: // * → error @@ -963,8 +995,9 @@ func syncPostProcess(c *syncContext, info syncFileInfo) error { case info.dbID == 0: // 0 → F - // TODO: Add an image entry. - + if err := syncImage(c, info); err != nil { + return err + } if _, err := c.tx.Exec(`INSERT INTO node(parent, name, mtime, sha1) VALUES (?, ?, ?, ?)`, info.dbParent, info.dbName, info.fsMtime, info.sha1); err != nil { @@ -978,11 +1011,10 @@ func syncPostProcess(c *syncContext, info syncFileInfo) error { return err } - // TODO: Ideally, do nothing if the hash doesn't change. - // But that will be a rare situation. - - // TODO: Add an image entry. - + // Even if the hash didn't change, we may fix any broken symlinks. + if err := syncImage(c, info); err != nil { + return err + } if _, err := c.tx.Exec(`UPDATE node SET mtime = ?, sha1 = ? WHERE id = ?`, info.fsMtime, info.sha1, info.dbID); err != nil { return err @@ -1098,6 +1130,9 @@ func syncDirectory(c *syncContext, dbParent int64, fsPath string) error { func syncArgument(ctx context.Context, tx *sql.Tx, path string) error { c := syncContext{ctx: ctx, tx: tx} + c.pb = newProgressBar(-1) + defer c.pb.Stop() + // Info tasks take a position in the task semaphore channel. // then fill the info channel. // |