package org.rr.jeborker.app;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import org.rr.commons.log.LoggerFactory;
import org.rr.commons.mufs.IResourceHandler;
import org.rr.commons.mufs.ResourceHandlerFactory;
import org.rr.commons.utils.DateUtils;
import org.rr.commons.utils.ReflectionUtils;
import org.rr.commons.utils.UtilConstants;
import org.rr.jeborker.Jeboorker;
import org.rr.jeborker.app.preferences.PreferenceStoreFactory;
import org.rr.jeborker.db.item.EbookPropertyItem;
import org.rr.jeborker.db.item.EbookPropertyItemUtils;
import org.rr.jeborker.gui.action.ActionFactory;
import org.rr.jeborker.gui.action.ActionUtils;
import org.rr.jeborker.gui.action.ApplicationAction;
public class FileRefreshBackground {
private static FileRefreshBackground singleton;
private static final Set<EbookPropertyItem> items = Collections.synchronizedSet(new HashSet<EbookPropertyItem>());
private static int isDisabled = 0;
private FileRefreshBackground() {
}
public static synchronized FileRefreshBackground getInstance() {
if (singleton == null) {
singleton = new FileRefreshBackground();
Jeboorker.APPLICATION_THREAD_POOL.submit(new Worker());
}
return singleton;
}
public void addEbook(EbookPropertyItem item) {
if(isDisabled == 0) {
synchronized (items) {
items.add(item);
}
}
}
public void addEbooks(List<EbookPropertyItem> changedResources) {
if(isDisabled == 0 && !changedResources.isEmpty()) {
synchronized (items) {
items.addAll(changedResources);
}
}
}
/**
* Disabled the {@link FileRefreshBackground}. The {@link #addEbook(EbookPropertyItem)} method
* did no longer add books if the {@link FileRefreshBackground} is set to disabled.
* @param disabled <code>true</code> for disabling the {@link FileRefreshBackground} and <code>false</code>
* to enable it.
*/
public static void setDisabled(boolean disabled) {
if(disabled) {
isDisabled ++;
} else {
isDisabled --;
}
}
/**
* Starts the given {@link Runnable} and takes sure that the background refresh is disabled while running.
* @param run The {@link Runnable} to be started while no file refresh will be detected.
*/
public static void runWithDisabledRefresh(Runnable run) {
setDisabled(true);
try {
run.run();
} finally {
setDisabled(false);
}
}
/**
* Tells if the background refresh is currently disabled or not.
*/
public static boolean isDisabled() {
return isDisabled != 0;
}
private static class Worker implements Runnable {
@Override
public void run() {
while (true) {
while (!items.isEmpty() && isDisabled == 0) {
EbookPropertyItem ebookPropertyItem = null;
try {
synchronized (items) {
List<EbookPropertyItem> processedItems = new ArrayList<>();
Iterator<EbookPropertyItem> itemsIter = items.iterator();
while(itemsIter.hasNext()) {
ebookPropertyItem = itemsIter.next();
if(ebookPropertyItem != null) {
this.processItem(ebookPropertyItem);
processedItems.add(ebookPropertyItem);
}
}
items.removeAll(processedItems);
items.remove(null);
}
} catch(Exception e) {
LoggerFactory.log(Level.WARNING, this, "Failed to handle " + ebookPropertyItem + " in background process", e);
}
}
// wait a moment before restart
ReflectionUtils.sleepSilent(1000);
}
}
/**
* Handle these items which are added with the {@link FileRefreshBackground#addEbook(EbookPropertyItem)} method.
*/
private void processItem(EbookPropertyItem ebookPropertyItem) {
IResourceHandler resourceHandler = ebookPropertyItem.getResourceHandler();
deleteOldTempFile(resourceHandler);
if (!resourceHandler.exists()) {
LoggerFactory.getLogger(this).log(Level.INFO, resourceHandler + " no longer exists and will be removed from catalog.");
handleDeletedItem(ebookPropertyItem);
return;
}
if(!isManagedItem(resourceHandler)) {
LoggerFactory.getLogger(this).log(Level.INFO, resourceHandler + " is not a managed resource and will not be refreshed.");
return;
}
if (isRefreshNeeded(ebookPropertyItem, resourceHandler)) {
handleRefreshItem(ebookPropertyItem, resourceHandler);
}
}
/**
* Handle an ebook file that has been changed until the last time.
* @param ebookPropertyItem The ebook file to process.
* @param resourceHandler
*/
private void handleRefreshItem(EbookPropertyItem ebookPropertyItem, IResourceHandler resourceHandler) {
EbookPropertyItem reloadedItem = EbookPropertyItemUtils.reloadEbookPropertyItem(ebookPropertyItem);
if(isRefreshNeeded(reloadedItem, resourceHandler)) {
ApplicationAction refreshAction = ActionFactory.getAction(ActionFactory.COMMON_ACTION_TYPES.REFRESH_ENTRY_ACTION, resourceHandler.toString());
refreshAction.putValue(ApplicationAction.NON_THREADED_ACTION_KEY, Boolean.TRUE);
refreshAction.invokeAction();
LoggerFactory.getLogger(this).log(Level.INFO, "Changed entry " + ebookPropertyItem.getResourceHandler().getName() + " refreshed.");
}
}
/**
* Handle an ebook file that has been deleted.
* @param ebookPropertyItem The ebook file to process.
*/
private void handleDeletedItem(EbookPropertyItem ebookPropertyItem) {
EbookPropertyItem reloadedItem = EbookPropertyItemUtils.reloadEbookPropertyItem(ebookPropertyItem);
if(reloadedItem != null) {
ActionUtils.removeEbookPropertyItem(ebookPropertyItem);
LoggerFactory.getLogger(this).log(Level.INFO, "Removed deleted entry " + ebookPropertyItem.getResourceHandler().getName());
}
}
private boolean isManagedItem(IResourceHandler resourceHandler) {
return PreferenceStoreFactory.getPreferenceStore(PreferenceStoreFactory.DB_STORE).getBasePath().containsBasePathFor(resourceHandler.getResourceString());
}
private boolean isRefreshNeeded(EbookPropertyItem ebookPropertyItem, IResourceHandler resourceHandler) {
if (ebookPropertyItem != null && ebookPropertyItem.getTimestamp() > 0l
&& ebookPropertyItem.getTimestamp() < resourceHandler.getModifiedAt().getTime()) {
return true;
}
return false;
}
/**
* Test if the given temp file is old and moves it to the trash if it is.
* @param tmpFile The file to be deleted.
*/
private void deleteOldTempFile(IResourceHandler ebook) {
final List<IResourceHandler> tmpFiles = ResourceHandlerFactory.getExistingUniqueResourceHandler(ebook, "tmp");
for(IResourceHandler tmpFile : tmpFiles) {
if (tmpFile.exists()) {
final Date modifiedAt = tmpFile.getModifiedAt();
try {
long dateDiff = DateUtils.dateDiff("d", modifiedAt, new Date(), Calendar.MONDAY, UtilConstants.FIRSTJAN1);
if(dateDiff > 1) {
tmpFile.moveToTrash();
LoggerFactory.getLogger().log(Level.INFO, "Moving old temp file " + tmpFile + " to the trash.");
}
} catch (IOException e) {
LoggerFactory.getLogger().log(Level.INFO, "Failed to delete old temp file " + tmpFile, e);
} catch (Exception e) {
LoggerFactory.getLogger().log(Level.INFO, "Failed to identify old temp file " + tmpFile, e);
}
}
}
}
}
}