/* Copyright (C) 2005-2011 Fabio Riccardi */
package com.lightcrafts.templates;
import static com.lightcrafts.templates.Locale.LOCALE;
import com.lightcrafts.image.metadata.ImageMetadata;
import com.lightcrafts.image.types.RawImageType;
import com.lightcrafts.platform.Platform;
import com.lightcrafts.utils.file.FileUtil;
import com.lightcrafts.utils.xml.XMLException;
import com.lightcrafts.utils.xml.XmlDocument;
import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.LinkedList;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
/**
* Read and write XmlDocuments from and to Preferences using user-provided
* String keys. Also, inspect the current set of key Strings.
*/
public class TemplateDatabase {
/**
* Any failure in the backing store mechanism for templates causes this
* exception to be thrown.
*/
public static class TemplateException extends Exception {
TemplateException(String message) {
super(message);
}
TemplateException(String message, Throwable cause) {
super(message, cause);
}
}
// The namespace that indicates a camera default Template.
final static String CameraDefaultNamespace = "CameraDefault";
// A namespace assigned to legacy Templates without a namespace.
final static String DefaultNamespace =
LOCALE.get("DefaultTemplateNamespace");
static File TemplateDir;
private static LinkedList<TemplateDatabaseListener> listeners =
new LinkedList<TemplateDatabaseListener>();
private static Preferences Prefs =
Preferences.userNodeForPackage(TemplateDatabase.class);
/**
* This class provides a static utility and cannot be constructed.
*/
private TemplateDatabase() {
}
public static void addListener(TemplateDatabaseListener listener) {
listeners.add(listener);
}
public static void removeListener(TemplateDatabaseListener listener) {
listeners.remove(listener);
}
public static void deployFactoryTemplates() {
TemplateDeployer.deploy();
notifyListeners();
}
public static List<TemplateKey> getTemplateKeys() throws TemplateException {
checkTemplateDir();
FileFilter filter = new FileFilter() {
public boolean accept(File file) {
if (file.isFile()) {
String name = file.getName().toLowerCase();
return name.endsWith(".lzt");
}
return false;
}
};
File[] files = FileUtil.listFiles(TemplateDir, filter, false);
if (files == null) {
throw new TemplateException(
"Couldn't read Template names from " +
TemplateDir.getAbsolutePath()
);
}
List<TemplateKey> keys = new ArrayList<TemplateKey>();
for (File file : files) {
TemplateKey key = new TemplateKey(file);
keys.add(key);
}
Collections.sort(keys);
return keys;
}
public static XmlDocument getTemplateDocument(TemplateKey key)
throws TemplateException
{
try {
checkTemplateDir();
File file = key.getFile();
if (file == null) {
throw new TemplateException(
"No Template named \"" + key + "\" exists in " +
TemplateDir.getAbsolutePath()
);
}
InputStream in = new FileInputStream(file);
XmlDocument doc = new XmlDocument(in);
in.close();
return doc;
}
catch (XMLException e) {
throw new TemplateException(
"Template \"" + key + "\" in " +
TemplateDir.getAbsolutePath() +
" is malformed: " + e.getMessage(),
e
);
}
catch (IOException e) {
throw new TemplateException(
"Couldn't access Template \"" + key + "\" in " +
TemplateDir.getAbsolutePath() + ": " + e.getMessage(),
e
);
}
}
public static void addTemplateDocument(
XmlDocument doc, TemplateKey key, boolean force
) throws TemplateException {
try {
checkTemplateDir();
File file = key.getFile();
if (file.exists() && (! force)) {
throw new TemplateException(
"A Template named \"" + key + "\" already exists in " +
TemplateDir.getAbsolutePath()
);
}
OutputStream out = new FileOutputStream(file);
doc.write(out);
out.close();
}
catch (IOException e) {
throw new TemplateException(
"Couldn't add Template \"" + key + "\" in " +
TemplateDir.getAbsolutePath(),
e
);
}
notifyListeners();
}
public static void removeTemplateDocument(TemplateKey key)
throws TemplateException
{
checkTemplateDir();
File file = key.getFile();
boolean deleted = file.delete();
if (! deleted) {
throw new TemplateException(
"Couldn't delete Template \"" + key + "\" in " +
TemplateDir.getAbsolutePath()
);
}
notifyListeners();
}
public static void setDefaultTemplate(ImageMetadata meta, TemplateKey key)
throws TemplateException
{
String camera = meta.getCameraMake(true);
if (camera != null) {
if (key != null) {
XmlDocument xml = getTemplateDocument(key);
TemplateKey defaultKey =
new TemplateKey(CameraDefaultNamespace, camera);
addTemplateDocument(xml, defaultKey, true);
}
else {
key = new TemplateKey(CameraDefaultNamespace, camera);
removeTemplateDocument(key);
}
}
}
public static TemplateKey getDefaultTemplate(ImageMetadata meta) {
boolean isRaw = (meta.getImageType() instanceof RawImageType);
if (isRaw) {
String camera = meta.getCameraMake(true);
if (camera != null) {
// A default template is a template with the camera's name
// in the special namespace:
TemplateKey key =
new TemplateKey(CameraDefaultNamespace, camera);
XmlDocument xml = null;
try {
xml = getTemplateDocument(key);
}
catch (TemplateException e) {
// Counts as a missing template.
System.err.println("Template error: " + e.getMessage());
}
if (xml == null) {
// Compatibility for users of the LightZone 2 beta:
// search for default templates in preferences.
return getDefaultFromPrefs(meta);
}
return key;
}
}
return null;
}
// Legacy preferences-based default mechanism.
private static TemplateKey getDefaultFromPrefs(ImageMetadata meta) {
String camera = meta.getCameraMake(true);
if (camera != null) {
String name = Prefs.get(camera, null);
if (name != null) {
// Make sure the template has not been removed:
XmlDocument xml = null;
TemplateKey key = new TemplateKey(CameraDefaultNamespace, name);
try {
xml = getTemplateDocument(key);
}
catch (TemplateException e) {
// Counts as a missing template.
System.err.println(
"Default template not found: " + e.getMessage()
);
}
if (xml == null) {
// The template's gone, so remove the camera key also:
Prefs.remove(camera);
return null;
}
return key;
}
}
return null;
}
private static void notifyListeners() {
for (TemplateDatabaseListener listener : listeners) {
listener.templatesChanged();
}
}
private static void checkTemplateDir() throws TemplateException {
if (TemplateDir == null) {
TemplateDir = new File(
Platform.getPlatform().getLightZoneDocumentsDirectory(),
"Templates"
);
if (! TemplateDir.isDirectory()) {
boolean success = TemplateDir.mkdirs();
if (! success) {
String path = TemplateDir.getAbsolutePath();
TemplateDir = null;
throw new TemplateException(
"Couldn't initialize Template directory at " + path
);
}
}
if (System.getProperty("lightcrafts.debug") == null) {
if (! TemplateDeployer.hasDeployed()) {
TemplateDeployer.deploy();
}
}
// Backwardsompatibility measures:
// migrate templates from preferences into files.
migratePrefsTemplates();
// migrate templates from an old folder to the correct folder:
migrateTemplateFolders();
// migrate old templates with no namespace to a default namespace:
migrateTemplateNamespace();
}
}
// In early pre-release versions of LightZone 2, Templates were stored in
// Preferences. This method is called at initialization and attempts to
// copy any Templates from preferences into files. If the copy is
// successful, the preferences are erased.
private static void migratePrefsTemplates() {
try {
List<String> names = getPrefsTemplateNames();
for (String name : names) {
try {
XmlDocument doc = getPrefsTemplateDocument(name);
TemplateKey newKey = new TemplateKey("", name);
addTemplateDocument(doc, newKey, false);
removePrefsTemplateDocument(name);
System.out.println(
"Migrated old-style template " + name
);
}
catch (TemplateException e) {
System.err.println(
"Failed to migrate old-style template " + name
);
}
}
}
catch (TemplateException e) {
System.err.println(
"Couldn't access old-style templates"
);
}
}
// In later pre-release versions of LightZone 2, Templates on Windows were
// stored as files under "Application Data\LightZone\Templates", instead
// of "My Documents\LightZone\Templates". This method copies templates
// from the old folder to the new one.
private static void migrateTemplateFolders() {
if (Platform.getType() != Platform.Windows) {
// Only on Windows did we move the template folder.
return;
}
String home = System.getProperty("user.home");
String oldPath = "Application Data\\LightZone\\Templates";
File oldTemplateDir = new File(home, oldPath);
if (oldTemplateDir.isDirectory()) {
File[] oldFiles = FileUtil.listFiles(oldTemplateDir);
if (oldFiles != null) {
boolean copySucceeded = true;
for (File oldFile : oldFiles) {
File newFile = new File(TemplateDir, oldFile.getName());
try {
FileUtil.copyFile(oldFile, newFile);
System.out.println(
"Copied a template from " +
oldFile + " to " + newFile
);
boolean deleted = oldFile.delete();
if (deleted) {
System.out.println(
"Deleted an old template at " + oldFile
);
}
else {
System.out.println(
"Failed to delete an old template at " + oldFile
);
}
}
catch (IOException e) {
System.out.println(
"Failed to migrate a template from " +
oldFile + " to " + newFile + ": " +
e.getClass().getName() + " " + e.getMessage()
);
copySucceeded = false;
}
}
if (copySucceeded) {
// All files in the old folder were successfully copied.
boolean deleted = oldTemplateDir.delete();
if (deleted) {
System.out.println(
"Deleted old template folder " +
oldTemplateDir.getAbsolutePath()
);
}
else {
System.out.println(
"Failed to delete old template folder " +
oldTemplateDir.getAbsolutePath()
);
}
}
}
}
}
// Older templates have no namespace. Find templates whose namespace is
// the empty string, and put them in a default namespace.
private static void migrateTemplateNamespace() {
try {
List<TemplateKey> keys = TemplateDatabase.getTemplateKeys();
for (TemplateKey key : keys) {
String namespace = key.getNamespace();
if (namespace.equals("")) {
try {
XmlDocument xml = TemplateDatabase.getTemplateDocument(key);
String name = key.getName();
TemplateKey newKey = new TemplateKey(DefaultNamespace, name);
TemplateDatabase.addTemplateDocument(xml, newKey, false);
TemplateDatabase.removeTemplateDocument(key);
}
catch (TemplateException e) {
// Skip this one and continue.
System.out.println(
"Template migration failed for " + key
);
e.printStackTrace();
}
}
}
}
catch (TemplateException e) {
// OK, just allow some templates with the empty namespace
System.out.println("Failed to migrate template namespaces");
}
}
// To help migrate LightZone 2 beta templates into files.
private static List<String> getPrefsTemplateNames()
throws TemplateException
{
try {
String[] keys = Prefs.keys();
ArrayList<String> names = new ArrayList<String>();
for (String key : keys) {
String name = getNameFromPrefsKey(key);
if (name != null) {
names.add(name);
}
}
Collections.sort(names);
return names;
}
catch (BackingStoreException e) {
throw new TemplateException("Couldn't read template names", e);
}
}
// To help migrate LightZone 2 beta templates into files.
private static XmlDocument getPrefsTemplateDocument(String name)
throws TemplateException
{
try {
String key = getPrefsKeyFromName(name);
String text = Prefs.get(key, null);
if (text == null) {
throw new TemplateException(
"No template named \"" + name + "\""
);
}
byte[] bytes = text.getBytes();
InputStream in = new ByteArrayInputStream(bytes);
XmlDocument doc = new XmlDocument(in);
return doc;
}
catch (IOException e) {
throw new TemplateException(
"Couldn't access template \"" + name + "\"", e
);
}
}
private static void removePrefsTemplateDocument(String name) {
String key = getPrefsKeyFromName(name);
Prefs.remove(key);
}
private static String getPrefsKeyFromName(String name) {
return "Template_" + name;
}
private static String getNameFromPrefsKey(String key) {
if (key.startsWith("Template_")) {
return key.replaceFirst("Template_", "");
}
return null;
}
}