package main import ( "net/http" "os" "path/filepath" "strings" ) // handleSettings persists the listing view/sort/order to the folder's // .page-settings file. Values are validated against the allowed sets (unknown // values fall back to defaults). Triggered by POST /{path}?settings. func (h *handler) handleSettings(w http.ResponseWriter, r *http.Request, urlPath, fsPath string) { if err := r.ParseForm(); err != nil { http.Error(w, "bad request", http.StatusBadRequest) return } view := validateView(r.FormValue("view")) sortKey := validateSort(r.FormValue("sort")) order := validateOrder(r.FormValue("order")) if err := writePageSettings(fsPath, view, sortKey, order); err != nil { http.Error(w, "write failed: "+err.Error(), http.StatusInternalServerError) return } http.Redirect(w, r, urlPath, http.StatusSeeOther) } // writePageSettings performs a read-modify-write of /.page-settings, // updating the view/sort/order lines while preserving every other line // (other keys, comments, blank lines, ordering) verbatim. Missing keys are // appended. The write is atomic (temp file + rename). func writePageSettings(dir, view, sortKey, order string) error { if err := os.MkdirAll(dir, 0755); err != nil { return err } p := filepath.Join(dir, ".page-settings") existing, err := os.ReadFile(p) if err != nil && !os.IsNotExist(err) { return err } updated := updateSettingsLines(existing, view, sortKey, order) return writeFileAtomic(p, updated, 0644) } // updateSettingsLines rewrites the view/sort/order lines in existing while // leaving all other lines untouched. Every occurrence of a known key is // updated (so the reader's last-wins parse stays consistent); keys absent from // the file are appended in a stable order. The result always ends in a newline. func updateSettingsLines(existing []byte, view, sortKey, order string) []byte { targets := map[string]string{"view": view, "sort": sortKey, "order": order} appendOrder := []string{"view", "sort", "order"} seen := map[string]bool{} var lines []string if len(existing) > 0 { s := string(existing) s = strings.TrimSuffix(s, "\n") lines = strings.Split(s, "\n") } for i, line := range lines { trimmed := strings.TrimSpace(line) if trimmed == "" || strings.HasPrefix(trimmed, "#") { continue } eq := strings.IndexByte(line, '=') if eq < 0 { continue } key := strings.TrimSpace(line[:eq]) if val, ok := targets[key]; ok { lines[i] = key + " = " + val seen[key] = true } } for _, k := range appendOrder { if !seen[k] { lines = append(lines, k+" = "+targets[k]) } } out := strings.Join(lines, "\n") if out != "" { out += "\n" } return []byte(out) }