| /******************************************************************************* |
| * Copyright (c) 2000, 2004 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.update.internal.core; |
| |
| import java.io.*; |
| import java.net.*; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Date; |
| import java.util.List; |
| import java.util.Properties; |
| |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.update.core.*; |
| import org.eclipse.update.configurator.*; |
| |
| |
| /** |
| * singleton pattern. |
| * manages the error/recover log file |
| */ |
| public class ErrorRecoveryLog { |
| |
| public static final boolean RECOVERY_ON = false; |
| |
| private static final String ERROR_RECOVERY_LOG = "error_recovery.log"; //$NON-NLS-1$ |
| private static final String LOG_ENTRY_KEY = "LogEntry."; //$NON-NLS-1$ |
| private static final String RETURN_CARRIAGE = "\r\n"; //$NON-NLS-1$ |
| private static final String END_OF_FILE = "eof=eof"; //$NON-NLS-1$ |
| |
| // |
| public static final String START_INSTALL_LOG = "START_INSTALL_LOG"; //$NON-NLS-1$ |
| public static final String PLUGIN_ENTRY = "PLUGIN"; //$NON-NLS-1$ |
| public static final String FRAGMENT_ENTRY = "FRAGMENT"; //$NON-NLS-1$ |
| public static final String BUNDLE_MANIFEST_ENTRY = "BUNDLE_MANIFEST"; //$NON-NLS-1$ |
| public static final String BUNDLE_JAR_ENTRY = "BUNDLE"; //$NON-NLS-1$ |
| public static final String FEATURE_ENTRY = "FEATURE"; //$NON-NLS-1$ |
| public static final String ALL_INSTALLED = "ALL_FEATURES_INSTALLED"; //$NON-NLS-1$ |
| public static final String RENAME_ENTRY = "RENAME"; //$NON-NLS-1$ |
| public static final String END_INSTALL_LOG = "END_INSTALL_LOG"; //$NON-NLS-1$ |
| public static final String START_REMOVE_LOG = "REMOVE_LOG"; //$NON-NLS-1$ |
| public static final String END_ABOUT_REMOVE = "END_ABOUT_TO_REMOVE"; //$NON-NLS-1$ |
| public static final String DELETE_ENTRY = "DELETE"; //$NON-NLS-1$ |
| public static final String END_REMOVE_LOG = "END_REMOVE_LOG"; //$NON-NLS-1$ |
| |
| public static boolean forceRemove = false; |
| |
| private static ErrorRecoveryLog inst; |
| private FileWriter out; |
| private int index; |
| private List paths; |
| |
| private boolean open = false; |
| private int nbOfOpen = 0; |
| |
| |
| /** |
| * Constructor for ErrorRecoveryLog. |
| */ |
| private ErrorRecoveryLog() { |
| super(); |
| } |
| |
| /** |
| * Singleton |
| */ |
| public static ErrorRecoveryLog getLog() { |
| if (inst == null){ |
| inst = new ErrorRecoveryLog(); |
| } |
| return inst; |
| } |
| |
| /** |
| * get a unique identifer for the file, ensure uniqueness up to now |
| */ |
| public static String getLocalRandomIdentifier(String path) { |
| |
| if (path==null) return null; |
| |
| // verify if it will be a directory without creating the file |
| // as it doesn't exist yet |
| if (path.endsWith(File.separator) || path.endsWith("/")) //$NON-NLS-1$ |
| return path; |
| File file = new File(path); |
| String newName = |
| UpdateManagerUtils.getLocalRandomIdentifier(file.getName(), new Date()); |
| while (new File(newName).exists()) { |
| newName = |
| UpdateManagerUtils.getLocalRandomIdentifier(file.getName(), new Date()); |
| } |
| File newFile = new File(file.getParentFile(),newName); |
| return newFile.getAbsolutePath(); |
| } |
| |
| /** |
| * returns the log file |
| * We do not check if the file exists |
| */ |
| public File getRecoveryLogFile() { |
| IPlatformConfiguration configuration = |
| ConfiguratorUtils.getCurrentPlatformConfiguration(); |
| URL location = configuration.getConfigurationLocation(); |
| String locationString = location.getFile(); |
| File platformConfiguration = new File(locationString); |
| if (!platformConfiguration.isDirectory()) platformConfiguration = platformConfiguration.getParentFile(); |
| return new File(platformConfiguration, ERROR_RECOVERY_LOG); |
| } |
| |
| |
| /** |
| * Open the log |
| */ |
| public void open(String logEntry) throws CoreException { |
| if (open) { |
| nbOfOpen++; |
| UpdateCore.warn("Open nested Error/Recovery log #"+nbOfOpen+":"+logEntry); //$NON-NLS-1$ //$NON-NLS-2$ |
| return; |
| } |
| |
| File logFile = null; |
| try { |
| logFile = getRecoveryLogFile(); |
| out = new FileWriter(logFile); |
| index = 0; |
| paths=null; |
| open=true; |
| nbOfOpen=0; |
| UpdateCore.warn("Start new Error/Recovery log #"+nbOfOpen+":"+logEntry); //$NON-NLS-1$ //$NON-NLS-2$ |
| } catch (IOException e) { |
| throw Utilities.newCoreException( |
| NLS.bind(Messages.UpdateManagerUtils_UnableToLog, (new Object[] { logFile })), |
| e); |
| } |
| |
| append(logEntry); |
| } |
| |
| /** |
| * Append the string to the log and flush |
| */ |
| public void append(String logEntry) throws CoreException { |
| File logFile = null; |
| try { |
| if (!open) { |
| UpdateCore.warn("Internal Error: The Error/Recovery log is not open:"+logEntry); //$NON-NLS-1$ |
| return; |
| } |
| |
| StringBuffer buffer = new StringBuffer(LOG_ENTRY_KEY); |
| buffer.append(index); |
| buffer.append("="); //$NON-NLS-1$ |
| buffer.append(logEntry); |
| buffer.append(RETURN_CARRIAGE); |
| |
| out.write(buffer.toString()); |
| out.flush(); |
| index++; |
| } catch (IOException e) { |
| throw Utilities.newCoreException( |
| NLS.bind(Messages.UpdateManagerUtils_UnableToLog, (new Object[] { logFile })), |
| e); |
| } |
| } |
| |
| /** |
| * Append the string to the log and flush |
| */ |
| public void appendPath(String logEntry, String path) throws CoreException { |
| if (path == null) |
| return; |
| StringBuffer buffer = new StringBuffer(logEntry); |
| buffer.append(" "); //$NON-NLS-1$ |
| buffer.append(path); |
| append(buffer.toString()); |
| |
| addPath(path); |
| } |
| |
| /** |
| * Close any open recovery log |
| */ |
| public void close(String logEntry) throws CoreException { |
| |
| if (nbOfOpen>0){ |
| UpdateCore.warn("Close nested Error/Recovery log #"+nbOfOpen+":"+logEntry); //$NON-NLS-1$ //$NON-NLS-2$ |
| nbOfOpen--; |
| return; |
| } |
| |
| UpdateCore.warn("Close Error/Recovery log #"+nbOfOpen+":"+logEntry); //$NON-NLS-1$ //$NON-NLS-2$ |
| append(logEntry); |
| if (out != null) { |
| try { |
| out.write(END_OF_FILE); |
| out.flush(); |
| out.close(); |
| } catch (IOException e) { //eat the exception |
| } finally { |
| out = null; |
| open=false; |
| } |
| } |
| } |
| |
| /** |
| * Delete the file from the file system |
| */ |
| public void delete() { |
| //File logFile = getRecoveryLogFile(); |
| getRecoveryLogFile(); |
| //if (logFile.exists()) |
| //logFile.delete(); |
| } |
| |
| /** |
| * |
| */ |
| private void addPath(String path){ |
| if (paths==null) paths = new ArrayList(); |
| paths.add(path); |
| } |
| |
| /** |
| * recover an install or remove that didn't finish |
| * Delete file for an unfinished delete |
| * Delete file for an unfinshed install if not all the files were installed |
| * Rename XML files for an install if all the files were installed but not renamed |
| */ |
| public IStatus recover(){ |
| |
| IStatus mainStatus = createStatus(IStatus.OK,Messages.ErrorRecoveryLog_recoveringStatus,null); |
| MultiStatus multi = new MultiStatus(mainStatus.getPlugin(),mainStatus.getCode(),mainStatus.getMessage(),null); |
| |
| //check if recovery is on |
| if (!RECOVERY_ON){ |
| UpdateCore.warn("Recovering is turned off. Abort recovery"); //$NON-NLS-1$ |
| return multi; |
| } |
| |
| File logFile = getRecoveryLogFile(); |
| if (!logFile.exists()){ |
| multi.add(createStatus(IStatus.ERROR,Messages.ErrorRecoveryLog_cannotFindLogFile+logFile,null)); |
| return multi; |
| } |
| |
| InputStream in = null; |
| Properties prop = null; |
| try { |
| in = new FileInputStream(logFile); |
| prop = new Properties(); |
| prop.load(in); |
| } catch (IOException e){ |
| UpdateCore.warn("Unable to read:"+logFile,e); //$NON-NLS-1$ |
| multi.add(createStatus(IStatus.ERROR,Messages.ErrorRecoveryLog_noPropertyFile+logFile,e)); |
| return multi; |
| } finally { |
| if (in != null) |
| try { |
| in.close(); |
| } catch (IOException e1) { |
| } |
| } |
| |
| String eof = prop.getProperty("eof"); //$NON-NLS-1$ |
| if(eof!=null && eof.equals("eof")){ //$NON-NLS-1$ |
| // all is good |
| delete(); |
| UpdateCore.warn("Found log file. Log file contains end-of-file. No need to process"); //$NON-NLS-1$ |
| multi.add(createStatus(IStatus.OK,null,null)); |
| return multi; |
| } |
| |
| String recovery = prop.getProperty(LOG_ENTRY_KEY+"0"); //$NON-NLS-1$ |
| if (recovery==null){ |
| multi.add(createStatus(IStatus.ERROR,Messages.ErrorRecoveryLog_noLogEntry+logFile,null)); |
| return multi; |
| } |
| |
| if(recovery.equalsIgnoreCase(START_INSTALL_LOG)){ |
| multi.addAll(processRecoverInstall(prop)); |
| return multi; |
| } |
| |
| if(recovery.equalsIgnoreCase(START_REMOVE_LOG)){ |
| multi.addAll(processRecoverRemove(prop)); |
| return multi; |
| } |
| |
| multi.add(createStatus(IStatus.ERROR,Messages.ErrorRecoveryLog_noRecoveryToExecute+logFile,null)); |
| return multi; |
| } |
| |
| /* |
| * creates a Status |
| */ |
| private IStatus createStatus(int statusSeverity, String msg, Exception e){ |
| String id = |
| UpdateCore.getPlugin().getBundle().getSymbolicName(); |
| |
| StringBuffer completeString = new StringBuffer(""); //$NON-NLS-1$ |
| if (msg!=null) |
| completeString.append(msg); |
| if (e!=null){ |
| completeString.append("\r\n["); //$NON-NLS-1$ |
| completeString.append(e.toString()); |
| completeString.append("]\r\n"); //$NON-NLS-1$ |
| } |
| return new Status(statusSeverity, id, IStatus.OK, completeString.toString(), e); |
| } |
| |
| /* |
| * |
| */ |
| private IStatus processRecoverInstall(Properties prop){ |
| |
| IStatus mainStatus = createStatus(IStatus.OK,"",null); //$NON-NLS-1$ |
| MultiStatus multi = new MultiStatus(mainStatus.getPlugin(),mainStatus.getCode(),"",null); //$NON-NLS-1$ |
| |
| Collection values = prop.values(); |
| |
| if(values.contains(END_INSTALL_LOG)){ |
| // all is good |
| delete(); |
| UpdateCore.warn("Found log file. Log file contains END_INSTALL_LOG. No need to process rename"); //$NON-NLS-1$ |
| multi.add(createStatus(IStatus.OK,null,null)); |
| return multi; |
| } |
| |
| if (values.contains(ALL_INSTALLED) && !forceRemove){ |
| // finish install by renaming |
| int index = 0; |
| boolean found = false; |
| String val = prop.getProperty(LOG_ENTRY_KEY+index); |
| while(val!=null && !found){ |
| if(val.equalsIgnoreCase(ALL_INSTALLED)) found = true; |
| IStatus renameStatus = processRename(val); |
| UpdateCore.log(renameStatus); |
| if(renameStatus.getSeverity()!=IStatus.OK){ |
| multi.add(renameStatus); |
| } |
| index++; |
| val = prop.getProperty(LOG_ENTRY_KEY+index); |
| } |
| if (val==null){ |
| UpdateCore.warn("Unable to find value for :"+LOG_ENTRY_KEY+index); //$NON-NLS-1$ |
| multi.add(createStatus(IStatus.ERROR,Messages.ErrorRecoveryLog_wrongLogFile+LOG_ENTRY_KEY+index,null)); |
| return multi; |
| } |
| // process recovery finished |
| delete(); |
| UpdateCore.warn("Found log file. Successfully recovered by renaming. Feature is installed."); //$NON-NLS-1$ |
| multi.add(createStatus(IStatus.OK,null,null)); |
| } else { |
| // remove all because install did not lay out all the files |
| // or recovery is not allowed |
| int index = 0; |
| String val = prop.getProperty(LOG_ENTRY_KEY+index); |
| while(val!=null){ |
| IStatus removeStatus = processRemove(val); |
| UpdateCore.log(removeStatus); |
| if(removeStatus.getSeverity()!=IStatus.OK){ |
| multi.addAll(removeStatus); |
| } |
| index++; |
| val = prop.getProperty(LOG_ENTRY_KEY+index); |
| } |
| // process recovery finished |
| delete(); |
| UpdateCore.warn("Found log file. Successfully recovered by removing. Feature is removed."); //$NON-NLS-1$ |
| multi.add(createStatus(IStatus.OK,null,null)); |
| } |
| return multi; |
| } |
| |
| /* |
| * |
| */ |
| private IStatus processRename(String val){ |
| |
| // get the path |
| int index = -1; |
| String newFileName = null; |
| if (val.startsWith(PLUGIN_ENTRY)){ |
| index = PLUGIN_ENTRY.length(); |
| newFileName= "plugin.xml"; //$NON-NLS-1$ |
| } else if (val.startsWith(BUNDLE_MANIFEST_ENTRY)){ |
| index = BUNDLE_MANIFEST_ENTRY.length(); |
| newFileName= "META-INF/MANIFEST.MF"; //$NON-NLS-1$ |
| }else if (val.startsWith(FRAGMENT_ENTRY)){ |
| index = FRAGMENT_ENTRY.length(); |
| newFileName= "fragment.xml"; //$NON-NLS-1$ |
| } else if (val.startsWith(FEATURE_ENTRY)){ |
| index = FEATURE_ENTRY.length(); |
| newFileName= "feature.xml"; //$NON-NLS-1$ |
| } else if (val.startsWith(BUNDLE_JAR_ENTRY)){ |
| index = BUNDLE_JAR_ENTRY.length(); |
| } |
| |
| if (index==-1){ |
| return createStatus(IStatus.ERROR,Messages.ErrorRecoveryLog_noAction+val,null); |
| } |
| |
| String oldName = val.substring(index+1); |
| // oldname is com.pid/plugin#####.xml |
| // or oldname is com.pid/pid_pver.jar######.tmp |
| File oldFile = new File(oldName); |
| File newFile; |
| if(val.startsWith(BUNDLE_JAR_ENTRY)){ |
| newFile = new File(oldFile.getAbsolutePath().substring(0, oldFile.getAbsolutePath().lastIndexOf(".jar")+".jar".length())); //$NON-NLS-1$ //$NON-NLS-2$ |
| }else{ |
| newFile = new File(oldFile.getParentFile(),newFileName); |
| } |
| if (!oldFile.exists()){ |
| if (newFile.exists()){ |
| // ok the file has been renamed apparently |
| return createStatus(IStatus.OK,Messages.ErrorRecoveryLog_fileAlreadyRenamed+newFile,null); |
| } else { |
| // the file doesn't exist, log as problem, and force the removal of the feature |
| return createStatus(IStatus.ERROR,Messages.ErrorRecoveryLog_cannotFindFile+oldFile,null); |
| } |
| } |
| |
| boolean sucess = false; |
| if (newFile.exists()) { |
| UpdateManagerUtils.removeFromFileSystem(newFile); |
| UpdateCore.warn("Removing already existing file:"+newFile); //$NON-NLS-1$ |
| } |
| sucess = oldFile.renameTo(newFile); |
| |
| if(!sucess){ |
| String msg =(Messages.ErrorRecoveryLog_oldToNew+oldFile+newFile); |
| return createStatus(IStatus.ERROR,msg,null); |
| } |
| return createStatus(IStatus.OK,Messages.ErrorRecoveryLog_renamed+oldFile+Messages.ErrorRecoveryLog_to+newFile,null); |
| } |
| |
| /* |
| * |
| */ |
| private IStatus processRemove(String val){ |
| |
| IStatus mainStatus = createStatus(IStatus.OK,"",null); //$NON-NLS-1$ |
| MultiStatus multi = new MultiStatus(mainStatus.getPlugin(),mainStatus.getCode(),"",null); //$NON-NLS-1$ |
| |
| // get the path |
| int index = -1; |
| if (val.startsWith(BUNDLE_JAR_ENTRY)){ |
| index = BUNDLE_JAR_ENTRY.length(); |
| } |
| |
| if (index==-1){ |
| return createStatus(IStatus.ERROR,Messages.ErrorRecoveryLog_noAction+val,null); |
| } |
| |
| String oldName = val.substring(index+1); |
| File oldFile = new File(oldName); |
| if (!oldFile.exists()){ |
| // the jar or directory doesn't exist, log as problem, and force the removal of the feature |
| multi.add(createStatus(IStatus.ERROR,Messages.ErrorRecoveryLog_cannotFindFile+oldFile,null)); |
| return multi; |
| } |
| multi.addAll(removeFromFileSystem(oldFile)); |
| |
| return multi; |
| } |
| |
| /** |
| * return a multi status, |
| * the children are the file that couldn't be removed |
| */ |
| public IStatus removeFromFileSystem(File file) { |
| |
| IStatus mainStatus = createStatus(IStatus.OK,"",null); //$NON-NLS-1$ |
| MultiStatus multi = new MultiStatus(mainStatus.getPlugin(),mainStatus.getCode(),"",null); //$NON-NLS-1$ |
| |
| if (!file.exists()){ |
| multi.add(createStatus(IStatus.ERROR,Messages.ErrorRecoveryLog_noFiletoRemove+file,null)); |
| return multi; |
| } |
| |
| if (file.isDirectory()) { |
| String[] files = file.list(); |
| if (files != null) // be careful since file.list() can return null |
| for (int i = 0; i < files.length; ++i){ |
| multi.addAll(removeFromFileSystem(new File(file, files[i]))); |
| } |
| } |
| |
| if (!file.delete()) { |
| String msg = "Unable to remove file" +file.getAbsolutePath(); //$NON-NLS-1$ |
| multi.add(createStatus(IStatus.ERROR,msg,null)); |
| } |
| return multi; |
| } |
| |
| /* |
| * |
| */ |
| private IStatus processRecoverRemove(Properties prop){ |
| |
| IStatus mainStatus = createStatus(IStatus.OK,"",null); //$NON-NLS-1$ |
| MultiStatus multi = new MultiStatus(mainStatus.getPlugin(),mainStatus.getCode(),"",null); //$NON-NLS-1$ |
| |
| Collection values = prop.values(); |
| |
| if(values.contains(END_REMOVE_LOG)){ |
| // all is good |
| delete(); |
| UpdateCore.warn("Found log file. Log file contains END_REMOVE_LOG. No need to process rename"); //$NON-NLS-1$ |
| multi.add(createStatus(IStatus.OK,null,null)); |
| return multi; |
| } |
| |
| if (!values.contains(END_ABOUT_REMOVE)){ |
| // finish install by renaming |
| multi.add(createStatus(IStatus.ERROR,Messages.ErrorRecoveryLog_removeFeature,null)); |
| return multi; |
| } else { |
| // finish install by renaming |
| int index = 0; |
| boolean found = false; |
| String val = prop.getProperty(LOG_ENTRY_KEY+index); |
| while(val!=null && !found){ |
| if(val.equalsIgnoreCase(END_ABOUT_REMOVE)) found = true; |
| IStatus renameStatus = processRemove(val); |
| UpdateCore.log(renameStatus); |
| if(renameStatus.getSeverity()!=IStatus.OK){ |
| multi.add(renameStatus); |
| } |
| index++; |
| val = prop.getProperty(LOG_ENTRY_KEY+index); |
| } |
| if (val==null){ |
| UpdateCore.warn("Unable to find value for :"+LOG_ENTRY_KEY+index); //$NON-NLS-1$ |
| multi.add(createStatus(IStatus.ERROR,Messages.ErrorRecoveryLog_wrongLogFile+LOG_ENTRY_KEY+index,null)); |
| return multi; |
| } |
| // process recovery finished |
| delete(); |
| UpdateCore.warn("Found log file. Successfully recovered by deleting. Feature is removed."); //$NON-NLS-1$ |
| multi.add(createStatus(IStatus.OK,null,null)); |
| } |
| return multi; |
| } |
| } |