/*
* Copyright 2004 - 2008 Christian Sprajc. All rights reserved.
*
* This file is part of PowerFolder.
*
* PowerFolder 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.
*
* PowerFolder 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 PowerFolder. If not, see <http://www.gnu.org/licenses/>.
*
* $Id$
*/
package de.dal33t.powerfolder.transfer;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Queue;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentLinkedQueue;
import de.dal33t.powerfolder.Constants;
import de.dal33t.powerfolder.Controller;
import de.dal33t.powerfolder.PFComponent;
import de.dal33t.powerfolder.disk.Folder;
import de.dal33t.powerfolder.light.DirectoryInfo;
import de.dal33t.powerfolder.light.FileHistory;
import de.dal33t.powerfolder.light.FileHistory.Conflict;
import de.dal33t.powerfolder.light.FileInfo;
import de.dal33t.powerfolder.light.FolderInfo;
import de.dal33t.powerfolder.message.FileHistoryReply;
import de.dal33t.powerfolder.util.ProblemUtil;
import de.dal33t.powerfolder.util.Profiling;
import de.dal33t.powerfolder.util.ProfilingEntry;
import de.dal33t.powerfolder.util.Reject;
/**
* The filerequestor handles all stuff about requesting new downloads
*
* @author <a href="mailto:totmacher@powerfolder.com">Christian Sprajc </a>
* @version $Revision: 1.18 $
*/
public class FileRequestor extends PFComponent {
private Thread myThread;
private final Queue<Folder> folderQueue;
private final Queue<FileInfo> pendingRequests;
public FileRequestor(Controller controller) {
super(controller);
folderQueue = new ConcurrentLinkedQueue<Folder>();
pendingRequests = new ConcurrentLinkedQueue<FileInfo>();
}
/**
* Starts the file requestor
*/
public void start() {
myThread = new Thread(new Worker(), "FileRequestor");
myThread.setPriority(Thread.MIN_PRIORITY);
myThread.start();
logFine("Started");
long waitTime = Controller.getWaitTime() * 12;
getController()
.scheduleAndRepeat(new PeriodicalTriggerTask(), waitTime);
}
/**
* Triggers the worker to request new files on the given folder.
*
* @param foInfo
* the folder to request files on
*/
public void triggerFileRequesting(FolderInfo foInfo) {
Reject.ifNull(foInfo, "Folder is null");
Folder folder = foInfo.getFolder(getController());
if (folder == null) {
logWarning("Folder not joined, not requesting files: " + foInfo);
return;
}
synchronized (folderQueue) {
if (folderQueue.contains(folder)) {
return;
}
folderQueue.offer(folder);
folderQueue.notify();
}
}
/**
* Triggers to request missing files on all folders.
*
* @see #triggerFileRequesting(FolderInfo) for single folder file requesting
* (=lower CPU usage)
*/
public void triggerFileRequesting() {
ProfilingEntry pe = Profiling.start();
Collection<Folder> folders = getController().getFolderRepository()
.getFolders(true);
synchronized (folderQueue) {
for (Folder folder : folders) {
if (folderQueue.contains(folder)) {
continue;
}
folderQueue.offer(folder);
}
folderQueue.notifyAll();
}
Profiling.end(pe, 100);
}
/**
* Stops file requsting
*/
public void shutdown() {
if (myThread != null) {
myThread.interrupt();
}
synchronized (folderQueue) {
folderQueue.notifyAll();
}
logFine("Stopped");
}
/**
* Checks all new received filelists from member and downloads unknown/new
* files, force the settings.
* <p>
* FIXME: Does requestFromFriends work?
*
* @param folder
* @param autoDownload
*/
public void requestMissingFiles(Folder folder, boolean autoDownload) {
// Dont request files until has own database
if (!folder.hasOwnDatabase()) {
return;
}
Collection<FileInfo> incomingFiles = folder.getIncomingFiles(false,
Constants.MAX_DLS_FROM_LAN_MEMBER * 2);
retrieveNewestVersions(folder, incomingFiles, autoDownload);
}
/**
* Requests missing files for autodownload. May not request any files if
* folder is not in auto download sync profile. Checks the syncprofile for
* each file. Sysncprofile may change in the meantime.
*
* @param folder
* the folder to request missing files on.
*/
private void requestMissingFilesForAutodownload(Folder folder) {
if (getController().isPaused()) {
if (isFiner()) {
logFiner("Paused: Skipping request of new files.");
}
return;
}
if (!folder.getSyncProfile().isAutodownload()) {
if (isFiner()) {
logFiner("Skipping " + folder + ". not on auto donwload");
}
return;
}
if (isFiner()) {
logFiner("Requesting files (autodownload), has own DB? "
+ folder.hasOwnDatabase());
}
if (!folder.isStarted()) {
if (isFiner()) {
logFiner("Not requesting files. Folder not started yet "
+ folder);
}
return;
}
if (folder.isDeviceDisconnected()) {
if (isFine()) {
logFine("Not requesting files. Device disconnected of "
+ folder);
}
return;
}
if (!folder.hasOwnDatabase()) {
if (isWarning()) {
logWarning("Not requesting files. No own database for "
+ folder);
}
return;
}
if (folder.getConnectedMembersCount() == 0) {
if (isFiner()) {
logFiner("Not requesting files. No member connected on "
+ folder);
}
return;
}
if (!folder.hasUploadCapacity()) {
if (isFiner()) {
logFiner("Not requesting files. Members of folder don't have upload capacity "
+ folder);
}
return;
}
Collection<FileInfo> incomingFiles = folder.getIncomingFiles(false,
Constants.MAX_DLS_FROM_LAN_MEMBER * 2);
if (incomingFiles.isEmpty()) {
if (isFiner()) {
logFiner("Not requesting files. No incoming files " + folder);
}
return;
}
retrieveNewestVersions(folder, incomingFiles, true);
}
/**
* Utility method that will retrieve (download or make directory) the newest
* version of all given files provided that the given filter accepts a file.
*
* @param folder
* @param fInfos
* @param autoDownload
*/
private void retrieveNewestVersions(Folder folder,
Collection<FileInfo> fInfos, boolean autoDownload)
{
TransferManager tm = getController().getTransferManager();
List<FileInfo> filesToDownload = new ArrayList<FileInfo>(fInfos.size());
for (FileInfo fInfo : fInfos) {
if (myThread.isInterrupted()) {
return;
}
if (fInfo.isDeleted()) {
// Dont retrieve deleted. done in a different place:
// Folder.syncRemoteDeletions.
continue;
}
if (fInfo.isFile()) {
if (tm.isDownloadingActive(fInfo)
|| tm.isDownloadingPending(fInfo))
{
// Already downloading/file
continue;
}
}
if (fInfo.isFile()) {
filesToDownload.add(fInfo);
} else if (fInfo.isDiretory()) {
createDirectory((DirectoryInfo) fInfo);
}
}
if (filesToDownload.isEmpty()) {
// Quit here.
return;
}
Collections.sort(filesToDownload, folder.getTransferPriorities()
.getComparator());
for (FileInfo fInfo : filesToDownload) {
FileInfo newestVersion = fInfo.getNewestVersion(getController()
.getFolderRepository());
if (newestVersion == null) {
logWarning("Unable to download. Newest version not found: "
+ fInfo.toDetailString());
continue;
}
prepareDownload(newestVersion, autoDownload);
}
}
private void createDirectory(DirectoryInfo dirInfo) {
File dirFile = dirInfo.getDiskFile(getController()
.getFolderRepository());
Folder folder = dirInfo
.getFolder(getController().getFolderRepository());
if (folder == null || dirFile == null) {
logWarning("Unable to created directory. not longer on folder: "
+ dirInfo.toDetailString());
return;
}
folder.scanDirectory(dirInfo, dirFile);
if (isFine()) {
logFine("Synced directory: " + dirInfo.toDetailString());
}
}
private void prepareDownload(FileInfo newestVersion, boolean autoDownload) {
TransferManager tm = getController().getTransferManager();
tm.downloadNewestVersion(newestVersion, autoDownload);
}
/**
* Called if a FileHistory was received.
*
* @param fhReply
*/
public void receivedFileHistory(final FileHistoryReply fhReply) {
Reject.notNull(fhReply, "fhReply");
if (!pendingRequests.remove(fhReply.getRequestFileInfo())) {
logWarning("Received FileHistory for unrequested FileInfo "
+ fhReply.getRequestFileInfo());
return;
}
getController().getIOProvider().startIO(new Runnable() {
public void run() {
checkForConflict(fhReply);
}
});
}
private void checkForConflict(FileHistoryReply fhRepl) {
FileInfo fi = fhRepl.getRequestFileInfo();
FileHistory fh = fhRepl.getFileHistory();
if (fh == null) {
logFine("Remote client claims not to have a history for " + fi
+ ", not downloading!");
logFine("That was a lie, since there are no FileHistories I'll download it anyways!");
// FIXME But it should not download the file and
// abort instead if FileHistories come available!
getController().getTransferManager()
.downloadNewestVersion(fi, true);
} else {
FileHistory localHistory = fi.getFolder(
getController().getFolderRepository()).getDAO().getFileHistory(
fi);
if (localHistory == null) {
logSevere("Local FileHistory missing for " + fi
+ ", not downloading!");
} else {
Conflict conflict = localHistory.getConflictWith(fh);
if (conflict != null) {
if (ProblemUtil.resolveConflict(conflict)) {
// The code currently only supports
// autoDownloads!
getController().getTransferManager()
.downloadNewestVersion(fi, true);
}
} else {
// The code currently only supports
// autoDownloads!
getController().getTransferManager().downloadNewestVersion(
fi, true);
}
}
}
}
/**
* Requests periodically new files from the folders
*
* @author <a href="mailto:totmacher@powerfolder.com">Christian Sprajc </a>
* @version $Revision: 1.18 $
*/
private class Worker implements Runnable {
public void run() {
while (!myThread.isInterrupted()) {
try {
synchronized (folderQueue) {
if (folderQueue.isEmpty()) {
folderQueue.wait();
}
}
int nFolders = 0;
if (isFiner()) {
logFiner("Started requesting files");
}
long start = System.currentTimeMillis();
for (Folder folder : folderQueue) {
// if (i % 100 == 0) {
// if (folderQueue.size() < 5) {
// logWarning("Still in queue: " + folderQueue);
// } else {
// logWarning("Still in queue: "
// + folderQueue.size());
// }
// }
nFolders++;
try {
folderQueue.remove(folder);
// Give CPU a bit time.
if (nFolders % 5 == 0) {
Thread.sleep(1);
}
requestMissingFilesForAutodownload(folder);
} catch (RuntimeException e) {
logSevere("RuntimeException: " + e.toString(), e);
}
}
if (isFine()) {
long took = System.currentTimeMillis() - start;
logFine("Requesting files for " + nFolders
+ " folder(s) took " + took + "ms.");
}
// Sleep a bit to avoid spamming
Thread.sleep(10);
} catch (InterruptedException e) {
logFine("Stopped");
logFiner(e);
break;
}
}
}
}
/**
* Periodically triggers the file requesting on all folders.
*/
private final class PeriodicalTriggerTask extends TimerTask {
@Override
public void run() {
triggerFileRequesting();
}
}
}