/* * This file is part of muCommander, http://www.mucommander.com * Copyright (C) 2002-2016 Maxence Bernard * * muCommander is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * muCommander is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.mucommander.desktop; import com.mucommander.commons.file.AbstractFile; import com.mucommander.text.Translator; import com.mucommander.ui.dialog.InformationDialog; import com.mucommander.ui.main.WindowManager; import java.util.List; import java.util.Vector; /** * QueuedTrash is an {@link AbstractTrash} which moves files to the trash asynchroneously. * * <p> * When {@link #moveToTrash(com.mucommander.commons.file.AbstractFile)} is called, the file is added to a queue. * The file is not moved to the trash immediately: the trash will wait a period of {@link #QUEUE_PERIOD} milliseconds * for additional files to be added. If files were added during that period, the trash will wait another period and * so on. When no more files are added were added during the period, {@link #moveToTrash(java.util.Vector)} is called * with the list of queued files to move to the trash. * </p> * * <p> * This mechanism allows to group calls to the underlying trash. It is effective when the atomic operation * of moving a file to the trash has a high cost and {@link #moveToTrash(com.mucommander.commons.file.AbstractFile)} is called * repeatedly. One thing to note is since the move is performed asynchronously, * {@link #moveToTrash(com.mucommander.commons.file.AbstractFile)} returns immediately without waiting for the file to be moved, * {@link #waitForPendingOperations()} can be used to wait for the files to have effectively been moved. * </p> * * @author Maxence Bernard */ public abstract class QueuedTrash extends AbstractTrash { /** Contains the files that are waiting to be moved to the trash */ private final static List<AbstractFile> queuedFiles = new Vector<AbstractFile>(); /** Use to synchronize access to the trash */ protected final static Object moveToTrashLock = new Object(); /** Thread that performs the actual job of moving files to the trash */ protected static Thread moveToTrashThread; /** Amount of time in millisecondes to wait for additional files before moving them to the trash */ protected final static int QUEUE_PERIOD = 1000; /** * Moves the {@link AbstractFile} instances contained in the given <code>Vector</code> to the trash. * Returns <code>true</code> if all files were moved successfully. * * @param queuedFiles a Vector of AbstractFile to move to the trash * @return true if all files were moved successfully */ protected abstract boolean moveToTrash(List<AbstractFile> queuedFiles); ////////////////////////////////// // AbstractTrash implementation // ////////////////////////////////// /** * Implementation notes: this method adds the given file to the queue of files to be moved to the trash and returns * immediately, i.e. without waiting for the file to be moved. The specified file will only be added to the queue if * {@link #canMoveToTrash(com.mucommander.commons.file.AbstractFile)} returned <code>true</code> for it. * Since the actual move is performed asynchronously, this method has no way of * knowing if the file was successfully moved to the trash. So this method will return <code>true</code> if the * given file has been scheduled to be moved to the trash, but it may end up failing to be moved for whatever reason. */ @Override public boolean moveToTrash(AbstractFile file) { if(!canMoveToTrash(file)) return false; synchronized(moveToTrashLock) { // Queue the given file queuedFiles.add(file); // Create a new thread and start it if one isn't already running if(moveToTrashThread ==null) { moveToTrashThread = new MoveToTrashThread(); moveToTrashThread.start(); } } return true; } @Override public void waitForPendingOperations() { synchronized(moveToTrashLock) { if(moveToTrashThread!=null) { try { // Wait until moveToTrashThread wakes this thread up moveToTrashLock.wait(); } catch(InterruptedException e) { } } } } /////////////////// // Inner classes // /////////////////// /** * Performs the actual job of moving files to the trash. * * <p>The thread starts by waiting {@link com.mucommander.desktop.osx.OSXTrash#QUEUE_PERIOD} milliseconds before moving them to give additional * files a chance to be queued and regrouped as a single call to {@link QueuedTrash#moveToTrash(java.util.List)}. * If more files were queued during that period, the thread will wait an additional {@link com.mucommander.desktop.osx.OSXTrash# QUEUE_PERIOD}, * and so on.<p> */ private class MoveToTrashThread extends Thread { @Override public void run() { // Loops until no files were added during the sleep period int queueSize; do { queueSize = queuedFiles.size(); try { Thread.sleep(QUEUE_PERIOD); } catch(InterruptedException e) {} } while(queueSize!=queuedFiles.size()); synchronized(moveToTrashLock) { // Files can't be added to queue while files are moved to trash if(!moveToTrash(queuedFiles)) InformationDialog.showErrorDialog(WindowManager.getCurrentMainFrame(), Translator.get("delete_dialog.move_to_trash.option"), Translator.get("delete_dialog.move_to_trash.failed")); queuedFiles.clear(); // Wake up any thread waiting for this thread to be finished moveToTrashLock.notify(); moveToTrashThread = null; } } } }