Skip to content

Commit

Permalink
Added "Import and Rename" (#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
Casvt committed Oct 28, 2023
1 parent c4a5b4a commit a522472
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 25 deletions.
2 changes: 1 addition & 1 deletion backend/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,7 @@ def delete_empty_folders(top_folder: str, root_folder: str) -> None:
if not top_folder.startswith(abspath(root_folder) + sep):
logging.error(f'The folder {top_folder} is not in {root_folder}')
return

while (not exists(top_folder) or not(
samefile(top_folder, root_folder)
or listdir(top_folder)
Expand Down
79 changes: 61 additions & 18 deletions backend/library_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@

import logging
from asyncio import create_task, gather, run
from os.path import basename, commonpath, dirname, splitext
from os.path import basename, commonpath, dirname, join, splitext
from shutil import move
from typing import Dict, List, Tuple, Union

from aiohttp import ClientSession

from backend.comicvine import ComicVine
from backend.custom_exceptions import VolumeAlreadyAdded
from backend.db import get_db
from backend.files import (_list_files, extract_filename_data, scan_files,
from backend.files import (_list_files, delete_empty_folders,
extract_filename_data, image_extensions, scan_files,
supported_extensions)
from backend.naming import mass_rename
from backend.root_folders import RootFolders
from backend.search import _check_matching_titles
from backend.volumes import Library, Volume
Expand Down Expand Up @@ -124,48 +127,88 @@ def propose_library_import() -> List[dict]:

return result

def import_library(matches: List[Dict[str, Union[str, int]]]) -> None:
def __find_lowest_common_folder(files: List[str]) -> str:
"""Find the lowest folder that is shared between the files
Args:
files (List[str]): The list of files to find the lowest common folder for
Returns:
str: The path of the lowest common folder
"""
if len(files) == 1:
return dirname(files[0])

return commonpath(files)

def import_library(matches: List[Dict[str, Union[str, int]]], rename_files: bool=False) -> None:
"""Add volume to library and import linked files
Args:
matches (List[Dict[str, Union[str, int]]]): List of dicts.
The key `id` should supply the CV id of the volume and `filepath` the linked file.
rename_files (bool, optional): Should Kapowarr trigger a rename after importing files? Defaults to False.
"""
logging.info('Starting library import')
id_to_filepath = {}
cvid_to_filepath: Dict[int, List[str]] = {}
for match in matches:
id_to_filepath.setdefault(match['id'], []).append(match['filepath'])
logging.debug(f'id_to_filepath: {id_to_filepath}')
cvid_to_filepath.setdefault(match['id'], []).append(match['filepath'])
logging.debug(f'id_to_filepath: {cvid_to_filepath}')

root_folders = RootFolders().get_all()

cursor = get_db()
library = Library()
for cv_id, files in id_to_filepath.items():
# Find lowest common folder
volume_folder: str
if len(files) == 1:
volume_folder = dirname(files[0])
else:
volume_folder = commonpath(files)
for cv_id, files in cvid_to_filepath.items():
# Find lowest common folder (lcf)
volume_folder = __find_lowest_common_folder(files) if not rename_files else None

# Find root folder that lcf is in
# Find root folder that media is in
for root_folder in root_folders:
if volume_folder.startswith(root_folder['folder']):
if files[0].startswith(root_folder['folder']):
root_folder_id = root_folder['id']
break
else:
continue
logging.debug(f'{cv_id} -> {volume_folder}')

# Add volume if it isn't already
try:
volume_id = library.add(
comicvine_id=str(cv_id),
root_folder_id=root_folder_id,
monitor=True,
volume_folder=volume_folder
)
scan_files(Volume(volume_id).get_info())
cursor.connection.commit()

except VolumeAlreadyAdded:
# The volume is already added but the file is not matched to it
# (it isn't because otherwise it wouldn't pop up in LI).
# That would mean that the file is actually not
# for that volume so skip.
continue

volume = Volume(volume_id)
if rename_files:
# Put files in volume folder
vf: str = volume.get_info()['folder']
new_files = []
for f in files:
if f.endswith(image_extensions):
new_files.append(move(
f, join(vf, basename(dirname(f)))
))
else:
new_files.append(move(f, vf))


delete_empty_folders(dirname(f), root_folder['folder'])

scan_files(volume.get_info())

# Trigger rename
mass_rename(volume_id, filepath_filter=new_files)

else:
scan_files(volume.get_info())

return
10 changes: 8 additions & 2 deletions frontend/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ def extract_key(request, key: str, check_existence: bool=True) -> Any:
if not value in blocklist_reasons:
raise InvalidKeyValue(key, value)

elif key in ('monitor', 'delete_folder'):
elif key in ('monitor', 'delete_folder', 'rename_files'):
if value == 'true':
value = True
elif value == 'false':
Expand All @@ -150,6 +150,9 @@ def extract_key(request, key: str, check_existence: bool=True) -> Any:
elif key == 'offset':
value = 0

elif key == 'rename_files':
value = False

return value

#=====================
Expand Down Expand Up @@ -357,12 +360,15 @@ def api_library_import():

elif request.method == 'POST':
data = request.get_json()
rename_files = extract_key(request, 'rename_files', False)

if (
not isinstance(data, list)
or not all(isinstance(e, dict) and 'filepath' in e and 'id' in e for e in data)
):
raise InvalidKeyValue
import_library(data)

import_library(data, rename_files)
return return_api({}, code=201)

#=====================
Expand Down
16 changes: 12 additions & 4 deletions frontend/static/js/library_import.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ function loadProposal(api_key) {
document.querySelector('.table-container').classList.remove('hidden');
document.querySelector('#run-button').innerText = 'Run';
document.querySelector('#import-button').classList.remove('hidden');
document.querySelector('#import-rename-button').classList.remove('hidden');
});
};

Expand Down Expand Up @@ -144,23 +145,29 @@ function searchCV() {
});
};

