Fix permanent delete
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -652,14 +652,16 @@ def delete_permanent(room_id):
|
|||||||
if not rf:
|
if not rf:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Delete the file from storage if it's a file
|
# Delete the file/folder from storage
|
||||||
if rf.type == 'file':
|
|
||||||
try:
|
try:
|
||||||
file_path = os.path.join(get_room_dir(room_id), rf.path, rf.name)
|
file_path = os.path.join(get_room_dir(room_id), rf.path, rf.name)
|
||||||
if os.path.exists(file_path):
|
if os.path.exists(file_path):
|
||||||
|
if rf.type == 'file':
|
||||||
os.remove(file_path)
|
os.remove(file_path)
|
||||||
|
elif rf.type == 'folder':
|
||||||
|
shutil.rmtree(file_path)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error deleting file from storage: {e}")
|
print(f"Error deleting {rf.type} from storage: {e}")
|
||||||
|
|
||||||
# Delete the database record
|
# Delete the database record
|
||||||
db.session.delete(rf)
|
db.session.delete(rf)
|
||||||
|
|||||||
@@ -103,8 +103,25 @@ def empty_trash(room_id):
|
|||||||
if not user_has_permission(room, 'can_delete'):
|
if not user_has_permission(room, 'can_delete'):
|
||||||
abort(403)
|
abort(403)
|
||||||
|
|
||||||
# Delete all trashed files for this room
|
# Get all trashed files for this room
|
||||||
|
trashed_files = TrashedFile.query.filter_by(room_id=room_id).all()
|
||||||
|
room_files = RoomFile.query.filter_by(room_id=room_id, deleted=True).all()
|
||||||
|
|
||||||
|
# Delete physical files
|
||||||
|
room_dir = os.path.join('/data/rooms', str(room_id))
|
||||||
|
for file in trashed_files + room_files:
|
||||||
|
try:
|
||||||
|
if file.type == 'file':
|
||||||
|
file_path = os.path.join(room_dir, file.original_path if hasattr(file, 'original_path') else file.path, file.name)
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
os.remove(file_path)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error deleting physical file {file.name}: {str(e)}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Delete all trashed files from both tables
|
||||||
TrashedFile.query.filter_by(room_id=room_id).delete()
|
TrashedFile.query.filter_by(room_id=room_id).delete()
|
||||||
|
RoomFile.query.filter_by(room_id=room_id, deleted=True).delete()
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return jsonify({'success': True})
|
return jsonify({'success': True})
|
||||||
@@ -32,6 +32,13 @@ window.emptyTrash = function() {
|
|||||||
fetch('/api/rooms/trash')
|
fetch('/api/rooms/trash')
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
.then(files => {
|
.then(files => {
|
||||||
|
if (!files || !files.length) {
|
||||||
|
// No files to delete, just close the modal
|
||||||
|
const modal = bootstrap.Modal.getInstance(document.getElementById('emptyTrashModal'));
|
||||||
|
modal.hide();
|
||||||
|
return Promise.resolve([]);
|
||||||
|
}
|
||||||
|
|
||||||
// Get unique room IDs
|
// Get unique room IDs
|
||||||
const roomIds = [...new Set(files.map(file => file.room_id))];
|
const roomIds = [...new Set(files.map(file => file.room_id))];
|
||||||
|
|
||||||
@@ -61,12 +68,20 @@ window.emptyTrash = function() {
|
|||||||
// Close the modal
|
// Close the modal
|
||||||
const modal = bootstrap.Modal.getInstance(document.getElementById('emptyTrashModal'));
|
const modal = bootstrap.Modal.getInstance(document.getElementById('emptyTrashModal'));
|
||||||
modal.hide();
|
modal.hide();
|
||||||
|
// Refresh the view to ensure everything is up to date
|
||||||
|
fetchFiles();
|
||||||
} else {
|
} else {
|
||||||
console.error('Failed to empty trash in some rooms');
|
console.error('Failed to empty trash in some rooms');
|
||||||
|
// Show error message to user
|
||||||
|
const grid = document.getElementById('fileGrid');
|
||||||
|
grid.innerHTML = '<div class="col"><div class="text-danger">Failed to empty trash. Please try again.</div></div>';
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error emptying trash:', error);
|
console.error('Error emptying trash:', error);
|
||||||
|
// Show error message to user
|
||||||
|
const grid = document.getElementById('fileGrid');
|
||||||
|
grid.innerHTML = '<div class="col"><div class="text-danger">Error emptying trash. Please try again.</div></div>';
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1360,7 +1360,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
moveModal = new bootstrap.Modal(document.getElementById('moveModal'));
|
moveModal = new bootstrap.Modal(document.getElementById('moveModal'));
|
||||||
|
|
||||||
// Add click handler for move confirmation button
|
// Add click handler for move confirmation button
|
||||||
|
if (canMove === true || canMove === 'true') {
|
||||||
document.getElementById('confirmMoveBtn').addEventListener('click', moveFileConfirmed);
|
document.getElementById('confirmMoveBtn').addEventListener('click', moveFileConfirmed);
|
||||||
|
}
|
||||||
|
|
||||||
// Add click handler for new folder button
|
// Add click handler for new folder button
|
||||||
if (canUpload === true || canUpload === 'true') {
|
if (canUpload === true || canUpload === 'true') {
|
||||||
@@ -1715,126 +1717,140 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
// Start processing files
|
// Start processing files
|
||||||
await processNextFile();
|
await processNextFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Robust modal show logic
|
|
||||||
function showOverwriteModal(filename) {
|
|
||||||
console.log('[Modal] showOverwriteModal called for', filename);
|
|
||||||
|
|
||||||
// If there's an existing modal promise, resolve it with skip
|
|
||||||
if (modalResolve) {
|
|
||||||
modalResolve('skip');
|
|
||||||
modalResolve = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new promise for this modal interaction
|
|
||||||
modalPromise = new Promise((resolve) => {
|
|
||||||
modalResolve = resolve;
|
|
||||||
|
|
||||||
// Update modal content
|
|
||||||
document.getElementById('overwriteFileName').textContent = filename;
|
|
||||||
|
|
||||||
// Get modal elements
|
|
||||||
const modalEl = document.getElementById('overwriteConfirmModal');
|
|
||||||
|
|
||||||
// Ensure modal is properly initialized with static backdrop
|
|
||||||
if (!overwriteModal) {
|
|
||||||
overwriteModal = new bootstrap.Modal(modalEl, {
|
|
||||||
backdrop: 'static',
|
|
||||||
keyboard: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get fresh button references
|
|
||||||
const confirmOverwriteBtn = document.getElementById('confirmOverwriteBtn');
|
|
||||||
const skipOverwriteBtn = document.getElementById('skipOverwriteBtn');
|
|
||||||
const confirmAllOverwriteBtn = document.getElementById('confirmAllOverwriteBtn');
|
|
||||||
const skipAllOverwriteBtn = document.getElementById('skipAllOverwriteBtn');
|
|
||||||
|
|
||||||
// Remove any existing event listeners by cloning and replacing
|
|
||||||
const newConfirmOverwriteBtn = confirmOverwriteBtn.cloneNode(true);
|
|
||||||
const newSkipOverwriteBtn = skipOverwriteBtn.cloneNode(true);
|
|
||||||
const newConfirmAllOverwriteBtn = confirmAllOverwriteBtn.cloneNode(true);
|
|
||||||
const newSkipAllOverwriteBtn = skipAllOverwriteBtn.cloneNode(true);
|
|
||||||
|
|
||||||
confirmOverwriteBtn.parentNode.replaceChild(newConfirmOverwriteBtn, confirmOverwriteBtn);
|
|
||||||
skipOverwriteBtn.parentNode.replaceChild(newSkipOverwriteBtn, skipOverwriteBtn);
|
|
||||||
confirmAllOverwriteBtn.parentNode.replaceChild(newConfirmAllOverwriteBtn, confirmAllOverwriteBtn);
|
|
||||||
skipAllOverwriteBtn.parentNode.replaceChild(newSkipAllOverwriteBtn, skipAllOverwriteBtn);
|
|
||||||
|
|
||||||
let modalClosed = false;
|
|
||||||
|
|
||||||
// Function to handle modal resolution
|
|
||||||
const resolveModal = (choice) => {
|
|
||||||
if (!modalClosed) {
|
|
||||||
modalClosed = true;
|
|
||||||
if (modalResolve) {
|
|
||||||
modalResolve(choice);
|
|
||||||
modalResolve = null;
|
|
||||||
}
|
|
||||||
overwriteModal.hide();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add click handlers directly to the buttons
|
|
||||||
newConfirmOverwriteBtn.onclick = () => {
|
|
||||||
console.log('[Modal] Overwrite clicked');
|
|
||||||
resolveModal('overwrite');
|
|
||||||
};
|
|
||||||
|
|
||||||
newSkipOverwriteBtn.onclick = () => {
|
|
||||||
console.log('[Modal] Skip clicked');
|
|
||||||
resolveModal('skip');
|
|
||||||
};
|
|
||||||
|
|
||||||
newConfirmAllOverwriteBtn.onclick = () => {
|
|
||||||
console.log('[Modal] Overwrite All clicked');
|
|
||||||
overwriteAll = true;
|
|
||||||
resolveModal('overwrite_all');
|
|
||||||
};
|
|
||||||
|
|
||||||
newSkipAllOverwriteBtn.onclick = () => {
|
|
||||||
console.log('[Modal] Skip All clicked');
|
|
||||||
skipAll = true;
|
|
||||||
resolveModal('skip_all');
|
|
||||||
};
|
|
||||||
|
|
||||||
// Prevent modal from being closed by clicking outside or pressing escape
|
|
||||||
modalEl.addEventListener('click', (e) => {
|
|
||||||
if (e.target === modalEl) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add modal close handler
|
|
||||||
const handleModalClose = (e) => {
|
|
||||||
if (!modalClosed && modalResolve) {
|
|
||||||
console.log('[Modal] Modal closed without explicit choice');
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
// Re-show the modal if it was closed without a choice
|
|
||||||
overwriteModal.show();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Remove any existing close handler and add new one
|
|
||||||
modalEl.removeEventListener('hidden.bs.modal', handleModalClose);
|
|
||||||
modalEl.addEventListener('hidden.bs.modal', handleModalClose);
|
|
||||||
|
|
||||||
// Show the modal
|
|
||||||
overwriteModal.show();
|
|
||||||
|
|
||||||
// Ensure modal is visible and focused
|
|
||||||
modalEl.style.display = 'block';
|
|
||||||
modalEl.classList.add('show');
|
|
||||||
modalEl.focus();
|
|
||||||
});
|
|
||||||
|
|
||||||
return modalPromise;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add event listener for delete confirmation button
|
||||||
|
if (canDelete === true || canDelete === 'true') {
|
||||||
|
document.getElementById('confirmDeleteBtn').addEventListener('click', deleteFileConfirmed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add event listener for rename confirmation button
|
||||||
|
if (canRename === true || canRename === 'true') {
|
||||||
|
document.getElementById('confirmRenameBtn').addEventListener('click', function() {
|
||||||
|
if (!renameTarget) return;
|
||||||
|
let newName = document.getElementById('renameInput').value.trim();
|
||||||
|
if (renameIsFile) {
|
||||||
|
if (!newName) {
|
||||||
|
document.getElementById('renameError').textContent = 'New name is required.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
newName = newName + renameOrigExt;
|
||||||
|
}
|
||||||
|
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
||||||
|
if (!newName) {
|
||||||
|
document.getElementById('renameError').textContent = 'New name is required.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fetch(`/api/rooms/${roomId}/rename`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRFToken': csrfToken
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
old_name: renameTarget,
|
||||||
|
new_name: newName,
|
||||||
|
path: currentPath
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(res => {
|
||||||
|
if (res.success) {
|
||||||
|
fetchFiles();
|
||||||
|
var modalEl = document.getElementById('renameModal');
|
||||||
|
var modal = bootstrap.Modal.getInstance(modalEl);
|
||||||
|
modal.hide();
|
||||||
|
} else {
|
||||||
|
document.getElementById('renameError').textContent = res.error || 'Rename failed.';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
document.getElementById('renameError').textContent = 'Rename failed.';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add event listener for download selected
|
||||||
|
if (canDownload === true || canDownload === 'true') {
|
||||||
|
document.getElementById('downloadSelectedBtn').addEventListener('click', function() {
|
||||||
|
const selectedCheckboxes = document.querySelectorAll('.select-item-checkbox:checked');
|
||||||
|
if (selectedCheckboxes.length === 0) return;
|
||||||
|
|
||||||
|
const selectedItems = Array.from(selectedCheckboxes).map(cb => {
|
||||||
|
const idx = parseInt(cb.dataset.idx);
|
||||||
|
return window.currentFiles[idx];
|
||||||
|
});
|
||||||
|
|
||||||
|
// Submit the request to download the zip
|
||||||
|
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
||||||
|
fetch(`/api/rooms/${roomId}/download-zip`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRFToken': csrfToken
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ items: selectedItems })
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Download failed');
|
||||||
|
}
|
||||||
|
return response.blob();
|
||||||
|
})
|
||||||
|
.then(blob => {
|
||||||
|
// Create a download link and trigger it
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = 'download.zip';
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
a.remove();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error downloading files:', error);
|
||||||
|
document.getElementById('fileError').textContent = 'Failed to download files.';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add event listener for batch delete
|
||||||
|
if (canDelete === true || canDelete === 'true') {
|
||||||
|
document.getElementById('deleteSelectedBtn').addEventListener('click', function() {
|
||||||
|
const selectedCheckboxes = document.querySelectorAll('.select-item-checkbox:checked');
|
||||||
|
if (selectedCheckboxes.length === 0) return;
|
||||||
|
|
||||||
|
batchDeleteItems = Array.from(selectedCheckboxes).map(cb => {
|
||||||
|
const idx = parseInt(cb.dataset.idx);
|
||||||
|
return window.currentFiles[idx];
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get the modal element
|
||||||
|
const modalEl = document.getElementById('deleteConfirmModal');
|
||||||
|
if (!modalEl) {
|
||||||
|
console.error('Delete modal element not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the modal if it hasn't been initialized
|
||||||
|
if (!deleteModal) {
|
||||||
|
deleteModal = new bootstrap.Modal(modalEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update modal content
|
||||||
|
const fileNameEl = document.getElementById('deleteFileName');
|
||||||
|
const labelEl = document.getElementById('deleteConfirmLabel');
|
||||||
|
|
||||||
|
if (fileNameEl) fileNameEl.textContent = `${selectedCheckboxes.length} item${selectedCheckboxes.length > 1 ? 's' : ''}`;
|
||||||
|
if (labelEl) labelEl.textContent = 'Move to Trash';
|
||||||
|
|
||||||
|
// Show the modal
|
||||||
|
deleteModal.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function navigateToParent() {
|
function navigateToParent() {
|
||||||
if (!currentPath) return;
|
if (!currentPath) return;
|
||||||
const parts = currentPath.split('/');
|
const parts = currentPath.split('/');
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 2.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.7 KiB |
Reference in New Issue
Block a user