Skip to content

Commit

Permalink
Added Mass editor (#51)
Browse files Browse the repository at this point in the history
* Added base for mass editor

* Added functionality for each action in backend
  • Loading branch information
Casvt authored Nov 29, 2023
1 parent 86ba2d4 commit 464b48f
Show file tree
Hide file tree
Showing 10 changed files with 462 additions and 1 deletion.
81 changes: 81 additions & 0 deletions backend/mass_edit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#-*- coding: utf-8 -*-

from __future__ import annotations

import logging
from typing import TYPE_CHECKING, List, Union

from backend.custom_exceptions import InvalidKeyValue, VolumeDownloadedFor
from backend.db import get_db
from backend.naming import mass_rename
from backend.search import auto_search
from backend.volumes import Volume, refresh_and_scan

if TYPE_CHECKING:
from backend.download_queue import DownloadHandler

class MassEditorVariables:
download_handler: Union[None, DownloadHandler] = None

def mass_editor_delete(volume_ids: List[int], **kwargs) -> None:
delete_volume_folder = kwargs.get('delete_folder', False)
if not isinstance(delete_volume_folder, bool):
raise InvalidKeyValue('delete_folder', delete_volume_folder)

logging.info(f'Using mass editor, deleting volumes: {volume_ids}')

for volume_id in volume_ids:
try:
Volume(volume_id).delete(delete_volume_folder)
except VolumeDownloadedFor:
continue
return

def mass_editor_rename(volume_ids: List[int], **kwargs) -> None:
logging.info(f'Using mass editor, renaming volumes: {volume_ids}')
for volume_id in volume_ids:
mass_rename(volume_id)
return

def mass_editor_update(volume_ids: List[int], **kwargs) -> None:
logging.info(f'Using mass editor, updating volumes: {volume_ids}')
for volume_id in volume_ids:
refresh_and_scan(volume_id)
return

def mass_editor_search(volume_ids: List[int], **kwargs) -> None:
logging.info(f'Using mass editor, auto searching for volumes: {volume_ids}')
for volume_id in volume_ids:
search_results = auto_search(volume_id)
for result in search_results:
MassEditorVariables.download_handler.add(
result['link'],
volume_id
)
return

def mass_editor_convert(volume_ids: List[int], **kwargs) -> None:
logging.info(f'Using mass editor, converting for volumes: {volume_ids}')
return

def mass_editor_unmonitor(volume_ids: List[int], **kwargs) -> None:
logging.info(f'Using mass editor, unmonitoring volumes: {volume_ids}')
for volume_id in volume_ids:
Volume(volume_id)._unmonitor()
return

def mass_editor_monitor(volume_ids: List[int], **kwargs) -> None:
logging.info(f'Using mass editor, monitoring volumes: {volume_ids}')
for volume_id in volume_ids:
Volume(volume_id)._monitor()
return

action_to_func = {
'delete': mass_editor_delete,
'rename': mass_editor_rename,
'update': mass_editor_update,
'search': mass_editor_search,
'convert': mass_editor_convert,
'unmonitor': mass_editor_unmonitor,
'monitor': mass_editor_monitor
}
38 changes: 37 additions & 1 deletion frontend/api.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#-*- coding: utf-8 -*-

import logging
from typing import Any, Tuple
from typing import Any, Dict, List, Tuple

from flask import Blueprint, Flask, request, send_file

Expand Down Expand Up @@ -31,6 +31,7 @@
get_download_history)
from backend.download_torrent_clients import TorrentClients, client_types
from backend.library_import import import_library, propose_library_import
from backend.mass_edit import MassEditorVariables, action_to_func
from backend.naming import (generate_volume_folder_name, mass_rename,
preview_mass_rename)
from backend.root_folders import RootFolders
Expand All @@ -50,6 +51,7 @@
handler_context.teardown_appcontext(close_db)
download_handler = DownloadHandler(handler_context)
task_handler = TaskHandler(handler_context, download_handler)
MassEditorVariables.download_handler = download_handler

def return_api(result: Any, error: str=None, code: int=200) -> Tuple[dict, int]:
return {'error': error, 'result': result}, code
Expand Down Expand Up @@ -796,3 +798,37 @@ def api_torrent_client(id: int):
elif request.method == 'DELETE':
client.delete()
return return_api({})

#=====================
# Torrent Clients
#=====================
@api.route('/masseditor', methods=['POST'])
@error_handler
@auth
def api_mass_editor():
data = request.get_json()
if not isinstance(data, dict):
raise InvalidKeyValue('body', data)
if not 'volume_ids' in data:
raise KeyNotFound('volume_ids')
if not 'action' in data:
raise KeyNotFound('action')

action: str = data['action']
volume_ids: List[int] = data['volume_ids']
args: Dict[str, Any] = data.get('args', {})

if not (
isinstance(volume_ids, list)
and all(isinstance(v, int) for v in volume_ids)
):
raise InvalidKeyValue('volume_ids', volume_ids)

if not action in action_to_func:
raise InvalidKeyValue('action', action)

if not isinstance(args, dict):
raise InvalidKeyValue('args', args)

action_to_func[action](volume_ids, **args)
return return_api({})
131 changes: 131 additions & 0 deletions frontend/static/css/mass_editor.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
.nav-main > main > div {
flex: none;
}

main {
color: var(--text-color);
padding: 1rem;
}

#loading-window {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}

#loading-window h2 {
font-size: clamp(1rem, 5vw, 2rem);
font-weight: 500;
text-align: center;
}