function importLibrary(api_key) {
function importLibrary(api_key, rename=false) {
const import_button = document.querySelector('#import-button');
const import_rename_button = document.querySelector('#import-rename-button');
const used_button = rename ? import_rename_button : import_button;

const data = [...document.querySelectorAll('.proposal-list > tr:not([data-cv_id=""]) input[type="checkbox"]:checked')]
.map(e => { return {
'filepath': e.parentNode.nextSibling.title,
'id': parseInt(e.parentNode.parentNode.dataset.cv_id)
} });

import_button.innerText = 'Importing';
fetch(`${url_base}/api/libraryimport?api_key=${api_key}`, {
used_button.innerText = 'Importing';
fetch(`${url_base}/api/libraryimport?api_key=${api_key}&rename_files=${rename}`, {
'method': 'POST',
'headers': {'Content-Type': 'application/json'},
'body': JSON.stringify(data)
})
.then(response => {
import_rename_button.innerText = 'Import and Rename';
import_rename_button.classList.add('hidden');
import_button.innerText = 'Import';
import_button.classList.add('hidden');

document.querySelector('.table-container').classList.add('hidden');
});
};
Expand All @@ -174,7 +181,8 @@ usingApiKey()
loadProposal(api_key);
});
addEventListener('#refresh-button', 'click', e => loadProposal(api_key));
addEventListener('#import-button', 'click', e => importLibrary(api_key));
addEventListener('#import-button', 'click', e => importLibrary(api_key, false));
addEventListener('#import-rename-button', 'click', e => importLibrary(api_key, true));
});

setAttribute('.search-bar', 'action', 'javascript:searchCV();');
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 @@ -82,6 +82,7 @@ <h2>Edit ComicVine Match</h2>
<div class="action-container">
<button id="run-button">Run</button>
<button id="import-button" class="hidden">Import</button>
<button id="import-rename-button" class="hidden">Import and Rename</button>
</div>
<div class="table-container hidden">
<table>
Expand Down

0 comments on commit a522472

Please sign in to comment.