feat(playlists): make YouTube-mirrored playlists editable + syncable
Mirrors (source='youtube') were read-only; per the original two-way design they can now be edited locally (add/remove/reorder/rename) and pushed back. The read-sync skips a dirty mirror so it won't clobber unpushed edits; a successful push clears dirty and lets the mirror refresh. _mark_dirty now covers any YouTube-linked list (exported local or mirror), and rename marks dirty too. push/delete no longer reject mirrors. Push reconciles the title as well (cheap playlists.list compare, then playlists.update only if changed) so a local rename doesn't revert on the next pull. Frontend: editable = not-built-in (was also excluding mirrors); rows/reorder enabled for all owned lists; AddToPlaylist popover lists mirrors too (with a YT marker); the origin chip now reads 'edits sync back'. Trilingual.
This commit is contained in:
parent
7a7a373983
commit
2b072a10de
8 changed files with 58 additions and 26 deletions
|
|
@ -108,6 +108,10 @@ def sync_user_playlists(db: Session, user: User) -> dict:
|
|||
if not ytid or ytid in owned_yt_ids:
|
||||
continue
|
||||
pl = existing.get(ytid)
|
||||
if pl is not None and pl.dirty:
|
||||
# Local edits not yet pushed: don't clobber them with YouTube's state.
|
||||
# A successful 'Sync to YouTube' clears dirty and lets the mirror refresh.
|
||||
continue
|
||||
if pl is None:
|
||||
pl = Playlist(
|
||||
user_id=user.id,
|
||||
|
|
@ -184,11 +188,14 @@ def plan_push(db: Session, user: User, pl: Playlist) -> dict:
|
|||
"to_insert": len(desired),
|
||||
"to_delete": 0,
|
||||
"to_reorder": 0,
|
||||
"rename": False,
|
||||
"yt_extra": 0,
|
||||
"units_estimate": ops * WRITE_UNIT_COST,
|
||||
}
|
||||
with YouTubeClient(db, user) as yt:
|
||||
current = list(yt.iter_playlist_items_with_ids(pl.yt_playlist_id))
|
||||
snippet = yt.get_playlist_snippet(pl.yt_playlist_id)
|
||||
rename = bool(snippet) and (snippet.get("title") or "") != pl.name
|
||||
cur_vids = [it["video_id"] for it in current]
|
||||
cur_set = set(cur_vids)
|
||||
desired_set = set(desired)
|
||||
|
|
@ -198,12 +205,13 @@ def plan_push(db: Session, user: User, pl: Playlist) -> dict:
|
|||
# appended; the reorder pass then moves items into the desired order.
|
||||
after_membership = [v for v in cur_vids if v in desired_set] + to_insert
|
||||
reorders = _reorder_moves(after_membership, desired)
|
||||
ops = len(to_insert) + len(to_delete) + reorders
|
||||
ops = len(to_insert) + len(to_delete) + reorders + (1 if rename else 0)
|
||||
return {
|
||||
"action": "update",
|
||||
"to_insert": len(to_insert),
|
||||
"to_delete": len(to_delete),
|
||||
"to_reorder": reorders,
|
||||
"rename": rename,
|
||||
"yt_extra": len(to_delete),
|
||||
"units_estimate": ops * WRITE_UNIT_COST,
|
||||
}
|
||||
|
|
@ -240,6 +248,12 @@ def push_playlist(db: Session, user: User, pl: Playlist) -> dict:
|
|||
}
|
||||
|
||||
# Existing link: diff against the live YouTube state.
|
||||
snippet = yt.get_playlist_snippet(pl.yt_playlist_id)
|
||||
if snippet is not None and (snippet.get("title") or "") != pl.name:
|
||||
try:
|
||||
yt.update_playlist_title(pl.yt_playlist_id, pl.name)
|
||||
except YouTubeError:
|
||||
failures.append("title")
|
||||
current = list(yt.iter_playlist_items_with_ids(pl.yt_playlist_id))
|
||||
item_id_by_vid = {it["video_id"]: it["item_id"] for it in current}
|
||||
cur_vids = [it["video_id"] for it in current]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue