aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPřemysl Eric Janouch <p@janouch.name>2023-12-21 04:48:56 +0100
committerPřemysl Eric Janouch <p@janouch.name>2023-12-21 04:48:56 +0100
commit2011f2b198871b6acb4bbc50012b8bd6801b6a7f (patch)
tree99b00f60e59cf98ae8d62f2b9af94ef0379103cd
parent868e7ec78a89558af55ac0aa4125ab5ec631f473 (diff)
downloadgallery-2011f2b198871b6acb4bbc50012b8bd6801b6a7f.tar.gz
gallery-2011f2b198871b6acb4bbc50012b8bd6801b6a7f.tar.xz
gallery-2011f2b198871b6acb4bbc50012b8bd6801b6a7f.zip
WIP: FS to DB sync
-rw-r--r--main.go79
1 files changed, 57 insertions, 22 deletions
diff --git a/main.go b/main.go
index 8a3de6d..897ae1e 100644
--- a/main.go
+++ b/main.go
@@ -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.
//