mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-01-23 10:45:27 +00:00
Ask to delete local files as well.
This commit is contained in:
parent
d926d90dd9
commit
bc08cb1270
4 changed files with 93 additions and 15 deletions
|
|
@ -1052,12 +1052,28 @@ class BulkDeleteLogosAPIView(APIView):
|
|||
)
|
||||
def delete(self, request):
|
||||
logo_ids = request.data.get("logo_ids", [])
|
||||
delete_files = request.data.get("delete_files", False)
|
||||
|
||||
# Get logos and their usage info before deletion
|
||||
logos_to_delete = Logo.objects.filter(id__in=logo_ids)
|
||||
total_channels_affected = 0
|
||||
|
||||
local_files_deleted = 0
|
||||
|
||||
for logo in logos_to_delete:
|
||||
# Handle file deletion for local files
|
||||
if delete_files and logo.url and logo.url.startswith('/data/logos'):
|
||||
try:
|
||||
if os.path.exists(logo.url):
|
||||
os.remove(logo.url)
|
||||
local_files_deleted += 1
|
||||
logger.info(f"Deleted local logo file: {logo.url}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to delete logo file {logo.url}: {str(e)}")
|
||||
return Response(
|
||||
{"error": f"Failed to delete logo file {logo.url}: {str(e)}"},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||
)
|
||||
|
||||
if logo.channels.exists():
|
||||
channel_count = logo.channels.count()
|
||||
total_channels_affected += channel_count
|
||||
|
|
@ -1071,6 +1087,8 @@ class BulkDeleteLogosAPIView(APIView):
|
|||
message = f"Successfully deleted {deleted_count} logos"
|
||||
if total_channels_affected > 0:
|
||||
message += f" and removed them from {total_channels_affected} channels"
|
||||
if local_files_deleted > 0:
|
||||
message += f" and deleted {local_files_deleted} local files"
|
||||
|
||||
return Response(
|
||||
{"message": message},
|
||||
|
|
@ -1157,6 +1175,20 @@ class LogoViewSet(viewsets.ModelViewSet):
|
|||
def destroy(self, request, *args, **kwargs):
|
||||
"""Delete a logo and remove it from any channels using it"""
|
||||
logo = self.get_object()
|
||||
delete_file = request.query_params.get('delete_file', 'false').lower() == 'true'
|
||||
|
||||
# Check if it's a local file that should be deleted
|
||||
if delete_file and logo.url and logo.url.startswith('/data/logos'):
|
||||
try:
|
||||
if os.path.exists(logo.url):
|
||||
os.remove(logo.url)
|
||||
logger.info(f"Deleted local logo file: {logo.url}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to delete logo file {logo.url}: {str(e)}")
|
||||
return Response(
|
||||
{"error": f"Failed to delete logo file: {str(e)}"},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||
)
|
||||
|
||||
# Instead of preventing deletion, remove the logo from channels
|
||||
if logo.channels.exists():
|
||||
|
|
|
|||
|
|
@ -1337,9 +1337,15 @@ export default class API {
|
|||
}
|
||||
}
|
||||
|
||||
static async deleteLogo(id) {
|
||||
static async deleteLogo(id, deleteFile = false) {
|
||||
try {
|
||||
await request(`${host}/api/channels/logos/${id}/`, {
|
||||
const params = new URLSearchParams();
|
||||
if (deleteFile) {
|
||||
params.append('delete_file', 'true');
|
||||
}
|
||||
|
||||
const url = `${host}/api/channels/logos/${id}/?${params.toString()}`;
|
||||
await request(url, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
|
|
@ -1351,11 +1357,16 @@ export default class API {
|
|||
}
|
||||
}
|
||||
|
||||
static async deleteLogos(ids) {
|
||||
static async deleteLogos(ids, deleteFiles = false) {
|
||||
try {
|
||||
const body = { logo_ids: ids };
|
||||
if (deleteFiles) {
|
||||
body.delete_files = true;
|
||||
}
|
||||
|
||||
await request(`${host}/api/channels/logos/bulk-delete/`, {
|
||||
method: 'DELETE',
|
||||
body: { logo_ids: ids },
|
||||
body: body,
|
||||
});
|
||||
|
||||
// Remove multiple logos from store
|
||||
|
|
|
|||
|
|
@ -29,12 +29,15 @@ const ConfirmationDialog = ({
|
|||
onSuppressChange,
|
||||
size = 'md',
|
||||
zIndex = 1000,
|
||||
showDeleteFileOption = false,
|
||||
deleteFileLabel = "Also delete files from disk",
|
||||
}) => {
|
||||
const suppressWarning = useWarningsStore((s) => s.suppressWarning);
|
||||
const isWarningSuppressed = useWarningsStore((s) => s.isWarningSuppressed);
|
||||
const [suppressChecked, setSuppressChecked] = useState(
|
||||
isWarningSuppressed(actionKey)
|
||||
);
|
||||
const [deleteFiles, setDeleteFiles] = useState(false);
|
||||
|
||||
const handleToggleSuppress = (e) => {
|
||||
setSuppressChecked(e.currentTarget.checked);
|
||||
|
|
@ -47,13 +50,23 @@ const ConfirmationDialog = ({
|
|||
if (suppressChecked) {
|
||||
suppressWarning(actionKey);
|
||||
}
|
||||
onConfirm();
|
||||
if (showDeleteFileOption) {
|
||||
onConfirm(deleteFiles);
|
||||
} else {
|
||||
onConfirm();
|
||||
}
|
||||
setDeleteFiles(false); // Reset for next time
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setDeleteFiles(false); // Reset for next time
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
opened={opened}
|
||||
onClose={onClose}
|
||||
onClose={handleClose}
|
||||
title={title}
|
||||
size={size}
|
||||
centered
|
||||
|
|
@ -70,8 +83,17 @@ const ConfirmationDialog = ({
|
|||
/>
|
||||
)}
|
||||
|
||||
{showDeleteFileOption && (
|
||||
<Checkbox
|
||||
checked={deleteFiles}
|
||||
onChange={(event) => setDeleteFiles(event.currentTarget.checked)}
|
||||
label={deleteFileLabel}
|
||||
mb="md"
|
||||
/>
|
||||
)}
|
||||
|
||||
<Group justify="flex-end">
|
||||
<Button variant="outline" onClick={onClose}>
|
||||
<Button variant="outline" onClick={handleClose}>
|
||||
{cancelLabel}
|
||||
</Button>
|
||||
<Button color="red" onClick={handleConfirm}>
|
||||
|
|
|
|||
|
|
@ -156,10 +156,10 @@ const LogosTable = () => {
|
|||
/**
|
||||
* Functions
|
||||
*/
|
||||
const executeDeleteLogo = useCallback(async (id) => {
|
||||
const executeDeleteLogo = useCallback(async (id, deleteFile = false) => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await API.deleteLogo(id);
|
||||
await API.deleteLogo(id, deleteFile);
|
||||
await fetchLogos();
|
||||
notifications.show({
|
||||
title: 'Success',
|
||||
|
|
@ -182,12 +182,12 @@ const LogosTable = () => {
|
|||
}
|
||||
}, [fetchLogos]);
|
||||
|
||||
const executeBulkDelete = useCallback(async () => {
|
||||
const executeBulkDelete = useCallback(async (deleteFiles = false) => {
|
||||
if (selectedRows.size === 0) return;
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await API.deleteLogos(Array.from(selectedRows));
|
||||
await API.deleteLogos(Array.from(selectedRows), deleteFiles);
|
||||
await fetchLogos();
|
||||
|
||||
notifications.show({
|
||||
|
|
@ -706,11 +706,11 @@ const LogosTable = () => {
|
|||
<ConfirmationDialog
|
||||
opened={confirmDeleteOpen}
|
||||
onClose={() => setConfirmDeleteOpen(false)}
|
||||
onConfirm={() => {
|
||||
onConfirm={(deleteFiles) => {
|
||||
if (isBulkDelete) {
|
||||
executeBulkDelete();
|
||||
executeBulkDelete(deleteFiles);
|
||||
} else {
|
||||
executeDeleteLogo(deleteTarget);
|
||||
executeDeleteLogo(deleteTarget, deleteFiles);
|
||||
}
|
||||
}}
|
||||
title={isBulkDelete ? "Delete Multiple Logos" : "Delete Logo"}
|
||||
|
|
@ -744,6 +744,19 @@ const LogosTable = () => {
|
|||
confirmLabel="Delete"
|
||||
cancelLabel="Cancel"
|
||||
size="md"
|
||||
showDeleteFileOption={
|
||||
isBulkDelete
|
||||
? Array.from(selectedRows).some(id => {
|
||||
const logo = Object.values(logos).find(l => l.id === id);
|
||||
return logo && logo.url && logo.url.startsWith('/data/logos');
|
||||
})
|
||||
: logoToDelete && logoToDelete.url && logoToDelete.url.startsWith('/data/logos')
|
||||
}
|
||||
deleteFileLabel={
|
||||
isBulkDelete
|
||||
? "Also delete local logo files from disk"
|
||||
: "Also delete logo file from disk"
|
||||
}
|
||||
/>
|
||||
|
||||
<ConfirmationDialog
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue