Skip to content

file_hashes

file_hashes #

The file_hashes table: leaf hashes remembered by path and stat, on their own connection.

FileHashes #

A HashCache over the file_hashes table, so hashing never waits on the index.

Source code in capturegraph-lib/capturegraph/recipes/scratch/index/file_hashes.py
class FileHashes:
    """A ``HashCache`` over the ``file_hashes`` table, so hashing never waits on the index."""

    def __init__(self, connection: sqlite3.Connection) -> None:
        """Serve the cache over ``connection``, which this object then owns."""
        self._lock = threading.Lock()
        self._connection = connection

    def lookup(self, path: Path, stat: Stat) -> ContentHash | None:
        """The remembered hash of ``path`` at ``stat``, if any."""
        with self._lock:
            row = self._connection.execute(
                "SELECT hash FROM file_hashes WHERE path = ? AND mtime_ns = ? AND size_bytes = ? "
                "AND inode = ? AND is_dir = ?",
                (str(path), stat.mtime_ns, stat.size, stat.inode, int(stat.is_dir)),
            ).fetchone()
        return ContentHash.from_hex(row[0]) if row else None

    def store(self, path: Path, stat: Stat, hash: ContentHash) -> None:
        """Remember that ``path`` at ``stat`` hashes to ``hash``."""
        with self._lock:
            self._connection.execute(
                "INSERT OR REPLACE INTO file_hashes "
                "(path, mtime_ns, size_bytes, inode, is_dir, hash) "
                "VALUES (?, ?, ?, ?, ?, ?)",
                (str(path), stat.mtime_ns, stat.size, stat.inode, int(stat.is_dir), hash.hex),
            )

    def move(self, source: Path, destination: Path) -> None:
        """Re-key every remembered path under ``source`` to live under ``destination``."""
        prefix = f"{source}/"
        with self._lock:
            self._connection.execute(
                "UPDATE OR REPLACE file_hashes SET path = ? || substr(path, ?) "
                "WHERE substr(path, 1, ?) = ?",
                (f"{destination}/", len(prefix) + 1, len(prefix), prefix),
            )

    def forget(self, source: Path) -> None:
        """Drop every remembered path under ``source``."""
        prefix = f"{source}/"
        with self._lock:
            self._connection.execute(
                "DELETE FROM file_hashes WHERE substr(path, 1, ?) = ?", (len(prefix), prefix)
            )

    def close(self) -> None:
        """Close the connection."""
        with self._lock:
            self._connection.close()

__init__(connection) #

Serve the cache over connection, which this object then owns.

Source code in capturegraph-lib/capturegraph/recipes/scratch/index/file_hashes.py
def __init__(self, connection: sqlite3.Connection) -> None:
    """Serve the cache over ``connection``, which this object then owns."""
    self._lock = threading.Lock()
    self._connection = connection

close() #

Close the connection.

Source code in capturegraph-lib/capturegraph/recipes/scratch/index/file_hashes.py
def close(self) -> None:
    """Close the connection."""
    with self._lock:
        self._connection.close()

forget(source) #

Drop every remembered path under source.

Source code in capturegraph-lib/capturegraph/recipes/scratch/index/file_hashes.py
def forget(self, source: Path) -> None:
    """Drop every remembered path under ``source``."""
    prefix = f"{source}/"
    with self._lock:
        self._connection.execute(
            "DELETE FROM file_hashes WHERE substr(path, 1, ?) = ?", (len(prefix), prefix)
        )

lookup(path, stat) #

The remembered hash of path at stat, if any.

Source code in capturegraph-lib/capturegraph/recipes/scratch/index/file_hashes.py
def lookup(self, path: Path, stat: Stat) -> ContentHash | None:
    """The remembered hash of ``path`` at ``stat``, if any."""
    with self._lock:
        row = self._connection.execute(
            "SELECT hash FROM file_hashes WHERE path = ? AND mtime_ns = ? AND size_bytes = ? "
            "AND inode = ? AND is_dir = ?",
            (str(path), stat.mtime_ns, stat.size, stat.inode, int(stat.is_dir)),
        ).fetchone()
    return ContentHash.from_hex(row[0]) if row else None

move(source, destination) #

Re-key every remembered path under source to live under destination.

Source code in capturegraph-lib/capturegraph/recipes/scratch/index/file_hashes.py
def move(self, source: Path, destination: Path) -> None:
    """Re-key every remembered path under ``source`` to live under ``destination``."""
    prefix = f"{source}/"
    with self._lock:
        self._connection.execute(
            "UPDATE OR REPLACE file_hashes SET path = ? || substr(path, ?) "
            "WHERE substr(path, 1, ?) = ?",
            (f"{destination}/", len(prefix) + 1, len(prefix), prefix),
        )

store(path, stat, hash) #

Remember that path at stat hashes to hash.

Source code in capturegraph-lib/capturegraph/recipes/scratch/index/file_hashes.py
def store(self, path: Path, stat: Stat, hash: ContentHash) -> None:
    """Remember that ``path`` at ``stat`` hashes to ``hash``."""
    with self._lock:
        self._connection.execute(
            "INSERT OR REPLACE INTO file_hashes "
            "(path, mtime_ns, size_bytes, inode, is_dir, hash) "
            "VALUES (?, ?, ?, ?, ?, ?)",
            (str(path), stat.mtime_ns, stat.size, stat.inode, int(stat.is_dir), hash.hex),
        )