Cache xattr values so that metadata works as expected at least for the current session even if xattr is not supported by the filesystem and thus metadata can't be persisted

This commit is contained in:
Reinhard Pointner 2016-03-27 16:56:54 +00:00
parent b9fdfcbe10
commit dc22249794
17 changed files with 119 additions and 121 deletions

View File

@ -14,7 +14,7 @@ public enum CacheType {
Daily(Duration.ofHours(18), true),
Ephemeral(Duration.ofHours(2), false);
Ephemeral(Duration.ofHours(4), false);
final long timeToLiveSeconds;
final boolean diskPersistent;
@ -27,7 +27,7 @@ public enum CacheType {
@SuppressWarnings("deprecation")
CacheConfiguration getConfiguration(String name) {
// Strategy.LOCALTEMPSWAP is not restartable so we can't but use the deprecated disk persistent code (see http://stackoverflow.com/a/24623527/1514467)
return new CacheConfiguration().name(name).maxEntriesLocalHeap(diskPersistent ? 200 : 0).maxEntriesLocalDisk(0).eternal(false).timeToLiveSeconds(timeToLiveSeconds).timeToIdleSeconds(timeToLiveSeconds).overflowToDisk(diskPersistent).diskPersistent(diskPersistent);
return new CacheConfiguration().name(name).maxEntriesLocalHeap(diskPersistent ? 200 : 20_000).maxEntriesLocalDisk(0).eternal(false).timeToLiveSeconds(timeToLiveSeconds).timeToIdleSeconds(timeToLiveSeconds).overflowToDisk(diskPersistent).diskPersistent(diskPersistent);
}
}

View File

@ -660,14 +660,14 @@ public class CmdlineOperations implements CmdlineInterface {
}
// write metadata into xattr if xattr is enabled
if (matches != null && renameLog.size() > 0 && (useExtendedFileAttributes() || useCreationDate()) && renameAction != StandardRenameAction.TEST) {
if (matches != null && renameLog.size() > 0 && renameAction != StandardRenameAction.TEST) {
for (Match<File, ?> match : matches) {
File source = match.getValue();
Object infoObject = match.getCandidate();
if (infoObject != null) {
File destination = renameLog.get(source);
if (destination != null && destination.isFile()) {
xattr.storeMetaInfo(destination, infoObject, source.getName());
xattr.setMetaInfo(destination, infoObject, source.getName());
}
}
}
@ -1076,7 +1076,7 @@ public class CmdlineOperations implements CmdlineInterface {
List<String> output = new ArrayList<String>();
for (File file : filter(files, fileFilter)) {
String line = formatter.format(new MediaBindingBean(xattr.readMetaInfo(file), file, null));
String line = formatter.format(new MediaBindingBean(xattr.getMetaInfo(file), file, null));
output.add(line);
}
return output;

View File

@ -227,7 +227,7 @@ public abstract class ScriptShellBaseClass extends Script {
public Movie detectMovie(File file, boolean strict) {
// 1. xattr
Object metaObject = xattr.readMetaInfo(file);
Object metaObject = xattr.getMetaInfo(file);
if (metaObject instanceof Movie) {
return (Movie) metaObject;
}

View File

@ -4,6 +4,7 @@ import static java.nio.charset.StandardCharsets.*;
import static java.util.Arrays.*;
import static java.util.Collections.*;
import static net.filebot.MediaTypes.*;
import static net.filebot.media.XattrMetaInfo.*;
import java.io.File;
import java.io.IOException;
@ -28,7 +29,6 @@ import groovy.lang.Closure;
import net.filebot.MediaTypes;
import net.filebot.MetaAttributeView;
import net.filebot.media.MediaDetection;
import net.filebot.media.MetaAttributes;
import net.filebot.similarity.NameSimilarityMetric;
import net.filebot.similarity.Normalization;
import net.filebot.similarity.SimilarityMetric;
@ -394,7 +394,7 @@ public class ScriptShellMethods {
public static Object getMetadata(File self) {
try {
return new MetaAttributes(self).getObject();
return xattr.getMetaInfo(self);
} catch (Exception e) {
return null;
}

View File

@ -23,7 +23,7 @@ public class ExpressionFileFilter implements FileFilter {
@Override
public boolean accept(File f) {
try {
return filter.matches(new MediaBindingBean(xattr.readMetaInfo(f), f, null));
return filter.matches(new MediaBindingBean(xattr.getMetaInfo(f), f, null));
} catch (Exception e) {
debug.warning(format("Expression failed: %s", e));
return error;

View File

@ -8,6 +8,7 @@ import static net.filebot.format.Define.*;
import static net.filebot.format.ExpressionFormatMethods.*;
import static net.filebot.hash.VerificationUtilities.*;
import static net.filebot.media.MediaDetection.*;
import static net.filebot.media.XattrMetaInfo.*;
import static net.filebot.similarity.Normalization.*;
import static net.filebot.subtitle.SubtitleUtilities.*;
import static net.filebot.util.FileUtilities.*;
@ -44,7 +45,6 @@ import net.filebot.Settings;
import net.filebot.Settings.ApplicationFolder;
import net.filebot.WebServices;
import net.filebot.hash.HashType;
import net.filebot.media.MetaAttributes;
import net.filebot.mediainfo.MediaInfo;
import net.filebot.mediainfo.MediaInfo.StreamKind;
import net.filebot.mediainfo.MediaInfoException;
@ -411,7 +411,7 @@ public class MediaBindingBean {
@Define("xattr")
public Object getMetaAttributesObject() throws Exception {
return new MetaAttributes(getMediaFile()).getObject();
return xattr.getMetaInfo(getMediaFile());
}
@Define("crc32")
@ -1054,11 +1054,7 @@ public class MediaBindingBean {
}
private String getOriginalFileName(File file) {
try {
return getNameWithoutExtension(new MetaAttributes(file).getOriginalName());
} catch (Throwable e) {
return null;
}
return getNameWithoutExtension(xattr.getOriginalName(file));
}
private List<String> getKeywords() {

View File

@ -300,7 +300,7 @@ public class MediaDetection {
// try xattr metadata if enabled
for (File it : files) {
Object metaObject = xattr.readMetaInfo(it);
Object metaObject = xattr.getMetaInfo(it);
if (metaObject instanceof Episode) {
unids.add(((Episode) metaObject).getSeriesName());
}
@ -575,7 +575,7 @@ public class MediaDetection {
List<Movie> options = new ArrayList<Movie>();
// try xattr metadata if enabled
Object metaObject = xattr.readMetaInfo(movieFile);
Object metaObject = xattr.getMetaInfo(movieFile);
if (metaObject instanceof Movie) {
options.add((Movie) metaObject);
}

View File

@ -13,8 +13,8 @@ import net.filebot.MetaAttributeView;
public class MetaAttributes {
private static final String FILENAME_KEY = "net.filebot.filename";
private static final String METADATA_KEY = "net.filebot.metadata";
public static final String FILENAME_KEY = "net.filebot.filename";
public static final String METADATA_KEY = "net.filebot.metadata";
private final BasicFileAttributeView fileAttributeView;
private final MetaAttributeView metaAttributeView;

View File

@ -4,9 +4,11 @@ import static net.filebot.Logging.*;
import static net.filebot.Settings.*;
import java.io.File;
import java.io.IOException;
import java.util.Locale;
import net.filebot.Cache;
import net.filebot.Cache.Compute;
import net.filebot.CacheType;
import net.filebot.WebServices;
import net.filebot.web.Episode;
import net.filebot.web.Movie;
@ -19,6 +21,9 @@ public class XattrMetaInfo {
private final boolean useExtendedFileAttributes;
private final boolean useCreationDate;
private final Cache xattrMetaInfoCache = Cache.getCache(MetaAttributes.METADATA_KEY, CacheType.Ephemeral);
private final Cache xattrOriginalNameCache = Cache.getCache(MetaAttributes.FILENAME_KEY, CacheType.Ephemeral);
public XattrMetaInfo(boolean useExtendedFileAttributes, boolean useCreationDate) {
this.useExtendedFileAttributes = useExtendedFileAttributes;
this.useCreationDate = useCreationDate;
@ -46,60 +51,80 @@ public class XattrMetaInfo {
return -1;
}
public Object readMetaInfo(File file) {
if (useExtendedFileAttributes) {
try {
MetaAttributes attr = new MetaAttributes(file);
Object metadata = attr.getObject();
if (isMetaInfo(metadata)) {
return metadata;
public synchronized Object getMetaInfo(File file) {
return getCachedValue(xattrMetaInfoCache, file, key -> {
Object metadata = new MetaAttributes(file).getObject();
return isMetaInfo(metadata) ? metadata : null;
});
}
public synchronized String getOriginalName(File file) {
return (String) getCachedValue(xattrOriginalNameCache, file, key -> {
return new MetaAttributes(file).getOriginalName();
});
}
private Object getCachedValue(Cache cache, File file, Compute<?> compute) {
// try in-memory cache of previously stored xattr metadata
try {
return cache.computeIfAbsent(file, key -> {
if (useExtendedFileAttributes) {
return compute.apply(key);
}
} catch (Throwable e) {
debug.warning("Failed to read xattr: " + e.getMessage());
}
return null;
});
} catch (Throwable e) {
debug.warning("Failed to read xattr: " + e.getMessage());
}
return null;
}
public void storeMetaInfo(File file, Object model, String original) {
public synchronized void setMetaInfo(File file, Object model, String original) {
// only for Episode / Movie objects
if ((useExtendedFileAttributes || useCreationDate) && isMetaInfo(model) && file.isFile()) {
if (!isMetaInfo(model) || !file.isFile()) {
return;
}
// set creation date to episode / movie release date
if (useCreationDate) {
try {
long t = getTimeStamp(model);
if (t > 0) {
new MetaAttributes(file).setCreationDate(t);
}
} catch (Throwable e) {
debug.warning("Failed to set creation date: " + e.getMessage());
}
}
// store metadata object and original name as xattr
if (useExtendedFileAttributes) {
try {
MetaAttributes attr = new MetaAttributes(file);
// set creation date to episode / movie release date
if (useCreationDate) {
try {
long t = getTimeStamp(model);
if (t > 0) {
attr.setCreationDate(t);
}
} catch (Throwable e) {
if (e.getCause() instanceof IOException) {
e = e.getCause();
}
debug.warning("Failed to set creation date: " + e.getMessage());
}
if (isMetaInfo(model)) {
xattrMetaInfoCache.put(file, model);
attr.setObject(model);
}
// store original name and model as xattr
if (useExtendedFileAttributes) {
try {
if (isMetaInfo(model)) {
attr.setObject(model);
}
if (attr.getOriginalName() == null && original != null && original.length() > 0) {
attr.setOriginalName(original);
}
} catch (Throwable e) {
if (e.getCause() instanceof IOException) {
e = e.getCause();
}
debug.warning("Failed to set xattr: " + e.getMessage());
}
if (original != null && original.length() > 0 && getOriginalName(file) == null) {
xattrOriginalNameCache.put(file, original);
attr.setOriginalName(original);
}
} catch (Throwable t) {
debug.warning("Failed to store xattr: " + t.getMessage());
} catch (Throwable e) {
debug.warning("Failed to set xattr: " + e.getMessage());
}
}
}
public synchronized void clear(File file) {
// clear in-memory cache
xattrMetaInfoCache.remove(file);
xattrOriginalNameCache.remove(file);
if (useExtendedFileAttributes) {
try {
new MetaAttributes(file).clear();
} catch (Throwable e) {
debug.warning("Failed to clear xattr: " + e.getMessage());
}
}
}

View File

@ -26,7 +26,7 @@ public class XattrMetaInfoProvider implements Datasource {
Map<File, Object> result = new LinkedHashMap<File, Object>();
for (File f : files) {
Object metaObject = xattr.readMetaInfo(f);
Object metaObject = xattr.getMetaInfo(f);
if (metaObject != null) {
result.put(f, metaObject);
}

View File

@ -679,7 +679,7 @@ public enum EpisodeMetrics implements SimilarityMetric {
// deserialize MetaAttributes if enabled and available
if (object instanceof File) {
Object metaObject = xattr.readMetaInfo((File) object);
Object metaObject = xattr.getMetaInfo((File) object);
if (metaObject != null) {
return super.getProperties(metaObject);
}

View File

@ -3,6 +3,7 @@ package net.filebot.subtitle;
import static java.util.Collections.*;
import static net.filebot.Logging.*;
import static net.filebot.media.MediaDetection.*;
import static net.filebot.media.XattrMetaInfo.*;
import static net.filebot.similarity.EpisodeMetrics.*;
import static net.filebot.util.FileUtilities.*;
@ -14,7 +15,6 @@ import java.util.WeakHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.filebot.media.MetaAttributes;
import net.filebot.mediainfo.MediaInfo;
import net.filebot.mediainfo.MediaInfo.StreamKind;
import net.filebot.similarity.CrossPropertyMetric;
@ -124,26 +124,20 @@ public enum SubtitleMetrics implements SimilarityMetric {
return (float) match.length() / Math.max(s1.length(), s2.length()) > 0.8 ? 1 : 0;
}
private final Map<File, String> xattrCache = new WeakHashMap<File, String>(64);
@Override
public String normalize(Object obj) {
if (obj instanceof File) {
synchronized (xattrCache) {
return xattrCache.computeIfAbsent((File) obj, (f) -> {
try {
String originalName = new MetaAttributes(f).getOriginalName();
return super.normalize(getNameWithoutExtension(originalName));
} catch (Exception e) {
return super.normalize(getNameWithoutExtension(f.getName()));
}
});
public String normalize(Object object) {
if (object instanceof File) {
File file = (File) object;
String name = xattr.getOriginalName(file);
if (name == null) {
name = file.getName();
}
} else if (obj instanceof OpenSubtitlesSubtitleDescriptor) {
String name = ((OpenSubtitlesSubtitleDescriptor) obj).getName();
return super.normalize(getNameWithoutExtension(name));
} else if (object instanceof OpenSubtitlesSubtitleDescriptor) {
String name = ((OpenSubtitlesSubtitleDescriptor) object).getName();
return super.normalize(name);
}
return super.normalize(obj);
return super.normalize(object);
}
}),

View File

@ -1,7 +1,7 @@
package net.filebot.ui.filter;
import static net.filebot.Logging.*;
import static net.filebot.MediaTypes.*;
import static net.filebot.media.XattrMetaInfo.*;
import static net.filebot.util.FileUtilities.*;
import java.awt.Color;
@ -16,7 +16,6 @@ import javax.swing.ListSelectionModel;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
import net.filebot.media.MetaAttributes;
import net.filebot.util.FileUtilities;
import net.filebot.util.ui.LoadingOverlayPane;
import net.filebot.web.Episode;
@ -58,36 +57,21 @@ class AttributeTool extends Tool<TableModel> {
}
for (File file : filter(FileUtilities.listFiles(root), VIDEO_FILES, SUBTITLE_FILES)) {
try {
MetaAttributes attributes = new MetaAttributes(file);
String metaId = null;
Object metaObject = null;
String originalName = null;
Object metaObject = xattr.getMetaInfo(file);
String originalName = xattr.getOriginalName(file);
try {
originalName = attributes.getOriginalName();
metaObject = attributes.getObject();
if (metaObject instanceof Episode) {
SeriesInfo seriesInfo = ((Episode) metaObject).getSeriesInfo();
if (seriesInfo != null) {
metaId = String.format("%s::%d", seriesInfo.getDatabase(), seriesInfo.getId());
}
} else if (metaObject instanceof Movie) {
Movie movie = (Movie) metaObject;
if (movie.getTmdbId() > 0) {
metaId = String.format("%s::%d", "TheMovieDB", movie.getTmdbId());
} else if (movie.getImdbId() > 0) {
metaId = String.format("%s::%d", "OMDb", movie.getImdbId());
}
}
} catch (Exception e) {
// ignore
if (metaObject instanceof Episode) {
SeriesInfo seriesInfo = ((Episode) metaObject).getSeriesInfo();
if (seriesInfo != null) {
model.addRow(String.format("%s::%d", seriesInfo.getDatabase(), seriesInfo.getId()), metaObject, originalName, file);
}
} else if (metaObject instanceof Movie) {
Movie movie = (Movie) metaObject;
if (movie.getTmdbId() > 0) {
model.addRow(String.format("%s::%d", "TheMovieDB", movie.getTmdbId()), metaObject, originalName, file);
} else if (movie.getImdbId() > 0) {
model.addRow(String.format("%s::%d", "OMDb", movie.getImdbId()), metaObject, originalName, file);
}
model.addRow(metaId, metaObject, originalName, file);
} catch (Exception e) {
debug.warning("Failed to read xattr: " + e);
}
}

View File

@ -378,7 +378,7 @@ class BindingDialog extends JDialog {
mediaFileTextField.setText(file.get(0).getAbsolutePath());
// set info object from xattr if possible
Object object = xattr.readMetaInfo(file.get(0));
Object object = xattr.getMetaInfo(file.get(0));
if (object != null && infoObjectFormat.format(object) != null) {
setInfoObject(object);
}

View File

@ -8,6 +8,7 @@ import static javax.swing.JOptionPane.*;
import static net.filebot.Logging.*;
import static net.filebot.Settings.*;
import static net.filebot.UserFiles.*;
import static net.filebot.media.XattrMetaInfo.*;
import static net.filebot.util.ui.SwingUI.*;
import java.awt.Color;
@ -73,7 +74,6 @@ import net.filebot.History.Sequence;
import net.filebot.ResourceManager;
import net.filebot.StandardRenameAction;
import net.filebot.mac.MacAppUtilities;
import net.filebot.media.MetaAttributes;
import net.filebot.ui.transfer.FileExportHandler;
import net.filebot.ui.transfer.FileTransferablePolicy;
import net.filebot.ui.transfer.LoadAction;
@ -539,9 +539,8 @@ class HistoryDialog extends JDialog {
File original = StandardRenameAction.revert(entry.getKey(), entry.getValue());
count++;
if (original.isFile() && useExtendedFileAttributes()) {
new MetaAttributes(original).clear();
}
// clear xattr that may or may not exist
xattr.clear(original);
}
} catch (Exception e) {
log.log(Level.WARNING, "Failed to revert files: " + e.getMessage(), e);

View File

@ -124,7 +124,7 @@ class RenameAction extends AbstractAction {
if (renameMap.containsKey(file) && meta != null) {
File destination = resolveDestination(file, renameMap.get(file), false);
if (destination.isFile()) {
xattr.storeMetaInfo(destination, meta, file.getName());
xattr.setMetaInfo(destination, meta, file.getName());
}
}
}

View File

@ -333,7 +333,7 @@ public class RenamePanel extends JComponent {
List<Object> objects = new ArrayList<Object>(files.size());
List<File> objectsTail = new ArrayList<File>();
for (File file : files) {
Object metaObject = xattr.readMetaInfo(file);
Object metaObject = xattr.getMetaInfo(file);
if (metaObject != null) {
objects.add(metaObject); // upper list is based on xattr metadata
} else {