diff --git a/server/scan.go b/server/scan.go new file mode 100644 index 0000000..b6837d7 --- /dev/null +++ b/server/scan.go @@ -0,0 +1,167 @@ +package server + +import ( + "bytes" + "context" + "errors" + "fmt" + "log/slog" + "net/http" + "os" + "path/filepath" + + "git.lastassault.de/speatzle/morffix/constants" + "github.com/jackc/pgx/v5" +) + +func handleScan(w http.ResponseWriter, r *http.Request) { + if r.Method == "GET" { + scanStatus(w, r) + return + } + + id := r.PathValue("id") + if id == "" { + http.Error(w, "No ID Set", http.StatusBadRequest) + return + } + + var name string + var path string + var enabled bool + err := db.QueryRow(r.Context(), "SELECT name, path, enable FROM libraries WHERE id = $1", id).Scan(&name, &path, &enabled) + if err != nil { + slog.ErrorContext(r.Context(), "Get Library", "err", err) + http.Error(w, "Error Get Library: "+err.Error(), http.StatusInternalServerError) + return + } + + scanCtx := context.Background() + go scan(scanCtx, id) + + message := "Scan Started" + + buf := bytes.Buffer{} + err = templates.ExecuteTemplate(&buf, constants.MESSAGE_TEMPLATE_NAME, message) + if err != nil { + slog.ErrorContext(r.Context(), "Executing Library Template", "err", err) + http.Error(w, "Error Executing Template: "+err.Error(), http.StatusInternalServerError) + return + } + + _, err = w.Write(buf.Bytes()) + if err != nil { + slog.ErrorContext(r.Context(), "Writing http Response", "err", err) + } +} + +func scanStatus(w http.ResponseWriter, r *http.Request) { + message := "TODO" + + buf := bytes.Buffer{} + err := templates.ExecuteTemplate(&buf, constants.MESSAGE_TEMPLATE_NAME, message) + if err != nil { + slog.ErrorContext(r.Context(), "Executing Library Template", "err", err) + http.Error(w, "Error Executing Template: "+err.Error(), http.StatusInternalServerError) + return + } + + _, err = w.Write(buf.Bytes()) + if err != nil { + slog.ErrorContext(r.Context(), "Writing http Response", "err", err) + } +} + +func scan(ctx context.Context, id string) { + slog.InfoContext(ctx, "Starting Scan", "id", id) + + var name string + var lpath string + var enabled bool + err := db.QueryRow(ctx, "SELECT name, path, enable FROM libraries WHERE id = $1", id).Scan(&name, &lpath, &enabled) + if err != nil { + slog.ErrorContext(ctx, "Get Library", "err", err) + return + } + + if !enabled { + slog.ErrorContext(ctx, "Scan Aborted, Library not Enabled", "id", id) + return + } + + dirInfo, err := os.Stat(lpath) + if err != nil { + slog.ErrorContext(ctx, "Stating Library Path", "id", id, "path", lpath, "err", err) + return + } + + if !dirInfo.IsDir() { + slog.ErrorContext(ctx, "Library Path is not a Folder", "id", id, "path", lpath) + return + } + + tx, err := db.Begin(ctx) + if err != nil { + slog.ErrorContext(ctx, "Begin Transaction", "err", err) + return + } + defer tx.Rollback(ctx) + + slog.InfoContext(ctx, "Checking Files...", "id", id, "path", lpath) + + // Mark all Files as Missing + _, err = tx.Exec(ctx, "UPDATE files SET missing = true where library_id = $1", id) + if err != nil { + slog.ErrorContext(ctx, "Setting Missing Status", "err", err) + return + } + + err = filepath.Walk(lpath, + func(path string, info os.FileInfo, err error) error { + if errors.Is(err, os.ErrPermission) { + slog.WarnContext(ctx, "Permission Denied While Scanning File", "path", path) + return nil + } else if err != nil { + return err + } + + if info.IsDir() { + // We don't care about folders + return nil + } + slog.InfoContext(ctx, "Scanning File", "path", path, "size", info.Size()) + + var fileID int + err = tx.QueryRow(ctx, "SELECT id FROM files WHERE library_id = $1 AND path = $2", id, path).Scan(&fileID) + if errors.Is(err, pgx.ErrNoRows) { + // File Does not Exist Yet + + // TODO check file extension and exclude + slog.InfoContext(ctx, "File is New", "path", path) + _, err = tx.Exec(ctx, "INSERT INTO files (library_id, path, size, missing) VALUES ($1, $2, $3, $4)", id, path, info.Size(), false) + if err != nil { + return fmt.Errorf("Creating New File: %w", err) + } + return nil + } else if err != nil { + return fmt.Errorf("Getting File: %w", err) + } + // File Exists so update Size and missing Status + _, err = tx.Exec(ctx, "UPDATE files SET size = $1, missing = $2 WHERE id = $3", info.Size(), false, fileID) + if err != nil { + return fmt.Errorf("Creating New File: %w", err) + } + return nil + }) + if err != nil { + slog.ErrorContext(ctx, "Error Walking Library", "err", err) + return + } + + err = tx.Commit(ctx) + if err != nil { + slog.ErrorContext(ctx, "Error Committing Changes", "err", err) + return + } + slog.InfoContext(ctx, "Scan Done", "id", id) +} diff --git a/server/server.go b/server/server.go index 7e4f46a..03e0ac7 100644 --- a/server/server.go +++ b/server/server.go @@ -74,6 +74,7 @@ func Start(_conf config.Config, tmplFS embed.FS, staticFS embed.FS, migrationsFS mux := http.NewServeMux() mux.HandleFunc("/worker", handleWorkerWebsocket) mux.Handle("/static/", fs) + mux.HandleFunc("/scan/{id}", handleScan) mux.HandleFunc("/libraries/{id}", handleLibrary) mux.HandleFunc("/libraries", handleLibraries) mux.HandleFunc("/", handleIndex) diff --git a/tmpl/message.tmpl b/tmpl/message.tmpl new file mode 100644 index 0000000..d43824f --- /dev/null +++ b/tmpl/message.tmpl @@ -0,0 +1,3 @@ +{{template "head"}} +

{{.}}

+{{template "tail"}} \ No newline at end of file