Refusing to rename Tag or move Channel when files are refreshing.

This commit is contained in:
Roland 2024-08-07 08:36:33 -06:00
parent d8ef7e175e
commit 6fe8a7b704
No known key found for this signature in database
GPG key ID: 1BA620CE6C9A4680
5 changed files with 67 additions and 28 deletions

View file

@ -757,7 +757,7 @@ export function ChannelsPage() {
const headers = [
{key: 'name', text: 'Name', sortBy: 'name', width: 8},
{key: 'tag', text: 'Tag', sortBy: 'tag', width: 2},
{key: 'tag', text: 'Tag', sortBy: 'tag_name', width: 2},
{key: 'video_count', text: 'Videos', sortBy: 'video_count', width: 2},
{key: 'download_frequency', text: 'Download Frequency', sortBy: 'minimum_frequency', width: 2},
{key: 'size', text: 'Size', sortBy: 'size', width: 2},

View file

@ -5,11 +5,12 @@ from typing import List, Dict, Union
from sqlalchemy import or_, func, desc, asc
from sqlalchemy.orm import Session
from wrolpi import flags
from wrolpi.common import run_after, logger, \
get_media_directory, wrol_mode_check
from wrolpi.db import get_db_curs, optional_session, get_db_session
from wrolpi.downloader import save_downloads_config, download_manager
from wrolpi.errors import UnknownDirectory, APIError, ValidationError
from wrolpi.errors import UnknownDirectory, APIError, ValidationError, RefreshConflict
from .. import schema
from ..common import check_for_channel_conflicts
from ..errors import UnknownChannel, ChannelDirectoryConflict
@ -257,6 +258,9 @@ async def tag_channel(tag_name: str, directory: pathlib.Path | None, channel_id:
Move the Channel to the new directory, if provided."""
from wrolpi.tags import Tag
if directory and flags.refreshing.is_set():
raise RefreshConflict('Refusing to move channel while file refresh is in progress')
channel = Channel.find_by_id(channel_id, session=session)
if tag_name:

View file

@ -169,3 +169,9 @@ class InvalidDirectory(APIError):
code = 'INVALID_DIRECTORY'
message = 'Directory is not valid or impossible'
status_code = HTTPStatus.BAD_REQUEST
class RefreshConflict(APIError):
code = 'REFRESH_CONFLICT'
message = 'Not possible during file refresh'
status_code = HTTPStatus.CONFLICT

View file

@ -7,12 +7,12 @@ from sqlalchemy import Column, Integer, String, ForeignKey, BigInteger
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import relationship, Session
from wrolpi import dates
from wrolpi import dates, flags
from wrolpi.common import ModelHelper, Base, logger, ConfigFile, get_media_directory, background_task, run_after, \
register_refresh_cleanup, get_relative_to_media_directory, is_valid_hex_color, walk, INVALID_FILE_CHARS
get_relative_to_media_directory, is_valid_hex_color, walk, INVALID_FILE_CHARS
from wrolpi.dates import TZDateTime
from wrolpi.db import optional_session, get_db_curs
from wrolpi.errors import UnknownTag, UsedTag, InvalidTag, FileGroupAlreadyTagged
from wrolpi.errors import UnknownTag, UsedTag, InvalidTag, FileGroupAlreadyTagged, RefreshConflict
from wrolpi.vars import PYTEST
logger = logger.getChild(__name__)
@ -150,6 +150,26 @@ class Tag(ModelHelper, Base):
"""Returns True if this Tag has been used with any FileGroups or Zim Entries."""
return bool(self.tag_files or self.tag_zim_entries)
def update_tag(self, name: str, color: str, session: Session):
"""Change name/color of tag. Ensures safeness of new values, checks for conflicts."""
if ',' in name:
raise InvalidTag('Tag name cannot have comma')
if not is_valid_hex_color(color):
raise InvalidTag('Tag color is invalid')
# Replace forward-slash (linux directories) with unicode Big Solidus (U+29F8)
name = name.replace('/', '⧸')
name = INVALID_FILE_CHARS.sub('', name)
if Tag.get_by_name(name, session):
raise InvalidTag('Tag name already taken')
if flags.refreshing.is_set():
raise RefreshConflict('Refusing to rename tag while file refresh is in progress')
self.name = name
self.color = color
class TagsConfig(ConfigFile):
file_name = 'tags.yaml'
@ -375,27 +395,16 @@ def get_tags() -> List[dict]:
@optional_session
@run_after(save_tags_config)
def upsert_tag(name: str, color: str, tag_id: int = None, session: Session = None) -> Tag:
if ',' in name:
raise InvalidTag('Tag name cannot have comma')
if not is_valid_hex_color(color):
raise InvalidTag('Tag color is invalid')
# Replace forward-slash (linux directories) with unicode Big Solidus (U+29F8)
name = name.replace('/', '⧸')
name = INVALID_FILE_CHARS.sub('', name)
if tag_id:
tag = session.query(Tag).filter_by(id=tag_id).one_or_none()
if not tag:
raise UnknownTag(f'Cannot find tag with id={tag_id}')
tag.name = name
tag.color = color
tag = Tag.find_by_id(tag_id, session)
tag.update_tag(name, color, session)
tag.flush()
else:
tag = Tag(name=name, color=color)
tag = Tag()
tag.update_tag(name, color, session)
session.add(tag)
try:
session.flush([tag])
session.commit()
except IntegrityError as e:
# Conflicting name
@ -410,14 +419,10 @@ def upsert_tag(name: str, color: str, tag_id: int = None, session: Session = Non
@optional_session
def delete_tag(tag_id: int, session: Session = None):
tag: Tag = session.query(Tag).filter_by(id=tag_id).one_or_none()
tag = Tag.find_by_id(tag_id, session)
if not tag:
raise UnknownTag(f'Cannot find tag {tag_id}')
if tag.tag_files:
count = len(tag.tag_files)
raise UsedTag(f'Cannot delete {tag.name} it is used by {count} files!')
if tag.has_relations():
raise UsedTag(f'Cannot delete {tag.name} it is used')
session.delete(tag)
session.commit()

View file

@ -339,6 +339,7 @@ async def test_invalid_tag(test_async_client, test_session, test_directory):
@pytest.mark.asyncio
async def test_tags_directory(test_async_client, test_session, test_directory, tag_factory, video_factory,
example_pdf):
"""Test that Tag Directory is synchronized with database."""
readme = test_directory / 'tags/README.txt'
# Should be deleted on sync.
@ -436,3 +437,26 @@ async def test_tags_directory(test_async_client, test_session, test_directory, t
# Only three FileGroups were created. Tags Directory files are ignored during refresh.
await files_lib.refresh_files()
assert test_session.query(FileGroup).count() == 3
@pytest.mark.asyncio
async def test_update_tag(test_async_client, test_session, test_directory, video_factory, tag_factory):
"""Linked files in the Tag Directory are moved when the tag is renamed."""
# Create tagged Video.
tag = tag_factory()
video_factory(title='video', tag_names=[tag.name])
test_session.commit()
# Video file is linked in tag directory.
assert (test_directory / 'tags').is_dir()
assert (test_directory / 'videos/NO CHANNEL/video.mp4').is_file()
assert (test_directory / 'tags/one/video.mp4').is_file()
# Invalid characters are replaced during rename.
tags.upsert_tag('new/name%', tag.color, tag.id)
# Video file was moved.
assert (test_directory / 'tags').is_dir()
assert (test_directory / 'videos/NO CHANNEL/video.mp4').is_file()
assert not (test_directory / 'tags/one/video.mp4').exists()
assert (test_directory / 'tags/new⧸name/video.mp4').is_file()