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("Add New File to DB: %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("Updating File in DB: %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) }