TIMESTAMP_FMT = '%Y-%m-%d %H:%M:%S.%f'
 LEGAL_EXTENSIONS = {'webm', 'mp4', 'mkv'}
 FILE_FLAGS: dict[FlagName, FlagsInt] = {
-  FlagName('do not sync'): FlagsInt(1 << 62),
-  FlagName('delete'): FlagsInt(-(1 << 63))
+  FlagName('do not sync'): FlagsInt(1 << 62)
 }
 ONE_MILLION = 1000 * 1000
 
             self.last_update = _now_string()
         return super().save(conn)
 
-    @property
-    def deleted(self) -> bool:
-        """Return if 'delete' flag set."""
-        return self.is_flag_set(FlagName('delete'))
-
-    @classmethod
-    def get_one(cls, conn: DbConn, id_: str | Hash) -> Self:
-        """Extend super by .test_deletion."""
-        file = super().get_one(conn, id_)
-        if file.deleted:  # pylint: disable=no-member
-            raise NotFoundException('not showing entry marked as deleted')
-        # NB: mypy recognizes file as VideoFile without below assert and
-        # if-isinstance-else, yet less type-smart pylint only does due to the
-        # latter (also the reason for the disable=no-member above; but wouldn't
-        # suffice here, pylint would still identify function's return falsely);
-        # the assert isn't needed by mypy and not enough for pylint, but is
-        # included just so no future code change would trigger the else result.
-        assert isinstance(file, VideoFile)
-        return file if isinstance(file, VideoFile) else cls(None, Path(''))
-
-    @classmethod
-    def get_all_non_deleted(cls, conn: DbConn) -> list[Self]:
-        """Extend super by excluding deleteds."""
-        return [f for f in super().get_all(conn) if not f.deleted]
-
-    @classmethod
-    def get_all_deleted(cls, conn: DbConn) -> list[Self]:
-        """Get only deleteds."""
-        return [f for f in super().get_all(conn) if f.deleted]
-
     @classmethod
     def get_by_yt_id(cls, conn: DbConn, yt_id: YoutubeId) -> Self:
-        """Return (non-deleted) VideoFile of .yt_id."""
+        """Return VideoFile of .yt_id."""
         rows = conn.exec(f'SELECT * FROM {cls._table_name} WHERE yt_id =',
                          (yt_id,)).fetchall()
         for file in [cls._from_table_row(row) for row in rows]:
-            if not file.deleted:
-                return file
-        raise NotFoundException(
-                f'no undeleted entry for file to Youtube ID {yt_id}')
+            return file
+        raise NotFoundException(f'no entry for file to Youtube ID {yt_id}')
 
     @classmethod
     def get_filtered(cls,
                      needed_tags_seen: TagSet = TagSet(set()),
                      show_absent: bool = False
                      ) -> list[Self]:
