mirror of
https://github.com/badvision/jace.git
synced 2024-06-10 07:29:30 +00:00
476 lines
16 KiB
Java
476 lines
16 KiB
Java
/*
|
|
* Copyright (C) 2013 brobert.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
* MA 02110-1301 USA
|
|
*/
|
|
package jace.library;
|
|
|
|
import jace.core.Utility;
|
|
import jace.library.MediaEntry.MediaFile;
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.FileNotFoundException;
|
|
import java.io.FileOutputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.ObjectInputStream;
|
|
import java.io.ObjectOutputStream;
|
|
import java.io.Serializable;
|
|
import java.net.MalformedURLException;
|
|
import java.net.URI;
|
|
import java.net.URISyntaxException;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.Iterator;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
|
|
/**
|
|
* Holds all information about media titles, manages low-level operations of
|
|
* downloading images from online sources, and also manages the persistence of
|
|
* the media library
|
|
*
|
|
* @author brobert
|
|
*/
|
|
public class MediaCache implements Serializable {
|
|
|
|
public static int DELAY_BEFORE_PERSISTING_LIBRARY = 2000;
|
|
public static MediaCache LOCAL_LIBRARY;
|
|
|
|
public static MediaEntry getMediaFromFile(File draggedFile) {
|
|
MediaEntry entry = new MediaEntry();
|
|
MediaFile file = new MediaFile();
|
|
file.path = draggedFile;
|
|
file.temporary = false;
|
|
file.activeVersion = true;
|
|
entry.files = new ArrayList<>();
|
|
entry.files.add(file);
|
|
entry.isLocal = true;
|
|
entry.type = DiskType.determineType(draggedFile);
|
|
return entry;
|
|
}
|
|
|
|
public static MediaEntry getMediaFromUrl(String url) {
|
|
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
|
|
}
|
|
|
|
public Set<Long> favorites;
|
|
public Map<String, Set<Long>> nameLookup;
|
|
public Map<String, Set<Long>> categoryLookup;
|
|
public Map<String, Set<Long>> keywordLookup;
|
|
public Map<Long, MediaEntry> mediaLookup;
|
|
public long lastDirtyMarker;
|
|
|
|
public MediaCache() {
|
|
favorites = new HashSet<>();
|
|
nameLookup = new HashMap<>();
|
|
categoryLookup = new HashMap<>();
|
|
keywordLookup = new HashMap<>();
|
|
mediaLookup = new HashMap<>();
|
|
}
|
|
|
|
public static MediaCache getLocalLibrary() {
|
|
if (LOCAL_LIBRARY == null) {
|
|
LOCAL_LIBRARY = new MediaCache();
|
|
LOCAL_LIBRARY.readLibraryFromDisk();
|
|
}
|
|
return LOCAL_LIBRARY;
|
|
|
|
}
|
|
|
|
private void cleanup() {
|
|
cleanup(nameLookup);
|
|
cleanup(categoryLookup);
|
|
cleanup(keywordLookup);
|
|
}
|
|
|
|
private void cleanup(Map<String, Set<Long>> lookup) {
|
|
Set<String> remove = new HashSet<>();
|
|
lookup.entrySet().stream().forEach((entry) -> {
|
|
if (entry.getValue() == null || entry.getValue().isEmpty()) {
|
|
remove.add(entry.getKey());
|
|
} else {
|
|
boolean hasSomething = false;
|
|
for (Iterator<Long> l = entry.getValue().iterator(); l.hasNext();) {
|
|
if (mediaLookup.containsKey(l.next())) {
|
|
hasSomething = true;
|
|
} else {
|
|
l.remove();
|
|
}
|
|
}
|
|
if (!hasSomething) {
|
|
remove.add(entry.getKey());
|
|
}
|
|
}
|
|
});
|
|
lookup.keySet().removeAll(remove);
|
|
}
|
|
|
|
public void add(MediaEntry e) {
|
|
e.isLocal = this.equals(LOCAL_LIBRARY);
|
|
// Randomize ID if it is not already set
|
|
// This is a combination of the string hash as well as a 8-bit sequence number
|
|
if (e.id == 0 || e.id == -1) {
|
|
e.id = e.name.hashCode();
|
|
e.id <<= 8;
|
|
while (mediaLookup.containsKey(e.id)) {
|
|
e.id++;
|
|
}
|
|
}
|
|
mediaLookup.put(e.id, e);
|
|
cacheEntry(nameLookup, e.name, e.id);
|
|
cacheEntry(categoryLookup, e.category, e.id);
|
|
if (e.favorite) {
|
|
favorites.add(e.id);
|
|
}
|
|
for (String s : e.keywords) {
|
|
cacheEntry(keywordLookup, s, e.id);
|
|
}
|
|
markDirty();
|
|
}
|
|
|
|
public void remove(MediaEntry e) {
|
|
mediaLookup.remove(e.id);
|
|
removeFiles(e);
|
|
cleanup();
|
|
markDirty();
|
|
}
|
|
|
|
public void update(MediaEntry e) {
|
|
remove(e);
|
|
add(e);
|
|
}
|
|
|
|
private void cacheEntry(Map<String, Set<Long>> cache, String key, long id) {
|
|
Set<Long> ids = cache.get(key);
|
|
if (ids == null) {
|
|
ids = new HashSet<>();
|
|
cache.put(key, ids);
|
|
}
|
|
ids.add(id);
|
|
}
|
|
|
|
public static File getMediaLibraryFolder() {
|
|
String userHome = System.getProperty("user.home");
|
|
if (userHome == null || userHome.equals("")) {
|
|
userHome = ".";
|
|
}
|
|
File f = new File(new File(userHome, ".jace"), "mediaLibrary");
|
|
if (!f.exists()) {
|
|
f.mkdirs();
|
|
}
|
|
return f;
|
|
}
|
|
|
|
// Remove file(s) associated with media entry
|
|
private void removeFiles(MediaEntry e) {
|
|
if (e.files == null) {
|
|
return;
|
|
}
|
|
e.files.stream().forEach((f) -> {
|
|
f.path.delete();
|
|
});
|
|
Utility.gripe("All disk images for " + e.name + " have been deleted.");
|
|
}
|
|
|
|
public MediaEntry.MediaFile getCurrentFile(MediaEntry e, boolean isPermanent) {
|
|
if (e == null) {
|
|
return null;
|
|
}
|
|
if (e.files == null || e.files.isEmpty()) {
|
|
e.files = new ArrayList<>();
|
|
getLocalLibrary().add(e);
|
|
getLocalLibrary().createBlankFile(e, "Initial", !isPermanent);
|
|
getLocalLibrary().downloadImage(e, e.files.get(0), true);
|
|
}
|
|
for (MediaEntry.MediaFile f : e.files) {
|
|
if (f.activeVersion) {
|
|
return f;
|
|
}
|
|
}
|
|
e.files.get(0).activeVersion = true;
|
|
return e.files.get(0);
|
|
}
|
|
|
|
public void saveFile(MediaEntry e, InputStream data) {
|
|
// saveFile(getCurrentFile(e, MediaLibrary.CREATE_LOCAL_ON_SAVE), data);
|
|
}
|
|
|
|
public void saveFile(MediaFile f, InputStream data) {
|
|
// TODO: If file is temporary but is supposed to be created local when saved, then move the save to a permanent file!!
|
|
// if (f.temporary && MediaLibrary.CREATE_LOCAL_ON_SAVE) {
|
|
// f = convertTemporaryFileToLocal(f);
|
|
// }
|
|
FileOutputStream fos = null;
|
|
f.lastWritten = System.currentTimeMillis();
|
|
try {
|
|
fos = new FileOutputStream(f.path, false);
|
|
byte[] b = new byte[4096];
|
|
while (data.available() > 0) {
|
|
int read = data.read(b);
|
|
fos.write(b, 0, read);
|
|
}
|
|
fos.close();
|
|
} catch (FileNotFoundException ex) {
|
|
Logger.getLogger(MediaCache.class.getName()).log(Level.SEVERE, null, ex);
|
|
Utility.gripe("Could not write disk for " + f.path + " -- File not found!");
|
|
} catch (IOException ex) {
|
|
Logger.getLogger(MediaCache.class.getName()).log(Level.SEVERE, null, ex);
|
|
Utility.gripe("Could not write disk for " + f.path + " -- I/O Exception: " + ex.getMessage());
|
|
} finally {
|
|
try {
|
|
if (fos != null) {
|
|
fos.close();
|
|
}
|
|
} catch (IOException ex) {
|
|
Logger.getLogger(MediaCache.class.getName()).log(Level.SEVERE, null, ex);
|
|
}
|
|
}
|
|
}
|
|
boolean disableWrites = false;
|
|
|
|
public void readLibraryFromDisk() {
|
|
disableWrites = true;
|
|
ObjectInputStream in = null;
|
|
try {
|
|
File mediaCatalogFile = getMediaLibaryCatalog();
|
|
if (!mediaCatalogFile.exists()) {
|
|
return;
|
|
}
|
|
FileInputStream fileStream = new FileInputStream(mediaCatalogFile);
|
|
in = new ObjectInputStream(fileStream);
|
|
while (fileStream.available() > 0) {
|
|
MediaEntry e = (MediaEntry) in.readObject();
|
|
add(e);
|
|
}
|
|
} catch (IOException | ClassNotFoundException ex) {
|
|
Utility.gripe(ex.getMessage());
|
|
Logger.getLogger(MediaCache.class.getName()).log(Level.SEVERE, null, ex);
|
|
} finally {
|
|
disableWrites = false;
|
|
try {
|
|
if (in != null) {
|
|
in.close();
|
|
}
|
|
} catch (IOException ex) {
|
|
Logger.getLogger(MediaCache.class.getName()).log(Level.SEVERE, null, ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void writeLibraryToDisk() {
|
|
ObjectOutputStream out = null;
|
|
try {
|
|
File mediaCatalogFile = getMediaLibaryCatalog();
|
|
out = new ObjectOutputStream(new FileOutputStream(mediaCatalogFile));
|
|
for (MediaEntry e : mediaLookup.values()) {
|
|
out.writeObject(e);
|
|
}
|
|
out.close();
|
|
} catch (FileNotFoundException ex) {
|
|
Logger.getLogger(MediaCache.class.getName()).log(Level.SEVERE, null, ex);
|
|
} catch (IOException ex) {
|
|
Logger.getLogger(MediaCache.class.getName()).log(Level.SEVERE, null, ex);
|
|
} finally {
|
|
try {
|
|
if (out != null) {
|
|
out.close();
|
|
}
|
|
} catch (IOException ex) {
|
|
Logger.getLogger(MediaCache.class.getName()).log(Level.SEVERE, null, ex);
|
|
}
|
|
}
|
|
}
|
|
Thread writerWorker;
|
|
|
|
public void markDirty() {
|
|
// We don't really care if a remote library is changing
|
|
if (disableWrites || !this.equals(getLocalLibrary())) {
|
|
return;
|
|
}
|
|
lastDirtyMarker = System.nanoTime();
|
|
if (writerWorker == null || !writerWorker.isAlive()) {
|
|
writerWorker = new Thread(new Runnable() {
|
|
long timeCheck = 0;
|
|
|
|
@Override
|
|
public void run() {
|
|
while (true) {
|
|
try {
|
|
Thread.sleep(DELAY_BEFORE_PERSISTING_LIBRARY / 2);
|
|
// Wait half the delay before capturing the value
|
|
// this will help avoid unnecessary delays when the
|
|
// library is updated a bunch in a small interval of time
|
|
timeCheck = lastDirtyMarker;
|
|
Thread.sleep(DELAY_BEFORE_PERSISTING_LIBRARY / 2);
|
|
} catch (InterruptedException ex) {
|
|
Logger.getLogger(MediaCache.class.getName()).log(Level.SEVERE, null, ex);
|
|
continue;
|
|
}
|
|
// If the invalidation time stamp changed again wait longer
|
|
if (timeCheck != lastDirtyMarker) {
|
|
continue;
|
|
}
|
|
writeLibraryToDisk();
|
|
// If the invalidation stamp wasn't changed then the worker can exit
|
|
if (timeCheck == lastDirtyMarker) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
writerWorker.start();
|
|
}
|
|
}
|
|
|
|
private MediaFile downloadTempCopy(MediaEntry e) {
|
|
downloadImage(e, getCurrentFile(e, false), isDownloading);
|
|
return getCurrentFile(e, false);
|
|
}
|
|
static boolean isDownloading = false;
|
|
|
|
public void downloadImage(final MediaEntry e, final MediaFile target, boolean wait) {
|
|
isDownloading = true;
|
|
Utility.runModalProcess("Loading disk image...", () -> {
|
|
InputStream in = null;
|
|
try {
|
|
URI uri = null;
|
|
try {
|
|
uri = new URI(e.source);
|
|
} catch (URISyntaxException ex) {
|
|
File f = new File(e.source);
|
|
if (f.exists()) {
|
|
uri = f.toURI();
|
|
}
|
|
}
|
|
if (uri == null) {
|
|
Utility.gripe("Unable to resolve path: " + e.source);
|
|
return;
|
|
}
|
|
in = uri.toURL().openStream();
|
|
saveFile(target, in);
|
|
} catch (MalformedURLException ex) {
|
|
Utility.gripe("Unable to resolve path: " + e.source);
|
|
Logger.getLogger(MediaCache.class.getName()).log(Level.SEVERE, null, ex);
|
|
} catch (IOException ex) {
|
|
Utility.gripe("Unable to download file: " + ex.getMessage());
|
|
Logger.getLogger(MediaCache.class.getName()).log(Level.SEVERE, null, ex);
|
|
} finally {
|
|
isDownloading = false;
|
|
try {
|
|
if (in != null) {
|
|
in.close();
|
|
}
|
|
} catch (IOException ex) {
|
|
Logger.getLogger(MediaCache.class.getName()).log(Level.SEVERE, null, ex);
|
|
}
|
|
}
|
|
});
|
|
if (wait) {
|
|
int timeout = 10000;
|
|
while (timeout > 0 && isDownloading) {
|
|
try {
|
|
Thread.sleep(100);
|
|
} catch (InterruptedException ex) {
|
|
return;
|
|
}
|
|
timeout -= 100;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void createBlankFile(MediaEntry e, String label, boolean isTemporary) {
|
|
MediaFile f = new MediaEntry.MediaFile();
|
|
e.files.add(f);
|
|
f.activeVersion = true;
|
|
f.checksum = 0L;
|
|
f.label = label;
|
|
f.lastWritten = System.currentTimeMillis();
|
|
f.temporary = isTemporary;
|
|
// Now generate new file path
|
|
File mediaFolder = isTemporary ? getTempDirectory() : getMediaLibraryFolder();
|
|
String name = e.name.replaceAll("[^0-9A-Za-z]", "");
|
|
String s1 = e.name.length() > 0 ? name.substring(0, 1) : "_";
|
|
String s2 = e.name.length() > 1 ? name.substring(1, 2) : "_";
|
|
File sub1 = new File(mediaFolder, "_" + s1);
|
|
File sub2 = new File(sub1, "_" + s2);
|
|
sub2.mkdirs();
|
|
f.path = new File(sub2, name + "_" + System.nanoTime());
|
|
if (isTemporary) {
|
|
sub1.deleteOnExit();
|
|
sub2.deleteOnExit();
|
|
f.path.deleteOnExit();
|
|
}
|
|
}
|
|
|
|
private MediaFile resolveLocalCopy(MediaEntry e) {
|
|
if (!e.isLocal) {
|
|
e = findLocalEntry(e);
|
|
}
|
|
// If this is a local entry, load the current file
|
|
if (e.isLocal) {
|
|
MediaFile f = getCurrentFile(e, true);
|
|
if (f != null && f.path.exists()) {
|
|
return f;
|
|
}
|
|
}
|
|
// If there is no current file, download it
|
|
// if (MediaLibrary.CREATE_LOCAL_ON_LOAD) {
|
|
// getLocalLibrary().add(e);
|
|
// MediaFile f = getCurrentFile(e, true);
|
|
// downloadImage(e, f, true);
|
|
// return f;
|
|
// } else {
|
|
return downloadTempCopy(e);
|
|
// }
|
|
|
|
}
|
|
|
|
public MediaEntry findLocalEntry(MediaEntry e) {
|
|
for (MediaEntry entry : getLocalLibrary().mediaLookup.values()) {
|
|
if (entry.source.equals(e.source))
|
|
return entry;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private File getTempDirectory() {
|
|
String temp = System.getProperty("java.io.tmpdir");
|
|
File tempDir = null;
|
|
if (temp != null) {
|
|
tempDir = new File(temp);
|
|
if (tempDir.exists() && tempDir.isDirectory()) {
|
|
return tempDir;
|
|
}
|
|
tempDir = new File(getMediaLibraryFolder(), "temp");
|
|
tempDir.mkdirs();
|
|
tempDir.deleteOnExit();
|
|
}
|
|
return tempDir;
|
|
}
|
|
|
|
private MediaFile convertTemporaryFileToLocal(MediaFile f) {
|
|
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
|
|
}
|
|
|
|
private File getMediaLibaryCatalog() {
|
|
return new File(getMediaLibraryFolder(), "mediacache.db");
|
|
}
|
|
}
|