Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drag and drop files in and out of TagStudio #153

Merged
merged 38 commits into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
dccf5f2
Ability to drop local files in to TagStudio to add to library
Creepler13 May 9, 2024
44c6a96
Added renaming option to drop import
Creepler13 May 9, 2024
95fe393
Improved readability and switched to pathLib
Creepler13 May 10, 2024
c3793d5
format
Creepler13 May 10, 2024
f0cf853
Apply suggestions from code review
Creepler13 May 10, 2024
f7eaec7
Merge branch 'TagStudioDev:main' into DropFiles
Creepler13 May 10, 2024
e835a00
Revert Change
Creepler13 May 10, 2024
741b2d6
Update tagstudio/src/qt/modals/drop_import.py
Creepler13 May 10, 2024
5149855
Added support for folders
Creepler13 May 12, 2024
12c4c43
formatting
Creepler13 May 12, 2024
aeb119e
Progress bars added
Creepler13 May 12, 2024
dbbe1b0
Added Ability to Drag out of window
Creepler13 May 22, 2024
9a45ae1
f
Creepler13 May 22, 2024
1a70369
format
Creepler13 May 22, 2024
0ac25d6
Ability to drop local files in to TagStudio to add to library
Creepler13 May 9, 2024
d7a9305
Added renaming option to drop import
Creepler13 May 9, 2024
f9d1b55
Improved readability and switched to pathLib
Creepler13 May 10, 2024
374ddbc
format
Creepler13 May 10, 2024
5b7f209
Apply suggestions from code review
Creepler13 May 10, 2024
76879da
Revert Change
Creepler13 May 10, 2024
cf1402f
Update tagstudio/src/qt/modals/drop_import.py
Creepler13 May 10, 2024
db8d94e
Added support for folders
Creepler13 May 12, 2024
75acd23
formatting
Creepler13 May 12, 2024
8f8b93b
Progress bars added
Creepler13 May 12, 2024
c7f420b
Added Ability to Drag out of window
Creepler13 May 22, 2024
40873ff
f
Creepler13 May 22, 2024
234a9cb
format
Creepler13 May 22, 2024
46b706b
Rebase
Creepler13 May 22, 2024
06774b6
format
Creepler13 May 22, 2024
b453c11
formatting and refactor
Creepler13 May 22, 2024
2020985
format again
Creepler13 May 22, 2024
acb27c7
formatting for mypy
Creepler13 May 22, 2024
fb1c3a2
convert lambda to func for clarity
Creepler13 May 24, 2024
dfbf31f
mypy fixes
Creepler13 May 24, 2024
45056f1
fixed dragout only worked on selected
Creepler13 May 24, 2024
301a7b0
Refactor typo, Add license
CyanVoxel Jun 13, 2024
763d189
Reformat QMessageBox
CyanVoxel Jun 13, 2024
9549d92
Disable drops when no library is open
CyanVoxel Jun 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions tagstudio/src/qt/modals/drop_import.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
from pathlib import Path
import shutil
import typing
import logging

from PySide6.QtGui import QDropEvent, QDragEnterEvent, QDragMoveEvent
from PySide6.QtWidgets import QMessageBox

if typing.TYPE_CHECKING:
from src.qt.ts_qt import QtDriver

logging.basicConfig(format="[DROP IMPORT] %(message)s", level=logging.INFO)


def dropEvent(driver: "QtDriver", event: QDropEvent):
if not event.mimeData().hasUrls():
return

import_files(driver, event.mimeData().urls())


def dragEnterEvent(event: QDragEnterEvent):
if event.mimeData().hasUrls():
event.accept()
else:
event.ignore()


def dragMoveEvent(event: QDragMoveEvent):
if event.mimeData().hasUrls():
event.accept()
else:
event.ignore()


def import_files(driver: "QtDriver", urls):
files: list[Path] = []
dirs_in_root: list[Path] = []
duplicate_files: list[Path] = []

for url in urls:
if url.isLocalFile():
Creepler13 marked this conversation as resolved.
Show resolved Hide resolved
file = Path(url.toLocalFile())