#list-window {
height: 100%;
display: flex;
flex-direction: column;
}

.action-bar {
display: flex;
flex-wrap: wrap;
gap: 1rem;

padding: .5rem;
border-radius: 4px;
background-color: var(--tool-bar-color);
color: var(--light-color);
}

.action-divider {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}

.action-bar button {
min-width: 5rem;

border: 2px solid var(--border-color);
border-radius: 4px;
padding: .4rem .5rem;
color: var(--text-color);
background-color: var(--library-entry-color);

transition: background-color 100ms linear;
}

.action-bar button:hover {
background-color: var(--dark-hover-color);
}

.option-container {
overflow: hidden;
border-radius: 4px;
border: 2px solid var(--border-color);
background-color: var(--library-entry-color);
}

.option-container button {
border-radius: none;
border: none;
}

.option-container select {
height: 100%;
background-color: var(--library-entry-color);
color: var(--text-color);
}

.list-container {
position: relative;

width: 100%;
overflow-x: auto;

padding-top: 1rem;
}

table {
width: max(100%, 30rem);
border-spacing: 0px;
}

th {
text-align: left;
}

th,
td {
padding: .5rem;
border-bottom: 1px solid var(--nav-color);
}

th:first-child,
td:first-child {
width: 3rem;
padding-inline: 1rem;
}

tbody > tr {
transition: background-color 150ms ease-in-out;
}

tbody > tr:hover {
background-color: var(--dark-hover-color);
}

@media (max-width: 820px) {
.action-bar {
justify-content: center;
}
.action-divider {
width: 100%;
}
.option-container {
flex: 1 1 auto;
}
.action-bar button {
flex: 1 1 0;
}
}
92 changes: 92 additions & 0 deletions frontend/static/js/mass_editor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
const windows = {
loading: document.querySelector('#loading-window'),
list: document.querySelector('#list-window')
};

function fillVolumeList(api_key) {
document.querySelector('#selectall-input').checked = false;
const table = document.querySelector('.volume-list');
table.innerHTML = '';
fetch(`${url_base}/api/volumes?api_key=${api_key}`)
.then(response => response.json())
.then(json => {
json.result.forEach(vol => {
const entry = document.createElement('tr');
entry.dataset.id = vol.id;

const select_container = document.createElement('td');
const select = document.createElement('input');
select.type = 'checkbox';
select.checked = false;
select_container.appendChild(select);
entry.appendChild(select_container);

const title = document.createElement('td');
title.innerText = vol.title;
entry.appendChild(title);

const year = document.createElement('td');
year.innerText = vol.year;
entry.appendChild(year);

const volume_number = document.createElement('td');
volume_number.innerText = vol.volume_number;
entry.appendChild(volume_number);

table.appendChild(entry);
});
});
};

function toggleSelection() {
const checked = document.querySelector('#selectall-input').checked;
document.querySelectorAll('.volume-list input[type="checkbox"]')
.forEach(c => c.checked = checked);
};

function runAction(api_key, action, args={}) {
windows.list.classList.add('hidden');
windows.loading.classList.remove('hidden');

const volume_ids = [...document.querySelectorAll(
'.volume-list input[type="checkbox"]:checked'
)].map(v => parseInt(v.parentNode.parentNode.dataset.id))

fetch(`${url_base}/api/masseditor?api_key=${api_key}`, {
'method': 'POST',
'headers': {'Content-Type': 'application/json'},
'body': JSON.stringify({
'volume_ids': volume_ids,
'action': action,
'args': args
})
})
.then(response => {
fillVolumeList(api_key);
windows.loading.classList.add('hidden');
windows.list.classList.remove('hidden');
});
};

// code run on load

usingApiKey()
.then(api_key => {
fillVolumeList(api_key);
addEventListener('.action-bar > div > button', 'click',
e => runAction(api_key, e.target.dataset.action)
);
addEventListener('button[data-action="delete"]', 'click',
e => runAction(
api_key,
e.target.dataset.action,
{
'delete_folder': document.querySelector(
'select[name="delete_folder"]'
).value === "true"
}
)
);
});

addEventListener('#selectall-input', 'change', e => toggleSelection());
1 change: 1 addition & 0 deletions frontend/templates/add_volume.html
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ <h2 id="add-title"></h2>
<a href="{{url_base}}/">Volumes</a>
<a href="{{url_base}}/add" class="sub-entry current-nav">Add Volume</a>
<a href="{{url_base}}/library-import" class="sub-entry">Library Import</a>
<a href="{{url_base}}/mass-editor" class="sub-entry">Mass Editor</a>
<a href="{{url_base}}/activity/queue">Activity</a>
<a href="{{url_base}}/settings">Settings</a>
<a href="{{url_base}}/system/status">System</a>
Expand Down
1 change: 1 addition & 0 deletions frontend/templates/library_import.html
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ <h2>Edit ComicVine Match</h2>
<a href="{{url_base}}/">Volumes</a>
<a href="{{url_base}}/add" class="sub-entry">Add Volume</a>
<a href="{{url_base}}/library-import" class="sub-entry current-nav">Library Import</a>
<a href="{{url_base}}/mass-editor" class="sub-entry">Mass Editor</a>
<a href="{{url_base}}/activity/queue">Activity</a>
<a href="{{url_base}}/settings">Settings</a>
<a href="{{url_base}}/system/status">System</a>
Expand Down
Loading

0 comments on commit 464b48f

Please sign in to comment.