package org.limewire.core.impl.library;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.limewire.core.api.Category;
import org.limewire.core.api.FilePropertyKey;
import org.limewire.core.api.library.LocalFileItem;
import org.limewire.core.api.library.MetaDataException;
import org.limewire.core.api.library.MetaDataManager;
import org.limewire.core.impl.util.FilePropertyKeyPopulator;
import org.limewire.util.NameValue;
import org.xml.sax.SAXException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.limegroup.gnutella.library.FileDesc;
import com.limegroup.gnutella.metadata.MetaDataFactory;
import com.limegroup.gnutella.xml.LimeXMLDocument;
import com.limegroup.gnutella.xml.LimeXMLDocumentFactory;
import com.limegroup.gnutella.xml.LimeXMLReplyCollection;
import com.limegroup.gnutella.xml.SchemaNotFoundException;
import com.limegroup.gnutella.xml.SchemaReplyCollectionMapper;
import com.limegroup.gnutella.xml.LimeXMLReplyCollection.MetaDataState;
@Singleton
public class MetaDataManagerImpl implements MetaDataManager {
private final SchemaReplyCollectionMapper schemaReplyCollectionMapper;
private final LimeXMLDocumentFactory limeXMLDocumentFactory;
private final Provider<MetaDataFactory> metaDataFactory;
@Inject
public MetaDataManagerImpl(LimeXMLDocumentFactory limeXMLDocumentFactory,
SchemaReplyCollectionMapper schemaReplyCollectionMapper,
Provider<MetaDataFactory> metaDataFactory) {
this.limeXMLDocumentFactory = limeXMLDocumentFactory;
this.schemaReplyCollectionMapper = schemaReplyCollectionMapper;
this.metaDataFactory = metaDataFactory;
}
@Override
public void save(LocalFileItem localFileItem, Map<FilePropertyKey, Object> newData) throws MetaDataException {
if (localFileItem instanceof CoreLocalFileItem) {
CoreLocalFileItem coreLocalFileItem = (CoreLocalFileItem) localFileItem;
saveMetaData(coreLocalFileItem, newData);
}
}
private void saveMetaData(CoreLocalFileItem coreLocalFileItem, Map<FilePropertyKey, Object> newData) throws MetaDataException {
FileDesc fileDesc = coreLocalFileItem.getFileDesc();
Category category = coreLocalFileItem.getCategory();
String limeXMLSchemaUri = FilePropertyKeyPopulator.getLimeXmlSchemaUri(category);
LimeXMLDocument oldDocument = fileDesc.getXMLDocument(limeXMLSchemaUri);
String input = buildInput(fileDesc, limeXMLSchemaUri, coreLocalFileItem, newData);
if (oldDocument != null && (input == null || input.trim().length() == 0)) {
removeMeta(fileDesc, limeXMLSchemaUri);
return;
} else if (input == null || input.trim().length() == 0) {
return;
}
LimeXMLDocument newDoc = null;
try {
newDoc = limeXMLDocumentFactory.createLimeXMLDocument(input);
} catch (SAXException e) {
throw new MetaDataException("Internal Document Error. Data could not be saved.", e);
} catch (SchemaNotFoundException e) {
throw new MetaDataException("Internal Document Error. Data could not be saved.", e);
} catch (IOException e) {
throw new MetaDataException("Internal Document Error. Data could not be saved.", e);
}
String schemaURI = newDoc.getSchemaURI();
LimeXMLReplyCollection collection = schemaReplyCollectionMapper
.getReplyCollection(schemaURI);
LimeXMLDocument result = null;
if (oldDocument != null) {
result = merge(oldDocument, newDoc);
oldDocument = collection.replaceDoc(fileDesc, result);
} else {
result = newDoc;
collection.addReply(fileDesc, result);
}
if(metaDataFactory.get().containsReader(fileDesc.getFile())) {
final MetaDataState committed = collection.mediaFileToDisk(fileDesc, result);
if (committed != MetaDataState.NORMAL && committed != MetaDataState.UNCHANGED) {
throw new MetaDataException("Internal Document Error. Data could not be saved.");
}
} else if (!collection.writeMapToDisk()) {
throw new MetaDataException("Internal Document Error. Data could not be saved.");
}
}
/**
* Merge the current and new doc.
*/
public LimeXMLDocument merge(LimeXMLDocument currentDoc, LimeXMLDocument newDoc) {
if (!currentDoc.getSchemaURI().equalsIgnoreCase(newDoc.getSchemaURI())) {
throw new IllegalArgumentException(
"Current XML document and new XML document must be of the same type!");
}
Map<String, Map.Entry<String, String>> map = new HashMap<String, Map.Entry<String, String>>();
// Initialize the Map with the current fields
for (Map.Entry<String, String> entry : currentDoc.getNameValueSet())
map.put(entry.getKey(), entry);
// And overwrite everything with the new fields
for (Map.Entry<String, String> entry : newDoc.getNameValueSet())
map.put(entry.getKey(), entry);
return limeXMLDocumentFactory.createLimeXMLDocument(map.values(), currentDoc.getSchemaURI());
}
private String buildInput(FileDesc fileDesc, String limeXMLSchemaUri, LocalFileItem localFileItem, Map<FilePropertyKey, Object> newData) {
List<NameValue<String>> nameValueList = new ArrayList<NameValue<String>>();
Category category = localFileItem.getCategory();
// cycle through the list of editable fields that were displayed
for(FilePropertyKey filePropertyKey : newData.keySet()) {
String limeXmlName = FilePropertyKeyPopulator.getLimeXmlName(category, filePropertyKey);
if (limeXmlName != null) {
Object value = newData.get(filePropertyKey);
if(value != null)
value = FilePropertyKeyPopulator.sanitizeValue(filePropertyKey, value);
// must set null fields to empty string otherwise when merging new and old XML
// docs, this will be replaced by the old value
if(value == null)
value = "";
nameValueList.add(new NameValue<String>(limeXmlName, value.toString()));
}
}
// if there are no fields, don't try to create an XML doc
if(nameValueList.size() == 0)
return null;
return limeXMLDocumentFactory.createLimeXMLDocument(nameValueList, limeXMLSchemaUri).getXMLString();
}
private void removeMeta(FileDesc fileDesc, String limeXMLSchemaUri) throws MetaDataException {
LimeXMLReplyCollection collection = schemaReplyCollectionMapper.getReplyCollection(limeXMLSchemaUri);
if (!collection.removeDoc(fileDesc)) {
throw new MetaDataException("Internal Document Error. Data could not be saved.");
}
}
}