if file.is_dir():
files += get_files_in_folder(file)
dirs_in_root.append(file.parent)

dupes = get_files_exists_in_library(
dirs_in_root, driver.lib.library_dir, file
)
duplicate_files += dupes
else:
files.append(file)

if file.parent not in dirs_in_root:
dirs_in_root.append(
file.parent
) # to create relative path of files not in folder

if (Path(driver.lib.library_dir) / file.name).exists():
duplicate_files.append(file)

ret = -1

if len(duplicate_files) > 0:
msgBox = QMessageBox()
msgBox.setText(
f"The files {', '.join(map(lambda path: str(path),get_relativ_paths(dirs_in_root,duplicate_files)))} have filenames that already exist in the library folder."
)
msgBox.addButton("Skip", QMessageBox.ButtonRole.YesRole)
msgBox.addButton("Override", QMessageBox.ButtonRole.DestructiveRole)
msgBox.addButton("Rename", QMessageBox.ButtonRole.DestructiveRole)
msgBox.addButton("Cancel", QMessageBox.ButtonRole.NoRole)
ret = msgBox.exec()

if ret == 3: # cancel
return
logging.info(files)
for file in files:
if file.is_dir():
continue

dest_file = get_relativ_path(dirs_in_root, file)

if file in duplicate_files:
if ret == 0: # skip duplicates
continue

if ret == 2: # rename
dest_file = dest_file.with_name(
get_renamed_duplicate_filename(
Path(driver.lib.library_dir), dest_file
)
)
driver.lib.files_not_in_library.append(dest_file)
else: # override is simply copying but not adding a new entry
driver.lib.files_not_in_library.append(dest_file)

(driver.lib.library_dir / dest_file).parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(file, driver.lib.library_dir / dest_file)

driver.add_new_files_runnable()


def get_renamed_duplicate_filename(path: Path, filePath: Path) -> str:
index = 2
o_filename = filePath.name
dot_idx = o_filename.index(".")
while (path / filePath).exists():
filePath = filePath.with_name(
o_filename[:dot_idx] + f" ({index})" + o_filename[dot_idx:]
)
index += 1
return filePath.name


def get_files_in_folder(path: Path) -> list[Path]:
files = []
for file in path.glob("**/*"):
files.append(file)
return files


def get_files_exists_in_library(
dirs_in_root: list[Path], lib_dir: str, path: Path
) -> list[Path]:
exists: list[Path] = []
if path.is_dir():
files = get_files_in_folder(path)
for file in files:
if file.is_dir():
exists += get_files_exists_in_library(dirs_in_root, lib_dir, file)
elif (lib_dir / get_relativ_path(dirs_in_root, file)).exists():
exists.append(file)
return exists


def get_relativ_paths(roots: list[Path], paths: list[Path]) -> list[Path]:
relativ_paths = []
for file in paths:
relativ_paths.append(get_relativ_path(roots, file))
return relativ_paths


def get_relativ_path(roots: list[Path], path: Path) -> Path:
for dir in roots:
if path.is_relative_to(dir):
return path.relative_to(dir)
return path.name
6 changes: 6 additions & 0 deletions tagstudio/src/qt/ts_qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
FixUnlinkedEntriesModal,
FixDupeFilesModal,
FoldersToTagsModal,
drop_import,
)
import src.qt.resources_rc

Expand Down Expand Up @@ -508,6 +509,11 @@ def start(self):
# self.render_times: list = []
# self.main_window.setWindowFlag(Qt.FramelessWindowHint)

self.main_window.setAcceptDrops(True)
self.main_window.dragEnterEvent = drop_import.dragEnterEvent
self.main_window.dropEvent = lambda event: drop_import.dropEvent(self, event)
self.main_window.dragMoveEvent = drop_import.dragMoveEvent

# NOTE: Putting this early will result in a white non-responsive
# window until everything is loaded. Consider adding a splash screen
# or implementing some clever loading tricks.
Expand Down