package eu.irreality.age.swing.newloader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.List; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMResult; import javax.xml.transform.stream.StreamSource; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import eu.irreality.age.filemanagement.WorldLoader; import eu.irreality.age.swing.newloader.download.ProgressKeepingDelegate; import eu.irreality.age.swing.newloader.download.ProgressKeepingReadableByteChannel; /** * An object of this class represents an entry for a game in the game loader. * Each entry contains metadata about the game (title, author, etc.) and information about the resources (files) it requires * and whether they have been downloaded or not. * @author carlos * */ public class GameEntry implements Comparable { private String title; private String author; private String date; private String version; private String ageVersion; private String type; private String language; //private String worldDir; //only for states (it's the way in which they recover multimedia). private GameResource mainResource; private List extraResources = new ArrayList(); private boolean downloaded; private boolean downloadInProgress; private void initGameEntryAttributes ( Element e ) throws MalformedGameEntryException { if ( !e.hasAttribute("worldName") && !e.hasAttribute("title") && !e.hasAttribute("moduleName") ) throw new MalformedGameEntryException("Game entry missing world name (attribute worldName, title or moduleName)"); else { if ( e.hasAttribute("worldName") ) title = e.getAttribute("worldName"); if ( e.hasAttribute("moduleName") ) title = e.getAttribute("moduleName"); if ( e.hasAttribute("title") ) title = e.getAttribute("title"); } if ( e.hasAttribute("author") ) author = e.getAttribute("author"); if ( e.hasAttribute("date") ) date = e.getAttribute("date"); if ( e.hasAttribute("version") ) version = e.getAttribute("version"); if ( e.hasAttribute("ageVersion") ) ageVersion = e.getAttribute("ageVersion"); if ( e.hasAttribute("parserVersion") ) ageVersion = e.getAttribute("parserVersion"); if ( e.hasAttribute("type") ) type = e.getAttribute("type"); if ( e.hasAttribute("language") ) language = e.getAttribute("language"); if ( e.hasAttribute("downloaded") ) downloaded = Boolean.valueOf(e.getAttribute("downloaded")).booleanValue(); } /** * Gets the information about a game entry from an XML node. * @param n * @throws MalformedGameEntryException */ public void initFromXML ( Node n ) throws MalformedGameEntryException { Element e = (Element) n; //initialize the attributes of the game entry element initGameEntryAttributes(e); //main resource NodeList mainResList = e.getElementsByTagName("main-resource"); if ( mainResList.getLength() < 1 ) throw new MalformedGameEntryException("Game entry for " + title + " missing main resource entry (element main-resource)"); Element mainResElt = (Element) mainResList.item(0); if ( mainResElt != null ) { mainResource = new GameResource(); mainResource.initFromXML(mainResElt); } //extra resources NodeList extraResList = e.getElementsByTagName("resource"); for ( int i = 0 ; i < extraResList.getLength() ; i++ ) { Element extraResElt = (Element) extraResList.item(i); if ( extraResElt != null ) { GameResource newResource = new GameResource(); newResource.initFromXML(mainResElt); extraResources.add(newResource); } } } /** * Reads the world file at the specified path (which is the absolute path to a file) and obtains the metadata to initialize the game entry, * if possible. * @param worldPath Absolute path to a world file. * @return true if and only if the metadata was successfully obtained from the world file. */ public boolean obtainFromWorld ( String worldPath ) { try { URL u = WorldLoader.getURLForWorldLoad(worldPath); InputStream is = u.openStream(); Transformer t = TransformerFactory.newInstance().newTransformer(); Source s = new StreamSource(is,u.toString()); DOMResult r = new DOMResult(); t.transform(s,r); Document d = (org.w3c.dom.Document)r.getNode(); Element rootElement = d.getDocumentElement(); //initialize game entry attributes initGameEntryAttributes(rootElement); mainResource = new GameResource(); mainResource.setLocalAbsolutePath(worldPath); return true; } catch (IOException e) { e.printStackTrace(); return false; } catch (TransformerException e) { e.printStackTrace(); return false; } catch (MalformedGameEntryException e) { e.printStackTrace(); return false; } } /** * Obtain an XML representation for this game entry entry. * @param doc The document in which to create the XML element associated with this game entry. * @return */ public Node getXML( Document doc ) { Element result; result = doc.createElement("game"); result.setAttribute("title", title); result.setAttribute("author", author); result.setAttribute("date", date); result.setAttribute("version", version); result.setAttribute("ageVersion", ageVersion); result.setAttribute("type", type); result.setAttribute("language", language); result.setAttribute("downloaded", String.valueOf(downloaded)); //main resource result.appendChild(mainResource.getXML(doc, true)); //extra resources for ( int i = 0 ; i < extraResources.size() ; i++ ) { result.appendChild( ((GameResource)extraResources.get(i)).getXML(doc,false) ); } return result; } /** * @return Whether this game is currently being downloaded. */ public boolean getDownloadInProgress() { return downloadInProgress; } /** * Returns whether the download of this game is in progress. */ public boolean isDownloadInProgress() { return downloadInProgress; } /** * Set this game as currently being downloaded or not. * If the value is false, it is propagated to all the game's resources. * @param value */ public void setDownloadInProgress ( boolean value ) { downloadInProgress = value; if ( value == false ) { mainResource.setDownloadInProgress(false); for ( int i = 0 ; i < extraResources.size() ; i++ ) { ((GameResource)extraResources.get(i)).setDownloadInProgress(false); } } } /** * Check whether the local filesystem has files for all the referenced resources (regardless of the value of the * downloaded flag) * @return */ public boolean checkLocalFilesExist() { if ( !mainResource.checkLocalFileExists() ) return false; for ( int i = 0 ; i < extraResources.size() ; i++ ) if ( ! ((GameResource)extraResources.get(i)).checkLocalFileExists() ) return false; return true; } /** * Downloads this game's resources from their remote URLs (if they haven't already been downloaded). * This method shouldn't be called when the resource has already been downloaded, as it will check the filesystem for the * resources, causing inefficiency. * @throws IOException */ public void download ( final ProgressKeepingDelegate toNotify ) throws IOException { if ( downloaded && checkLocalFilesExist() ) return; //no need to download, file is already there. else { try { setDownloadInProgress(true); final int numResources = 1 + extraResources.size(); //this is used to keep progress mainResource.download(new ProgressKeepingDelegate() { public void progressUpdate(double progress , String progressString) { toNotify.progressUpdate(progress / ((double)numResources) , progressString); } } ); for ( int i = 0 ; i < extraResources.size() ; i++ ) { final int alreadyDownloaded = i+1; ((GameResource)extraResources.get(i)).download(new ProgressKeepingDelegate() { public void progressUpdate(double progress , String progressString) { toNotify.progressUpdate( (alreadyDownloaded+1+progress) / ((double)numResources),progressString); } } ); } setDownloaded(true); setDownloadInProgress(false); } catch (IOException e) { setDownloadInProgress(false); throw e; } } } /** * @return the title */ public String getTitle() { return title; } /** * @return the author */ public String getAuthor() { return author; } /** * @return the date */ public String getDate() { return date; } /** * @return the ageVersion */ public String getAgeVersion() { return ageVersion; } /** * @return the version */ public String getVersion() { return version; } /** * @return the type */ public String getType() { return type; } /** * @return the language */ public String getLanguage() { return language; } /** * @return the mainResource */ public GameResource getMainResource() { return mainResource; } /** * @return the downloaded */ public boolean isDownloaded() { return downloaded; } /** * Returns whether we know where to download this game. * @return */ public boolean isDownloadable() { return getMainResource().getRemoteURL() != null; } /** * Marks this game as downloaded or not. */ public void setDownloaded ( boolean value ) { downloaded = value; } /** * We will consider two game entries to be the same if both the remote URL and the local relative path associated to their main resource is the same. * @param ge * @return */ public boolean equals ( Object obj ) { if ( !(obj instanceof GameEntry) ) return false; GameEntry ge = (GameEntry) obj; if ( mainResource.getLocalPath() == null ) { if ( ge.getMainResource().getLocalPath() != null ) return false; } else { if ( !mainResource.getLocalPath().equals(ge.getMainResource().getLocalPath()) ) return false; } if ( mainResource.getRemoteURL() == null ) { if ( ge.getMainResource().getRemoteURL() != null ) return false; } else { if ( !mainResource.getRemoteURL().equals(ge.getMainResource().getRemoteURL()) ) return false; } return true; } public int hashCode() { int hash = 1; if ( mainResource.getLocalPath() != null ) hash = hash*31 + mainResource.getLocalPath().hashCode(); if ( mainResource.getRemoteURL() != null ) hash = hash*31 + mainResource.getRemoteURL().hashCode(); return hash; } public int compareTo(Object o) { return getTitle().compareTo(((GameEntry)o).getTitle()); } }