/*
* Copyright (c) 1998-2017 by Richard A. Wilkes. All rights reserved.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, version 2.0. If a copy of the MPL was not distributed with
* this file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This Source Code Form is "Incompatible With Secondary Licenses", as
* defined by the Mozilla Public License, version 2.0.
*/
package com.trollworks.gcs.common;
import com.trollworks.gcs.app.GCS;
import com.trollworks.toolkit.collections.Stack;
import com.trollworks.toolkit.io.Log;
import com.trollworks.toolkit.utility.FileType;
import com.trollworks.toolkit.utility.PathUtils;
import com.trollworks.toolkit.utility.text.NumericComparator;
import java.awt.EventQueue;
import java.io.IOException;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.List;
/** A thread that periodically updates the set of available list files. */
public class ListCollectionThread extends Thread implements FileVisitor<Path>, Comparator<Object> {
private static final ListCollectionThread INSTANCE;
private List<Object> mLists;
private List<Object> mCurrent;
private Stack<List<Object>> mStack;
private List<ListCollectionListener> mListeners;
static {
INSTANCE = new ListCollectionThread();
INSTANCE.start();
}
/** @return The one and only instance of this thread. */
public static final ListCollectionThread get() {
return INSTANCE;
}
private ListCollectionThread() {
super("List Collection"); //$NON-NLS-1$
setPriority(NORM_PRIORITY);
setDaemon(true);
mListeners = new ArrayList<>();
}
/** @param listener The {@link ListCollectionListener} to add. */
public synchronized void addListener(ListCollectionListener listener) {
mListeners.add(listener);
}
/** @param listener The {@link ListCollectionListener} to remove. */
public synchronized void removeListener(ListCollectionListener listener) {
mListeners.remove(listener);
}
protected void notifyListeners() {
ListCollectionListener[] listeners;
synchronized (this) {
listeners = mListeners.toArray(new ListCollectionListener[mListeners.size()]);
}
List<Object> lists = getLists();
for (ListCollectionListener listener : listeners) {
try {
listener.dataFileListUpdated(lists);
} catch (Throwable throwable) {
Log.error(throwable);
}
}
}
/** @return The current list of lists. */
public List<Object> getLists() {
try {
while (mLists == null) {
sleep(100);
}
} catch (InterruptedException outerIEx) {
// Someone is trying to terminate us... let them.
}
return mLists == null ? new ArrayList<>() : mLists;
}
@Override
public void run() {
try {
while (true) {
List<Object> lists = collectLists();
if (!lists.equals(mLists)) {
mLists = lists;
EventQueue.invokeLater(() -> notifyListeners());
}
sleep(5000);
}
} catch (InterruptedException outerIEx) {
// Someone is trying to terminate us... let them.
}
}
@SuppressWarnings("unchecked")
private List<Object> collectLists() {
mCurrent = new ArrayList<>();
mStack = new Stack<>();
try {
Files.walkFileTree(GCS.getLibraryRootPath(), EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, this);
} catch (Exception exception) {
Log.error(exception);
}
List<Object> result = mCurrent;
mCurrent = null;
mStack = null;
return result.isEmpty() ? new ArrayList<>() : (List<Object>) result.get(0);
}
private static boolean shouldSkip(Path path) {
return path.getFileName().toString().startsWith("."); //$NON-NLS-1$
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
if (shouldSkip(dir)) {
return FileVisitResult.SKIP_SUBTREE;
}
mStack.push(mCurrent);
mCurrent = new ArrayList<>();
mCurrent.add(dir.getFileName().toString());
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (!shouldSkip(file)) {
String ext = PathUtils.getExtension(file.getFileName());
for (String one : FileType.getOpenableExtensions()) {
if (one.equalsIgnoreCase(ext)) {
mCurrent.add(file);
break;
}
}
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exception) throws IOException {
Log.error(exception);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exception) throws IOException {
if (exception != null) {
Log.error(exception);
}
Collections.sort(mCurrent, this);
List<Object> restoring = mStack.pop();
if (mCurrent.size() > 1) {
restoring.add(mCurrent);
}
mCurrent = restoring;
return FileVisitResult.CONTINUE;
}
@Override
public int compare(Object o1, Object o2) {
return NumericComparator.compareStrings(getName(o1), getName(o2));
}
private static final String getName(Object obj) {
if (obj instanceof Path) {
return ((Path) obj).getFileName().toString();
}
if (obj instanceof List) {
return ((List<?>) obj).get(0).toString();
}
return ""; //$NON-NLS-1$
}
}