2021-05-02 00:56:10 +02:00
|
|
|
from dataclasses import dataclass
|
|
|
|
from pathlib import PurePath
|
|
|
|
from typing import Set
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class MarkDuplicateException(Exception):
|
|
|
|
"""
|
|
|
|
Tried to mark a file that was already marked.
|
|
|
|
"""
|
|
|
|
|
|
|
|
path: PurePath
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class MarkConflictException(Exception):
|
|
|
|
"""
|
|
|
|
Marking the path would have caused a conflict.
|
|
|
|
|
|
|
|
A conflict can have two reasons: Either the new file has the same path as
|
|
|
|
the parent directory of a known file, or a parent directory of the new file
|
|
|
|
has the same path as a known file. In either case, adding the new file
|
|
|
|
would require a file and a directory to share the same path, which is
|
|
|
|
usually not possible.
|
|
|
|
"""
|
|
|
|
|
|
|
|
path: PurePath
|
|
|
|
collides_with: PurePath
|
|
|
|
|
|
|
|
|
2021-05-06 01:02:40 +02:00
|
|
|
# TODO Use PurePath.is_relative_to when updating to 3.9
|
|
|
|
def is_relative_to(a: PurePath, b: PurePath) -> bool:
|
|
|
|
try:
|
|
|
|
a.relative_to(b)
|
|
|
|
return True
|
|
|
|
except ValueError:
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
2021-05-02 00:56:10 +02:00
|
|
|
class Report:
|
|
|
|
"""
|
|
|
|
A report of a synchronization. Includes all files found by the crawler, as
|
|
|
|
well as the set of changes made to local files.
|
|
|
|
"""
|
|
|
|
|
2021-05-05 18:08:34 +02:00
|
|
|
def __init__(self) -> None:
|
2021-05-02 00:56:10 +02:00
|
|
|
self.known_files: Set[PurePath] = set()
|
|
|
|
|
|
|
|
self.new_files: Set[PurePath] = set()
|
|
|
|
self.changed_files: Set[PurePath] = set()
|
|
|
|
self.deleted_files: Set[PurePath] = set()
|
|
|
|
|
2021-05-05 18:08:34 +02:00
|
|
|
def mark(self, path: PurePath) -> None:
|
2021-05-02 00:56:10 +02:00
|
|
|
"""
|
|
|
|
Mark a previously unknown file as known.
|
|
|
|
|
|
|
|
May throw a MarkDuplicateException or a MarkConflictException. For more
|
|
|
|
detail, see the respective exception's docstring.
|
|
|
|
"""
|
|
|
|
|
|
|
|
for known_path in self.known_files:
|
|
|
|
if path == known_path:
|
|
|
|
raise MarkDuplicateException(path)
|
|
|
|
|
2021-05-06 01:02:40 +02:00
|
|
|
if is_relative_to(path, known_path) or is_relative_to(known_path, path):
|
2021-05-02 00:56:10 +02:00
|
|
|
raise MarkConflictException(path, known_path)
|
|
|
|
|
|
|
|
self.known_files.add(path)
|
|
|
|
|
2021-05-05 18:08:34 +02:00
|
|
|
def marked(self, path: PurePath) -> bool:
|
|
|
|
return path in self.known_files
|
|
|
|
|
|
|
|
def add_file(self, path: PurePath) -> None:
|
2021-05-02 00:56:10 +02:00
|
|
|
"""
|
|
|
|
Unlike mark(), this function accepts any paths.
|
|
|
|
"""
|
|
|
|
|
|
|
|
self.new_files.add(path)
|
|
|
|
|
2021-05-05 18:08:34 +02:00
|
|
|
def change_file(self, path: PurePath) -> None:
|
2021-05-02 00:56:10 +02:00
|
|
|
"""
|
|
|
|
Unlike mark(), this function accepts any paths.
|
|
|
|
"""
|
|
|
|
|
|
|
|
self.changed_files.add(path)
|
|
|
|
|
2021-05-05 18:08:34 +02:00
|
|
|
def delete_file(self, path: PurePath) -> None:
|
2021-05-02 00:56:10 +02:00
|
|
|
"""
|
|
|
|
Unlike mark(), this function accepts any paths.
|
|
|
|
"""
|
|
|
|
|
|
|
|
self.deleted_files.add(path)
|