-        """Return cls.get_all_non_deleted matching provided filter criteria."""
+        """Return all matching provided filter criteria."""
         return [
-            f for f in cls.get_all_non_deleted(conn)
+            f for f in cls.get_all(conn)
             if (show_absent or f.present)
             and str(filter_path).lower() in str(f.rel_path).lower()
             and (cls.tags_prefilter_needed.are_all_in(f.tags)
     def all_tags_showable(cls, conn) -> TagSet:
         """Show all used tags passing .tags_display_whitelist."""
         tags = TagSet()
-        for file in cls.get_all_non_deleted(conn):
+        for file in cls.get_all(conn):
             tags.add(file.tags.whitelisted(cls.tags_display_whitelist))
         return tags
 
     def unused_tags(self, conn: DbConn) -> TagSet:
         """Return tags used among other VideoFiles, not in self."""
         tags = TagSet()
-        for file in self.get_all_non_deleted(conn):
+        for file in self.get_all(conn):
             tags.add(file.tags.all_not_in(self.tags).whitelisted(
                 self.tags_display_whitelist))
         return tags
         """Return if file exists in filesystem."""
         return self.full_path.exists()
 
-    @property
-    def missing(self) -> bool:
-        """Return if file absent despite absence of 'delete' flag."""
-        return not (self.is_flag_set(FlagName('delete')) or self.present)
-
     @property
     def flags_as_str_list(self) -> list[str]:
         """Return all set flags."""
         """Return if flag of flag_name is set in flags field."""
         return bool(self.flags & FILE_FLAGS[flag_name])
 
-    def ensure_unlinked_if_no_living_owners(self, conn: DbConn) -> None:
-        """If 'delete' flag set and no undeleted owner, unlink."""
-        if self.full_path in [f.full_path
-                              for f in self.get_all_non_deleted(conn)]:
-            return
-        if self.present:
-            self.unlink_locally()
-
     def unlink_locally(self) -> None:
         """Remove actual file from local filesystem."""
         print(f'SYNC: removing from filesystem: {self.rel_path}')
         self.full_path.unlink()
 
+    def purge(self, conn) -> None:
+        """Remove self from database, and in filesystem if no other owners."""
+        if self.present and self.full_path not in [
+                f.full_path for f in self.get_all(conn) if self != f]:
+            self.unlink_locally()
+        print(f'SYNC: purging off DB: {self.digest.b64} ({self.rel_path})')
+        conn.exec(f'DELETE FROM {self._table_name} WHERE digest =',
+                  (self.digest.bytes,))
+
     @classmethod
-    def purge_deleteds(cls, conn: DbConn) -> None:
-        """For all of .is_flag_set("deleted"), remove file _and_ DB entry."""
-        for file in cls.get_all_deleted(conn):
-            if file.present:
-                file.unlink_locally()
-            print(f'SYNC: purging off DB: {file.digest.b64} ({file.rel_path})')
-            conn.exec(f'DELETE FROM {cls._table_name} WHERE digest =',
-                      (file.digest.bytes,))
+    def purge_deleteds(cls, conn: DbConn) -> list[Hash]:
+        """Purge all with .last_update < a respective entry in delete table."""
+        too_early = '2000-01-01 00:00:00.0'
+        del_req_by_timestamp: dict[Hash, DatetimeStr] = {}
+        for del_req in FileDeletionRequest.get_all(conn):
+            if del_req_by_timestamp.get(del_req.digest, too_early
+                                        ) < del_req.last_update:
+                del_req_by_timestamp[del_req.digest] = del_req.last_update
+        deleteds: list[Hash] = []
+        for file in [file for file in cls.get_all(conn)
+                     if del_req_by_timestamp.get(file.digest, too_early
+                                                 ) > file.last_update]:
+            deleteds += [file.digest]
+            file.purge(conn)
+        return deleteds
+
+    def delete(self, conn: DbConn) -> None:
+        """Add/update into deletion request table, then purge locally."""
+        FileDeletionRequest(self.digest, _now_string()).save(conn)
+        self.purge(conn)
+
+
+class FileDeletionRequest(DbData):
+    """Collect file deletion requests in their own table, for syncability."""
+    id_name = 'digest'
+    _table_name = 'file_deletion_requests'
+    _cols = ('digest', 'last_update')
+    digest: Hash
+    last_update: DatetimeStr
+
+    def __init__(self, digest: Hash, last_update: DatetimeStr) -> None:
+        self.digest = digest
+        self.last_update = last_update
+
+    def __str__(self) -> str:
+        return f'{self.digest.b64}:{self.last_update}'
 
 
 class QuotaLog(DbData):
 
     def _sync_db(self):
         with DbConn() as conn:
-            known_paths = [file.rel_path for
-                           file in VideoFile.get_all_non_deleted(conn)]
+            VideoFile.purge_deleteds(conn)
+            known_paths = [file.rel_path for file in VideoFile.get_all(conn)]
             old_cwd = Path.cwd()
             chdir(PATH_DOWNLOADS)
             for path in [p for p in Path('.').iterdir() if p.is_file()]:
                             'present',
                             str(path),
                             VideoFile.get_by_yt_id(conn, yt_id).digest.b64)
-            for file in VideoFile.get_all_deleted(conn):
-                file.ensure_unlinked_if_no_living_owners(conn)
-            self._files = VideoFile.get_all_non_deleted(conn)
+            self._files = VideoFile.get_all(conn)
             chdir(old_cwd)
 
     def last_update_for(self,
 
 from scp import SCPClient  # type: ignore
 # ourselves
 from ytplom.db import DbConn, DbFile, Hash, PATH_DB
-from ytplom.misc import (PATH_TEMP, Config, FlagName, QuotaLog, VideoFile,
-                         YoutubeQuery, YoutubeVideo)
+from ytplom.misc import (PATH_TEMP, Config, FileDeletionRequest, FlagName,
+                         QuotaLog, VideoFile, YoutubeQuery, YoutubeVideo)
 from ytplom.http import PAGE_NAMES
 
 
     """Download remote DB, run sync_(objects|relations), put remote DB back."""
     scp.get(PATH_DB, _PATH_DB_REMOTE)
     with DbConn() as db_local, DbConn(DbFile(_PATH_DB_REMOTE)) as db_remote:
-        for cls in (QuotaLog, YoutubeQuery, YoutubeVideo, VideoFile):
+        for cls in (FileDeletionRequest, QuotaLog, YoutubeQuery, YoutubeVideo,
+                    VideoFile):
             _back_and_forth(_sync_objects, (db_local, db_remote), cls)
         for yt_video_local in YoutubeVideo.get_all(db_local):
             _back_and_forth(_sync_relations, (db_local, db_remote),