r2558 - trunk/nuiton-updater/src/main/java/org/nuiton/util/updater
Author: tchemit Date: 2013-03-24 12:41:54 +0100 (Sun, 24 Mar 2013) New Revision: 2558 Url: http://nuiton.org/projects/nuiton-utils/repository/revisions/2558 Log: fixes #2628: [ApplicationUpdater] Improve action API Added: trunk/nuiton-updater/src/main/java/org/nuiton/util/updater/AbstractApplicationUpdaterAction.java trunk/nuiton-updater/src/main/java/org/nuiton/util/updater/ApplicationUpdaterActionGetVersions.java trunk/nuiton-updater/src/main/java/org/nuiton/util/updater/ApplicationUpdaterActionUpdate.java Modified: trunk/nuiton-updater/src/main/java/org/nuiton/util/updater/ApplicationUpdater.java Added: trunk/nuiton-updater/src/main/java/org/nuiton/util/updater/AbstractApplicationUpdaterAction.java =================================================================== --- trunk/nuiton-updater/src/main/java/org/nuiton/util/updater/AbstractApplicationUpdaterAction.java (rev 0) +++ trunk/nuiton-updater/src/main/java/org/nuiton/util/updater/AbstractApplicationUpdaterAction.java 2013-03-24 11:41:54 UTC (rev 2558) @@ -0,0 +1,258 @@ +package org.nuiton.util.updater; + +/* + * #%L + * Nuiton Utils :: Nuiton Updater + * $Id$ + * $HeadURL$ + * %% + * Copyright (C) 2013 CodeLutin, Tony Chemit + * %% + * This program 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 3 of the + * License, or (at your option) any later version. + * + * This program 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 General Lesser Public License for more details. + * + * You should have received a copy of the GNU General Lesser Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/lgpl-3.0.html>. + * #L% + */ + +import com.google.common.collect.Maps; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.vfs2.FileObject; +import org.apache.commons.vfs2.FileSystemManager; +import org.apache.commons.vfs2.FileSystemOptions; +import org.apache.commons.vfs2.VFS; +import org.apache.commons.vfs2.provider.http.HttpFileSystemConfigBuilder; +import org.nuiton.util.VersionUtil; +import org.nuiton.util.config.ApplicationConfig; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.InputStream; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Abstract updater action. + * + * @author tchemit <chemit@codelutin.com> + * @since 2.6.12 + */ +public abstract class AbstractApplicationUpdaterAction implements Runnable { + + /** Logger. */ + private static final Log log = + LogFactory.getLog(AbstractApplicationUpdaterAction.class); + + public static final String HTTP_PROXY = "http_proxy"; + + public static final String URL_KEY = "url"; + + public static final String AUTHENTICATION_KEY = "auth"; + + public static final String VERSION_KEY = "version"; + + protected ApplicationConfig config; + + protected String vfsPropertiesUrl; + + protected File currentDir; + + public AbstractApplicationUpdaterAction(ApplicationConfig config, + String vfsPropertiesUrl, + File currentDir) { + this.config = config; + this.vfsPropertiesUrl = vfsPropertiesUrl; + this.currentDir = currentDir; + } + + protected Map<String, ApplicationInfo> getVersions(ApplicationConfig releaseConfig, + boolean allVersion, + File destDir) { + List<String> appNames = getApplicationName(releaseConfig); + Map<String, String> appVersions = getCurrentVersion(appNames, currentDir); + + log.debug("application current version: " + appVersions); + + // recherche des applications a mettre a jour + Map<String, ApplicationInfo> appToUpdate = new HashMap<String, ApplicationInfo>(); + for (String app : appNames) { + String currentVersion = appVersions.get(app); + String newVersion = releaseConfig.getOption(app + ApplicationUpdater.SEPARATOR_KEY + VERSION_KEY); + boolean greater = VersionUtil.greaterThan(newVersion, currentVersion); + log.debug(String.format("for %s Current(%s) < newVersion(%s) ? %s", + app, currentVersion, newVersion, greater)); + boolean add = allVersion || greater; + if (add) { + String urlString = releaseConfig.getOption( + app + ApplicationUpdater.SEPARATOR_KEY + URL_KEY); + boolean needAuthentication = releaseConfig.getOptionAsBoolean( + app + ApplicationUpdater.SEPARATOR_KEY + AUTHENTICATION_KEY); + + if (allVersion && !greater) { + newVersion = null; + } + appToUpdate.put(app, new ApplicationInfo( + app, currentVersion, newVersion, urlString, destDir, needAuthentication)); + } + } + return appToUpdate; + } + + /** + * Converti le path en URL vfs2. Path doit etre une URL, mais pour les fichiers + * au lieu d'etre absolue ils peuvent etre relatif, un traitement special + * est donc fait pour ce cas. Cela est necessaire pour facilement faire + * des tests unitaires independant de la machine ou il sont fait + * + * @param path + * @return + */ + protected String toVfsURL(String path) { + String result = path; + Pattern p = Pattern.compile("(.*?file:)([^/][^!]*)(.*)"); + Matcher m = p.matcher(path); + if (m.matches()) { + String filepath = m.group(2); + File f = new File(filepath); + result = path.replaceAll( + "(.*?file:)([^/][^!]*)(.*)", + "$1" + f.getAbsolutePath() + "$3"); + } + return result; + } + + /** + * Return config prepared for os and arch + * + * @return + * @throws Exception + */ + protected ApplicationConfig getUpdaterConfig(FileSystemOptions vfsConfig, + String vfsPropertiesUrl) throws Exception { + String osName = StringUtils.lowerCase(config.getOsName()); + String osArch = StringUtils.lowerCase(config.getOsArch()); + // take only first part for osName (windows 2000 or windows 2003 -> windows) + osName = StringUtils.substringBefore(osName, " "); + + if (log.isDebugEnabled()) { + log.debug(String.format("Try to load properties from '%s'", vfsPropertiesUrl)); + } + + Properties prop = new Properties(); + + FileSystemManager fsManager = VFS.getManager(); + FileObject properties = fsManager.resolveFile(toVfsURL(vfsPropertiesUrl), vfsConfig); + try { + InputStream in = new BufferedInputStream(properties.getContent().getInputStream()); + prop.load(in); + } finally { + try { + properties.close(); + } catch (Exception doNothing) { + log.debug("Can't close vfs file", doNothing); + } + } + + if (log.isDebugEnabled()) { + log.debug(String.format( + "Properties loaded from '%s'\n%s", + vfsPropertiesUrl, prop)); + } + + // load config with new properties as default + ApplicationConfig result = new ApplicationConfig(prop); + // don't parse. We want only prop in applicationConfig + result = result.getSubConfig( + ApplicationUpdater.class.getSimpleName() + ApplicationUpdater.SEPARATOR_KEY); + + result = result.getSubConfig(osName + ApplicationUpdater.SEPARATOR_KEY); + result = result.getSubConfig(osArch + ApplicationUpdater.SEPARATOR_KEY); + return result; + } + + /** + * Recupere le proxy http a utiliser pour les connexions reseaux + * + * @param config + * @return + */ + protected FileSystemOptions getVFSConfig(ApplicationConfig config) { + FileSystemOptions result = new FileSystemOptions(); + String proxyHost = config.getOption(HTTP_PROXY); + try { + proxyHost = StringUtils.substringAfter(proxyHost, "://"); + if (StringUtils.isNotBlank(proxyHost)) { + String hostname = StringUtils.substringBefore(proxyHost, ":"); + String port = StringUtils.substringAfter(proxyHost, ":"); + if (StringUtils.isNumeric(port)) { + + int portNumber = Integer.parseInt(port); + + HttpFileSystemConfigBuilder.getInstance().setProxyHost(result, hostname); + HttpFileSystemConfigBuilder.getInstance().setProxyPort(result, portNumber); + } else { + log.warn(String.format("Invalide proxy port number '%s', not used proxy", port)); + } + } + } catch (Exception eee) { + log.warn(String.format("Can't use proxy '%s'", proxyHost), eee); + } + return result; + } + + /** + * Recherche pour chaque application la version courante + * + * @param apps la liste des applications a rechercher + * @return + */ + protected Map<String, String> getCurrentVersion(List<String> apps, File dir) { + Map<String, String> result = Maps.newTreeMap(); + for (String app : apps) { + File applicationDirectory = new File(dir, app); + String version = ApplicationUpdater.loadVersionFile( + app, + applicationDirectory); + result.put(app, version); + } + return result; + } + + /** + * Retourne la liste des noms d'application se trouvant dans la + * configuration + * + * @param config + * @return + */ + protected List<String> getApplicationName(ApplicationConfig config) { + Pattern p = Pattern.compile("([^.]+)\\.version"); + List<String> result = new LinkedList<String>(); + for (String v : config.getFlatOptions().stringPropertyNames()) { + Matcher match = p.matcher(v); + if (match.matches()) { + result.add(match.group(1)); + } else if (StringUtils.endsWith(v, ".version")) { + log.debug(String.format("value is not valid application version '%s'", v)); + } + } + return result; + } + +} Property changes on: trunk/nuiton-updater/src/main/java/org/nuiton/util/updater/AbstractApplicationUpdaterAction.java ___________________________________________________________________ Added: svn:keywords + Author Date Id Revision HeadURL Added: svn:eol-style + native Modified: trunk/nuiton-updater/src/main/java/org/nuiton/util/updater/ApplicationUpdater.java =================================================================== --- trunk/nuiton-updater/src/main/java/org/nuiton/util/updater/ApplicationUpdater.java 2013-03-18 15:52:39 UTC (rev 2557) +++ trunk/nuiton-updater/src/main/java/org/nuiton/util/updater/ApplicationUpdater.java 2013-03-24 11:41:54 UTC (rev 2558) @@ -25,33 +25,15 @@ */ import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.commons.vfs2.AllFileSelector; -import org.apache.commons.vfs2.FileObject; -import org.apache.commons.vfs2.FileSystemException; -import org.apache.commons.vfs2.FileSystemManager; -import org.apache.commons.vfs2.FileSystemOptions; -import org.apache.commons.vfs2.VFS; -import org.apache.commons.vfs2.provider.http.HttpFileSystemConfigBuilder; -import org.nuiton.util.VersionUtil; import org.nuiton.util.config.ApplicationConfig; import org.nuiton.util.config.ArgumentsParserException; -import java.io.BufferedInputStream; import java.io.File; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; import java.util.Map; -import java.util.Properties; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * Permet de telecharger des mises a jour d'application. @@ -134,22 +116,14 @@ /** Logger. */ private static final Log log = LogFactory.getLog(ApplicationUpdater.class); - final static private String SEPARATOR_KEY = "."; + protected static final String VERSION_FILE = "version.appup"; - public static final String HTTP_PROXY = "http_proxy"; + protected final static String SEPARATOR_KEY = "."; - public static final String URL_KEY = "url"; + public static final String ZERO_VERSION = "0"; - public static final String AUTHENTICATION_KEY = "auth"; - - public static final String VERSION_KEY = "version"; - - public static final String VERSION_FILE = "version.appup"; - protected ApplicationConfig config; - protected DownloadMonitor downloadMonitor; - /** Utilise le fichier de configuration par defaut: ApplicationUpdater.properties */ public ApplicationUpdater() { this(null); @@ -174,10 +148,35 @@ this.config = config; } - public void setDownloadMonitor(DownloadMonitor downloadMonitor) { - this.downloadMonitor = downloadMonitor; + public static File getVersionFile(File dir) { + File versionFile = new File(dir, VERSION_FILE); + return versionFile; } + public static void createVersionFile(File dir, String version) throws IOException { + File versionFile = getVersionFile(dir); + FileUtils.writeStringToFile(versionFile, version); + } + + public static String loadVersionFile(String appName, File dir) { + File f = getVersionFile(dir); + String version = ZERO_VERSION; + try { + version = FileUtils.readFileToString(f); + } catch (IOException ex) { + log.warn(String.format( + "Can't find file version file for application '%s', this file should be '%s'", + appName, f)); + } + version = StringUtils.trim(version); + return version; + } + + public static void storeVersionFile(File dir, String version) throws IOException { + File f = getVersionFile(dir); + FileUtils.writeStringToFile(f, version); + } + /** * @param url url where properties file is downloadable. This properties * must contains information on application release @@ -191,449 +190,58 @@ File destDir, boolean async, ApplicationUpdaterCallback callback) { - Updater up = new Updater(config, url, currentDir, destDir, downloadMonitor, callback); - if (async) { - Thread thread = new Thread(up, ApplicationUpdater.class.getSimpleName()); - thread.start(); - } else { - up.run(); - } - } - public static void createVersionFile(File dir, String version) throws IOException { - File versionFile = new File(dir, VERSION_FILE); - FileUtils.writeStringToFile(versionFile, version); + update(url, currentDir, destDir, async, callback, null); } /** - * La classe ou le travail est reellement fait, peut-etre appeler dans - * un thread si necessaire. + * @param url url where properties file is downloadable. This properties + * must contains information on application release + * @param currentDir directory where application is currently + * @param destDir default directory to put new application version, can be null if you used callback + * @param async if true, check is done in background mode + * @param callback callback used to interact with updater, can be null + * @param downloadMonitor optinal download monitor */ - public static class Updater implements Runnable { - - protected ApplicationConfig config; - - protected String vfsPropertiesUrl; - - protected File currentDir; - - protected File destDir; - - protected ApplicationUpdaterCallback callback; - - protected DownloadMonitor downloadMonitor; - - public Updater(ApplicationConfig config, - String vfsPropertiesUrl, + public void update(String url, File currentDir, File destDir, - DownloadMonitor downloadMonitor, - ApplicationUpdaterCallback callback) { - this.config = config; - this.vfsPropertiesUrl = vfsPropertiesUrl; - this.currentDir = currentDir; - this.destDir = destDir; - this.downloadMonitor = downloadMonitor; - this.callback = callback; - } + boolean async, + ApplicationUpdaterCallback callback, + DownloadMonitor downloadMonitor) { - /** - * <li>Recupere le fichier properties contenant les informations de mise a jour - * <li>liste les applications et leur version actuelle - * <li>pour chaque application a mettre a jour recupere le zip et le decompresse - * <p/> - * Si callback existe envoi les messages necessaire - */ - @Override - public void run() { - try { - FileSystemOptions vfsConfig = getVFSConfig(config); - ApplicationConfig releaseConfig = getUpdaterConfig(vfsConfig, vfsPropertiesUrl); - - List<String> appNames = getApplicationName(releaseConfig); - Map<String, String> appVersions = getCurrentVersion(appNames, currentDir); - - log.debug("application current version: " + appVersions); - - // recherche des applications a mettre a jour - Map<String, ApplicationInfo> appToUpdate = new HashMap<String, ApplicationInfo>(); - for (String app : appNames) { - String currentVersion = appVersions.get(app); - String newVersion = releaseConfig.getOption(app + SEPARATOR_KEY + VERSION_KEY); - boolean greater = VersionUtil.greaterThan(newVersion, currentVersion); - log.debug(String.format("for %s Current(%s) < newVersion(%s) ? %s", - app, currentVersion, newVersion, greater)); - if (greater) { - String urlString = releaseConfig.getOption( - app + SEPARATOR_KEY + URL_KEY); - boolean needAuthentication = releaseConfig.getOptionAsBoolean( - app + SEPARATOR_KEY + AUTHENTICATION_KEY); - - appToUpdate.put(app, new ApplicationInfo( - app, currentVersion, newVersion, urlString, destDir, needAuthentication)); - } - } - - // offre la possibilite a l'appelant de modifier les valeurs par defaut - if (callback != null) { - appToUpdate = callback.updateToDo(appToUpdate); - } - - // mise a jour - Map<String, Exception> appUpdateError = new HashMap<String, Exception>(); - for (Map.Entry<String, ApplicationInfo> appInfo : appToUpdate.entrySet()) { - String app = appInfo.getKey(); - ApplicationInfo info = appInfo.getValue(); - try { - doUpdate(vfsConfig, appInfo.getValue()); - } catch (Exception eee) { - appUpdateError.put(app, eee); - try { - // clear data if error occur during uncompress operation - File dest = new File(info.destDir, info.name); - if (dest.exists()) { - log.debug(String.format("Cleaning destination directory due to error '%s'", dest)); - FileUtils.deleteDirectory(dest); - } - } catch (Exception doNothing) { - log.debug("Can't clean directory", doNothing); - } - - log.warn(String.format( - "Can't update application '%s' with url '%s'", - app, info.url)); - log.debug("Application update aborted because: ", eee); - } - } - - // envoi le resultat a l'appelant s'il le souhaite - if (callback != null) { - callback.updateDone(appToUpdate, appUpdateError); - } - } catch (Exception eee) { - log.warn("Can't update"); - log.info("Application update aborted because: ", eee); - if (callback != null) { - callback.aborted(vfsPropertiesUrl, eee); - } - } + ApplicationUpdaterActionUpdate action = + new ApplicationUpdaterActionUpdate( + config, + url, + currentDir, + destDir, + downloadMonitor, + callback); + if (async) { + Thread thread = new Thread(action, ApplicationUpdater.class.getSimpleName()); + thread.start(); + } else { + action.run(); } + } - /** - * Decompresse le zip qui est pointer par l'url dans le repertoire - * specifie, et ajoute le fichier contenant la version de l'application. - * Le repertoire root du zip est renomme par le nom de l'application. - * Par exemple si un fichier se nomme "monApp-1.2/Readme.txt" il se - * nommera au final "monApp/Readme.txt" - * - * @param vfsConfig le proxy a utiliser pour la connexion a l'url - * @param info information sur l'application a mettre a jour - * @throws Exception - */ - protected void doUpdate(FileSystemOptions vfsConfig, - ApplicationInfo info) throws Exception { - if (info.destDir != null) { - File dest = new File(info.destDir, info.name); - String url = toVfsURL(info.url); - if (info.needAuthentication) { - url = StringUtils.replaceOnce(url, "://", - String.format("://%s:%s@", info.login, new String(info.password))); - } - if (callback != null) { - callback.startUpdate(info); - } + /** + * @param url url where properties file is downloadable. This properties + * must contains information on application release + * @param currentDir directory where application is currently + */ + public Map<String, ApplicationInfo> getVersions(String url, + File currentDir) { - // le type de l'archive contenant la mise à jour - String archiveType = url.substring(0, url.indexOf(':')); - - // recuperation de l'archive en locale (dans /tmp) - File archive = downloadUpdate(vfsConfig, info, url.substring(archiveType.length() + 1)); - - // extraction depuis l'archive téléchargée de l'unique répertoire vers la destination - explodeUpdate(vfsConfig, - info, - archiveType, - archive, - dest); - - // ajout du fichier de version - createVersionFile(dest, info.newVersion); - log.info(String.format( - "Application '%s' is uptodate with version '%s' in '%s'", - info.name, info.newVersion, info.destDir)); - } else { - log.info(String.format("Update for '%s' aborted because destination dir is set to null", info.name)); - } - } - - /** - * Télécharge une archive dans un fichier temporaraire. - * <p/> - * Si l'archive a plus d'un repertoire root, une exception est levee - * - * @param vfsConfig configuration of vsf ( - * @param info - * @param srcPath source path de la forme vfs2 ex:"zip:http://www.nuiton.org/attachments/download/830/nuiton-utils-2.6.5-deps.zip" - * @throws FileSystemException - */ - protected File downloadUpdate(FileSystemOptions vfsConfig, - ApplicationInfo info, - String srcPath) throws IOException { - - FileSystemManager fsManager = VFS.getManager(); - FileObject source = fsManager.resolveFile(srcPath, vfsConfig); - - if (!source.exists()) { - throw new UpdateNotFoundException(info); - } - - File result = new File(FileUtils.getTempDirectory(), - source.getName().getBaseName() + - '_' + System.nanoTime()); - - FileObject target = fsManager.toFileObject(result); - InputStream input = source.getContent().getInputStream(); - try { - OutputStream output = target.getContent().getOutputStream(); - try { - long inputSize = source.getContent().getSize(); - if (downloadMonitor != null) { - downloadMonitor.setSize(inputSize); - } - long count = 0; - int n; - byte[] buffer = new byte[1024]; - while (-1 != (n = input.read(buffer))) { - output.write(buffer, 0, n); - count += n; - if (downloadMonitor != null) { - downloadMonitor.setCurrent(count); - } - } - output.close(); - } finally { - - IOUtils.closeQuietly(output); - } - input.close(); - } finally { - IOUtils.closeQuietly(input); - } - return result; - } - - /** - * Recopie le contenu du répertoire de l'archive dans le - * répertoire {@code target}. - * <p/> - * Si le répertoire cible existe déjà, il sera alors vidé. - * <p/> - * Si l'archive ne contient pas exactement un répertoire alors une exception est levée - * - * @param archiveType le type de l'archive - * @param source l'archive à décompresser - * @param target le répertoire cible - * @throws FileSystemException - * @throws UpdateInvalidArchiveLayoutException - * si l'archive n'a pas le bon format - */ - protected void explodeUpdate(FileSystemOptions vfsConfig, - ApplicationInfo info, - String archiveType, - File source, - File target) throws FileSystemException, UpdateInvalidArchiveLayoutException { - - FileSystemManager fsManager = VFS.getManager(); - FileObject sourceObject = fsManager.resolveFile(archiveType + ":" + source.getAbsolutePath(), vfsConfig); - - FileObject[] children = sourceObject.getChildren(); - if (children.length != 1) { - throw new UpdateInvalidArchiveLayoutException(info, source); - } - // clean target - FileObject targetObject = fsManager.toFileObject(target); - targetObject.delete(new AllFileSelector()); - - //copy to it the archive only directory - FileObject child = children[0]; - targetObject.copyFrom(child, new AllFileSelector()); - } - - /** - * Recupere le contenu du repertoire de l'archive pour le mettre dans targetPath - * si targetPath existait deja, il est supprime au prealable. - * <p/> - * Si l'archive a plus d'un repertoire root, une exception est levee - * - * @param srcPath source path de la forme vfs2 ex:"zip:http://www.nuiton.org/attachments/download/830/nuiton-utils-2.6.5-deps.zip" - * @param targetPath le path destination - * @throws FileSystemException - * @deprecated since 2.6.11, no more used - */ - @Deprecated - protected void deepCopy(FileSystemOptions vfsConfig, - String srcPath, - String targetPath) throws FileSystemException { - FileSystemManager fsManager = VFS.getManager(); - FileObject archive = fsManager.resolveFile(srcPath, vfsConfig); - - FileObject[] children = archive.getChildren(); - if (children.length == 1) { - FileObject child = children[0]; - - FileObject target = fsManager.resolveFile(toVfsURL(targetPath), vfsConfig); - target.delete(new AllFileSelector()); - target.copyFrom(child, new AllFileSelector()); - } else { - throw new RuntimeException("must have only one root directory"); - } - } - - /** - * Converti le path en URL vfs2. Path doit etre une URL, mais pour les fichiers - * au lieu d'etre absolue ils peuvent etre relatif, un traitement special - * est donc fait pour ce cas. Cela est necessaire pour facilement faire - * des tests unitaires independant de la machine ou il sont fait - * - * @param path - * @return - */ - protected String toVfsURL(String path) { - String result = path; - Pattern p = Pattern.compile("(.*?file:)([^/][^!]*)(.*)"); - Matcher m = p.matcher(path); - if (m.matches()) { - String filepath = m.group(2); - File f = new File(filepath); - result = path.replaceAll( - "(.*?file:)([^/][^!]*)(.*)", - "$1" + f.getAbsolutePath() + "$3"); - } - return result; - } - - /** - * Return config prepared for os and arch - * - * @return - * @throws Exception - */ - protected ApplicationConfig getUpdaterConfig(FileSystemOptions vfsConfig, String vfsPropertiesUrl) throws Exception { - String osName = StringUtils.lowerCase(config.getOsName()); - String osArch = StringUtils.lowerCase(config.getOsArch()); - // take only first part for osName (windows 2000 or windows 2003 -> windows) - osName = StringUtils.substringBefore(osName, " "); - - if (log.isDebugEnabled()) { - log.debug(String.format("Try to load properties from '%s'", vfsPropertiesUrl)); - } - - Properties prop = new Properties(); - - FileSystemManager fsManager = VFS.getManager(); - FileObject properties = fsManager.resolveFile(toVfsURL(vfsPropertiesUrl), vfsConfig); - try { - InputStream in = new BufferedInputStream(properties.getContent().getInputStream()); - prop.load(in); - } finally { - try { - properties.close(); - } catch (Exception doNothing) { - log.debug("Can't close vfs file", doNothing); - } - } - - if (log.isDebugEnabled()) { - log.debug(String.format( - "Properties loaded from '%s'\n%s", - vfsPropertiesUrl, prop)); - } - - // load config with new properties as default - ApplicationConfig result = new ApplicationConfig(prop); - // don't parse. We want only prop in applicationConfig - result = result.getSubConfig( - ApplicationUpdater.class.getSimpleName() + SEPARATOR_KEY); - - result = result.getSubConfig(osName + SEPARATOR_KEY); - result = result.getSubConfig(osArch + SEPARATOR_KEY); - return result; - } - - /** - * Recupere le proxy http a utiliser pour les connexions reseaux - * - * @param config - * @return - */ - protected FileSystemOptions getVFSConfig(ApplicationConfig config) { - FileSystemOptions result = new FileSystemOptions(); - String proxyHost = config.getOption(HTTP_PROXY); - try { - proxyHost = StringUtils.substringAfter(proxyHost, "://"); - if (StringUtils.isNotBlank(proxyHost)) { - String hostname = StringUtils.substringBefore(proxyHost, ":"); - String port = StringUtils.substringAfter(proxyHost, ":"); - if (StringUtils.isNumeric(port)) { - - int portNumber = Integer.parseInt(port); - - HttpFileSystemConfigBuilder.getInstance().setProxyHost(result, hostname); - HttpFileSystemConfigBuilder.getInstance().setProxyPort(result, portNumber); - } else { - log.warn(String.format("Invalide proxy port number '%s', not used proxy", port)); - } - } - } catch (Exception eee) { - log.warn(String.format("Can't use proxy '%s'", proxyHost), eee); - } - return result; - } - - /** - * Recherche pour chaque application la version courante - * - * @param apps la liste des applications a rechercher - * @return - */ - protected Map<String, String> getCurrentVersion(List<String> apps, File dir) { - Map<String, String> result = new HashMap<String, String>(); - for (String app : apps) { - File f = new File(dir, app + File.separator + VERSION_FILE); - String version = "0"; - try { - version = FileUtils.readFileToString(f); - } catch (IOException ex) { - log.warn(String.format( - "Can't find file version '%s' for application '%s', this file should be '%s'", - VERSION_FILE, app, f)); - } - version = StringUtils.trim(version); - result.put(app, version); - } - return result; - } - - /** - * Retourne la liste des noms d'application se trouvant dans la - * configuration - * - * @param config - * @return - */ - protected List<String> getApplicationName(ApplicationConfig config) { - Pattern p = Pattern.compile("([^.]+)\\.version"); - List<String> result = new LinkedList<String>(); - for (String v : config.getFlatOptions().stringPropertyNames()) { - Matcher match = p.matcher(v); - if (match.matches()) { - result.add(match.group(1)); - } else if (StringUtils.endsWith(v, ".version")) { - log.debug(String.format("value is not valid application version '%s'", v)); - } - } - return result; - } - + ApplicationUpdaterActionGetVersions action = + new ApplicationUpdaterActionGetVersions( + config, + url, + currentDir); + action.run(); + Map<String, ApplicationInfo> updates = action.getUpdates(); + return updates; } + } Added: trunk/nuiton-updater/src/main/java/org/nuiton/util/updater/ApplicationUpdaterActionGetVersions.java =================================================================== --- trunk/nuiton-updater/src/main/java/org/nuiton/util/updater/ApplicationUpdaterActionGetVersions.java (rev 0) +++ trunk/nuiton-updater/src/main/java/org/nuiton/util/updater/ApplicationUpdaterActionGetVersions.java 2013-03-24 11:41:54 UTC (rev 2558) @@ -0,0 +1,77 @@ +package org.nuiton.util.updater; + +/* + * #%L + * Nuiton Utils :: Nuiton Updater + * $Id$ + * $HeadURL$ + * %% + * Copyright (C) 2013 CodeLutin, Tony Chemit + * %% + * This program 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 3 of the + * License, or (at your option) any later version. + * + * This program 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 General Lesser Public License for more details. + * + * You should have received a copy of the GNU General Lesser Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/lgpl-3.0.html>. + * #L% + */ + +import com.google.common.collect.Maps; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.vfs2.FileSystemOptions; +import org.nuiton.util.config.ApplicationConfig; + +import java.io.File; +import java.util.Map; + +/** + * Get version of updates. + * + * @author tchemit <chemit@codelutin.com> + * @since 2.6.12 + */ +public class ApplicationUpdaterActionGetVersions extends AbstractApplicationUpdaterAction { + + /** Logger. */ + private static final Log log = + LogFactory.getLog(ApplicationUpdaterActionGetVersions.class); + + protected Map<String, ApplicationInfo> updates; + + public ApplicationUpdaterActionGetVersions(ApplicationConfig config, + String vfsPropertiesUrl, + File currentDir) { + super(config, vfsPropertiesUrl, currentDir); + } + + public Map<String, ApplicationInfo> getUpdates() { + return updates; + } + + @Override + public void run() { + try { + FileSystemOptions vfsConfig = getVFSConfig(config); + ApplicationConfig releaseConfig = getUpdaterConfig( + vfsConfig, vfsPropertiesUrl); + + Map<String, ApplicationInfo> appToUpdate = + getVersions(releaseConfig, true, null); + + updates = Maps.newTreeMap(); + updates.putAll(appToUpdate); + } catch (Exception eee) { + log.warn("Can't update"); + log.info("Application update aborted because: ", eee); + } + } +} Property changes on: trunk/nuiton-updater/src/main/java/org/nuiton/util/updater/ApplicationUpdaterActionGetVersions.java ___________________________________________________________________ Added: svn:keywords + Author Date Id Revision HeadURL Added: svn:eol-style + native Added: trunk/nuiton-updater/src/main/java/org/nuiton/util/updater/ApplicationUpdaterActionUpdate.java =================================================================== --- trunk/nuiton-updater/src/main/java/org/nuiton/util/updater/ApplicationUpdaterActionUpdate.java (rev 0) +++ trunk/nuiton-updater/src/main/java/org/nuiton/util/updater/ApplicationUpdaterActionUpdate.java 2013-03-24 11:41:54 UTC (rev 2558) @@ -0,0 +1,287 @@ +package org.nuiton.util.updater; + +/* + * #%L + * Nuiton Utils :: Nuiton Updater + * $Id$ + * $HeadURL$ + * %% + * Copyright (C) 2013 CodeLutin, Tony Chemit + * %% + * This program 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 3 of the + * License, or (at your option) any later version. + * + * This program 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 General Lesser Public License for more details. + * + * You should have received a copy of the GNU General Lesser Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/lgpl-3.0.html>. + * #L% + */ + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.vfs2.AllFileSelector; +import org.apache.commons.vfs2.FileObject; +import org.apache.commons.vfs2.FileSystemException; +import org.apache.commons.vfs2.FileSystemManager; +import org.apache.commons.vfs2.FileSystemOptions; +import org.apache.commons.vfs2.VFS; +import org.nuiton.util.config.ApplicationConfig; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Map; + +/** + * Apply available updates. + * + * @author tchemit <chemit@codelutin.com> + * @since 2.6.12 + */ +public class ApplicationUpdaterActionUpdate extends AbstractApplicationUpdaterAction { + + /** Logger. */ + private static final Log log = + LogFactory.getLog(ApplicationUpdaterActionUpdate.class); + + protected ApplicationUpdaterCallback callback; + + protected DownloadMonitor downloadMonitor; + + protected File destDir; + + public ApplicationUpdaterActionUpdate(ApplicationConfig config, + String vfsPropertiesUrl, + File currentDir, + File destDir, + DownloadMonitor downloadMonitor, + ApplicationUpdaterCallback callback) { + super(config, vfsPropertiesUrl, currentDir); + this.destDir = destDir; + this.downloadMonitor = downloadMonitor; + this.callback = callback; + } + + /** + * <li>Recupere le fichier properties contenant les informations de mise a jour + * <li>liste les applications et leur version actuelle + * <li>pour chaque application a mettre a jour recupere le zip et le decompresse + * <p/> + * Si callback existe envoi les messages necessaires + */ + @Override + public void run() { + try { + FileSystemOptions vfsConfig = getVFSConfig(config); + ApplicationConfig releaseConfig = getUpdaterConfig( + vfsConfig, vfsPropertiesUrl); + + Map<String, ApplicationInfo> appToUpdate = getVersions( + releaseConfig, false, destDir); + + // offre la possibilite a l'appelant de modifier les valeurs par defaut + if (callback != null) { + appToUpdate = callback.updateToDo(appToUpdate); + } + + // mise a jour + Map<String, Exception> appUpdateError = new HashMap<String, Exception>(); + for (Map.Entry<String, ApplicationInfo> appInfo : appToUpdate.entrySet()) { + String app = appInfo.getKey(); + ApplicationInfo info = appInfo.getValue(); + try { + doUpdate(vfsConfig, appInfo.getValue()); + } catch (Exception eee) { + onUpdateError(app, info, appUpdateError, eee); + } + } + + // envoi le resultat a l'appelant s'il le souhaite + if (callback != null) { + callback.updateDone(appToUpdate, appUpdateError); + } + } catch (Exception eee) { + log.warn("Can't update"); + log.info("Application update aborted because: ", eee); + if (callback != null) { + callback.aborted(vfsPropertiesUrl, eee); + } + } + } + + protected void onUpdateError(String app, + ApplicationInfo info, + Map<String, Exception> appUpdateError, + Exception eee) { + appUpdateError.put(app, eee); + + log.warn(String.format( + "Can't update application '%s' with url '%s'", + app, info.url)); + log.debug("Application update aborted because: ", eee); + + try { + // clear data if error occur during uncompress operation + File dest = new File(info.destDir, info.name); + if (dest.exists()) { + log.debug(String.format("Cleaning destination directory due to error '%s'", dest)); + FileUtils.deleteDirectory(dest); + } + } catch (Exception doNothing) { + log.debug("Can't clean directory", doNothing); + } + } + + /** + * Decompresse le zip qui est pointer par l'url dans le repertoire + * specifie, et ajoute le fichier contenant la version de l'application. + * Le repertoire root du zip est renomme par le nom de l'application. + * Par exemple si un fichier se nomme "monApp-1.2/Readme.txt" il se + * nommera au final "monApp/Readme.txt" + * + * @param vfsConfig le proxy a utiliser pour la connexion a l'url + * @param info information sur l'application a mettre a jour + * @throws Exception + */ + protected void doUpdate(FileSystemOptions vfsConfig, + ApplicationInfo info) throws Exception { + if (info.destDir != null) { + File dest = new File(info.destDir, info.name); + String url = toVfsURL(info.url); + if (info.needAuthentication) { + url = StringUtils.replaceOnce(url, "://", + String.format("://%s:%s@", info.login, new String(info.password))); + } + if (callback != null) { + callback.startUpdate(info); + } + + // le type de l'archive contenant la mise à jour + String archiveType = url.substring(0, url.indexOf(':')); + + // recuperation de l'archive en locale (dans /tmp) + File archive = downloadUpdate(vfsConfig, info, url.substring(archiveType.length() + 1)); + + // extraction depuis l'archive téléchargée de l'unique répertoire vers la destination + explodeUpdate(vfsConfig, + info, + archiveType, + archive, + dest); + + // ajout du fichier de version + ApplicationUpdater.createVersionFile(dest, info.newVersion); + log.info(String.format( + "Application '%s' is uptodate with version '%s' in '%s'", + info.name, info.newVersion, info.destDir)); + } else { + log.info(String.format("Update for '%s' aborted because destination dir is set to null", info.name)); + } + } + + /** + * Télécharge une archive dans un fichier temporaraire. + * <p/> + * Si l'archive a plus d'un repertoire root, une exception est levee + * + * @param vfsConfig configuration of vsf ( + * @param info + * @param srcPath source path de la forme vfs2 ex:"zip:http://www.nuiton.org/attachments/download/830/nuiton-utils-2.6.5-deps.zip" + * @throws FileSystemException + */ + protected File downloadUpdate(FileSystemOptions vfsConfig, + ApplicationInfo info, + String srcPath) throws IOException { + + FileSystemManager fsManager = VFS.getManager(); + FileObject source = fsManager.resolveFile(srcPath, vfsConfig); + + if (!source.exists()) { + throw new UpdateNotFoundException(info); + } + + File result = new File(FileUtils.getTempDirectory(), + source.getName().getBaseName() + + '_' + System.nanoTime()); + + FileObject target = fsManager.toFileObject(result); + InputStream input = source.getContent().getInputStream(); + try { + OutputStream output = target.getContent().getOutputStream(); + try { + long inputSize = source.getContent().getSize(); + if (downloadMonitor != null) { + downloadMonitor.setSize(inputSize); + } + long count = 0; + int n; + byte[] buffer = new byte[1024]; + while (-1 != (n = input.read(buffer))) { + output.write(buffer, 0, n); + count += n; + if (downloadMonitor != null) { + downloadMonitor.setCurrent(count); + } + } + output.close(); + } finally { + + IOUtils.closeQuietly(output); + } + input.close(); + } finally { + IOUtils.closeQuietly(input); + } + return result; + } + + /** + * Recopie le contenu du répertoire de l'archive dans le + * répertoire {@code target}. + * <p/> + * Si le répertoire cible existe déjà, il sera alors vidé. + * <p/> + * Si l'archive ne contient pas exactement un répertoire alors une exception est levée + * + * @param archiveType le type de l'archive + * @param source l'archive à décompresser + * @param target le répertoire cible + * @throws FileSystemException + * @throws UpdateInvalidArchiveLayoutException + * si l'archive n'a pas le bon format + */ + protected void explodeUpdate(FileSystemOptions vfsConfig, + ApplicationInfo info, + String archiveType, + File source, + File target) throws FileSystemException, UpdateInvalidArchiveLayoutException { + + FileSystemManager fsManager = VFS.getManager(); + FileObject sourceObject = fsManager.resolveFile(archiveType + ":" + source.getAbsolutePath(), vfsConfig); + + FileObject[] children = sourceObject.getChildren(); + if (children.length != 1) { + throw new UpdateInvalidArchiveLayoutException(info, source); + } + // clean target + FileObject targetObject = fsManager.toFileObject(target); + targetObject.delete(new AllFileSelector()); + + //copy to it the archive only directory + FileObject child = children[0]; + targetObject.copyFrom(child, new AllFileSelector()); + } + +} Property changes on: trunk/nuiton-updater/src/main/java/org/nuiton/util/updater/ApplicationUpdaterActionUpdate.java ___________________________________________________________________ Added: svn:keywords + Author Date Id Revision HeadURL Added: svn:eol-style + native
participants (1)
-
tchemit@users.nuiton.org