1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
 2//
 3// SPDX-License-Identifier: AGPL-3.0-or-later
 4
 5package db
 6
 7import (
 8	"path/filepath"
 9	"runtime"
10	"strings"
11
12	"github.com/zeebo/blake3"
13)
14
15// CanonicalizeDir normalizes path according to the repository requirements.
16func CanonicalizeDir(path string) (string, error) {
17	abs, err := filepath.Abs(path)
18	if err != nil {
19		return "", err
20	}
21
22	evaluated, err := filepath.EvalSymlinks(abs)
23	if err != nil {
24		return "", err
25	}
26
27	clean := filepath.Clean(evaluated)
28	slashed := filepath.ToSlash(clean)
29	if runtime.GOOS == "windows" {
30		slashed = strings.ToLower(slashed)
31	}
32	return slashed, nil
33}
34
35// DirHash produces the blake3-256 hash for canonicalPath and renders it in
36// lowercase hex (64 characters).
37func DirHash(canonicalPath string) string {
38	sum := blake3.Sum256([]byte(canonicalPath))
39	return encodeHex(sum[:])
40}
41
42// CanonicalizeAndHash resolves path to its canonical representation and
43// computes its hash.
44func CanonicalizeAndHash(path string) (canonical string, hash string, err error) {
45	canonical, err = CanonicalizeDir(path)
46	if err != nil {
47		return "", "", err
48	}
49	return canonical, DirHash(canonical), nil
50}
51
52// ParentWalk returns canonical parent paths (including the input) ascending to
53// the root. The caller can hash each entry as needed.
54func ParentWalk(canonicalPath string) []string {
55	if canonicalPath == "/" {
56		return []string{canonicalPath}
57	}
58	var parents []string
59	seen := make(map[string]struct{})
60	current := canonicalPath
61	for {
62		if _, exists := seen[current]; exists {
63			break
64		}
65		seen[current] = struct{}{}
66		parents = append(parents, current)
67		next := parentDir(current)
68		if next == current {
69			break
70		}
71		current = next
72	}
73	return parents
74}
75
76func parentDir(path string) string {
77	if path == "/" {
78		return path
79	}
80	idx := strings.LastIndex(path, "/")
81	if idx == -1 {
82		return path
83	}
84	parent := path[:idx]
85	if parent == "" {
86		return "/"
87	}
88	if runtime.GOOS == "windows" {
89		if len(parent) == 2 && parent[1] == ':' {
90			parent += "/"
91		}
92	}
93	return parent
94}