/*- * Copyright (C) 2006-2014 Erik Larsson * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.catacombae.hfsexplorer; import java.awt.BorderLayout; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.net.MalformedURLException; import java.net.URL; import java.security.InvalidKeyException; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.JCheckBoxMenuItem; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPopupMenu; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.filechooser.FileFilter; import org.catacombae.dmg.encrypted.ReadableCEncryptedEncodingStream; import org.catacombae.dmg.sparsebundle.ReadableSparseBundleStream; import org.catacombae.dmg.sparseimage.ReadableSparseImageStream; import org.catacombae.dmg.sparseimage.SparseImageRecognizer; import org.catacombae.dmg.udif.UDIFDetector; import org.catacombae.dmg.udif.UDIFRandomAccessStream; import org.catacombae.dmgextractor.ui.PasswordDialog; import org.catacombae.hfs.ProgressMonitor; import org.catacombae.hfs.types.hfscommon.CommonHFSVolumeHeader; import org.catacombae.hfs.types.hfsplus.HFSPlusVolumeHeader; import org.catacombae.hfsexplorer.ExtractProgressMonitor.CreateDirectoryFailedAction; import org.catacombae.hfsexplorer.ExtractProgressMonitor.CreateFileFailedAction; import org.catacombae.hfsexplorer.ExtractProgressMonitor.DirectoryExistsAction; import org.catacombae.hfsexplorer.ExtractProgressMonitor.ExtractProperties; import org.catacombae.hfsexplorer.ExtractProgressMonitor.FileExistsAction; import org.catacombae.hfsexplorer.ExtractProgressMonitor.UnhandledExceptionAction; import org.catacombae.hfsexplorer.FileSystemBrowser.Record; import org.catacombae.hfsexplorer.FileSystemBrowser.RecordType; import org.catacombae.hfsexplorer.fs.AppleSingleBuilder; import org.catacombae.hfsexplorer.fs.AppleSingleBuilder.AppleSingleVersion; import org.catacombae.hfsexplorer.fs.AppleSingleBuilder.FileSystem; import org.catacombae.hfsexplorer.fs.AppleSingleBuilder.FileType; import org.catacombae.hfsexplorer.gui.ErrorSummaryPanel; import org.catacombae.hfsexplorer.gui.FileOperationsPanel; import org.catacombae.hfsexplorer.gui.HFSExplorerJFrame; import org.catacombae.hfsexplorer.gui.MemoryStatisticsPanel; import org.catacombae.hfsexplorer.helpbrowser.HelpBrowserPanel; import org.catacombae.io.ReadableConcatenatedStream; import org.catacombae.io.ReadableFileStream; import org.catacombae.io.ReadableRandomAccessStream; import org.catacombae.io.ReadableRandomAccessSubstream; import org.catacombae.io.SynchronizedReadableRandomAccessStream; import org.catacombae.storage.fs.FSAttributes.POSIXFileAttributes; import org.catacombae.storage.fs.FSEntry; import org.catacombae.storage.fs.FSFile; import org.catacombae.storage.fs.FSFolder; import org.catacombae.storage.fs.FSFork; import org.catacombae.storage.fs.FSForkType; import org.catacombae.storage.fs.FSLink; import org.catacombae.storage.fs.FileSystemHandlerFactory; import org.catacombae.storage.fs.FileSystemHandlerFactory.StandardAttribute; import org.catacombae.storage.fs.FileSystemMajorType; import org.catacombae.storage.fs.hfscommon.HFSCommonFileSystemHandler; import org.catacombae.storage.fs.hfscommon.HFSCommonFileSystemRecognizer; import org.catacombae.storage.fs.hfscommon.HFSCommonFileSystemRecognizer.FileSystemType; import org.catacombae.storage.io.DataLocator; import org.catacombae.storage.io.ReadableStreamDataLocator; import org.catacombae.storage.io.win32.ReadableWin32FileStream; import org.catacombae.storage.ps.Partition; import org.catacombae.storage.ps.PartitionSystemDetector; import org.catacombae.storage.ps.PartitionSystemHandler; import org.catacombae.storage.ps.PartitionSystemHandlerFactory; import org.catacombae.storage.ps.PartitionSystemType; import org.catacombae.storage.ps.PartitionType; import org.catacombae.util.ObjectContainer; import org.catacombae.util.Util.Pair; /** * The main window for the graphical part of HFSExplorer. This class contains a lot of * non-presentation code and is very large. Should be restructured in the future. * * @author <a href="http://www.catacombae.org/" target="_top">Erik Larsson</a> */ public class FileSystemBrowserWindow extends HFSExplorerJFrame { private static final String TITLE_STRING = "HFSExplorer " + HFSExplorer.VERSION; private static final String[] VERSION_INFO_DICTIONARY = { "http://www.catacombae.org/hfsexplorer/version.sdic.txt", "http://catacombae.sourceforge.net/hfsexplorer/version.sdic.txt", }; /** * The command line argument that makes HFSExplorer print stdout and * stderr to a special debug console. */ private static final String DEBUG_CONSOLE_ARG = "-dbgconsole"; private FileSystemBrowser<FSEntry> fsb; // Fast accessors for the corresponding variables in org.catacombae.hfsexplorer.gui.FilesystemBrowserPanel private JCheckBoxMenuItem toggleCachingItem; // For managing all files opened with the "open file" command private final LinkedList<File> tempFiles = new LinkedList<File>(); private final JFileChooser fileChooser = new JFileChooser(); //private HFSPlusFileSystemView fsView; private final DebugConsoleWindow dcw; private HFSCommonFileSystemHandler fsHandler = null; /** The backing data locator for the fsHandler. Useful when extracting raw data. */ private DataLocator fsDataLocator; public FileSystemBrowserWindow() { this(null); } public FileSystemBrowserWindow(final DebugConsoleWindow dcw) { super(TITLE_STRING); this.dcw = dcw; fsb = new FileSystemBrowser<FSEntry>(new FileSystemProvider()); //final Class objectClass = new Object().getClass(); setUpMenus(); if(MacUtil.isMacOSX()) { MacUtil.registerMacApplicationHandler(new MacUtil.MacApplicationHandler() { /* @Override */ public boolean acceptQuit() { exitApplication(); return false; } /* @Override */ public void showAboutDialog() { actionShowAboutDialog(); } }); } addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent we) { exitApplication(); } }); setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); add(fsb.getViewComponent(), BorderLayout.CENTER); pack(); setLocationRelativeTo(null); } private void setUpMenus() { // Menus JMenuItem loadFSFromDeviceItem = null; if(SelectDeviceDialog.isSystemSupported()) { // Only for Windows systems... loadFSFromDeviceItem = new JMenuItem("Load file system from device..."); loadFSFromDeviceItem.addActionListener(new ActionListener() { /* @Override */ public void actionPerformed(ActionEvent ae) { SelectDeviceDialog deviceDialog = SelectDeviceDialog.createSelectDeviceDialog( FileSystemBrowserWindow.this, true, "Load file system from device"); deviceDialog.setVisible(true); ReadableRandomAccessStream io = deviceDialog.getPartitionStream(); String pathName = deviceDialog.getPathName(); if(io != null) { SynchronizedReadableRandomAccessStream syncStream = new SynchronizedReadableRandomAccessStream(io); try { loadFS(syncStream, pathName); } catch(Exception e) { System.err.print("INFO: Non-critical exception when trying to load file system from \"" + pathName + "\": "); e.printStackTrace(); JOptionPane.showMessageDialog(FileSystemBrowserWindow.this, "Could not find any file systems on device!", "Error", JOptionPane.ERROR_MESSAGE); } finally { syncStream.close(); } } } }); loadFSFromDeviceItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_L, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); } /* JMenuItem loadFSFromFileItem = new JMenuItem("Load file system from file..."); loadFSFromFileItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent ae) { //JFileChooser fileChooser = new JFileChooser(); fileChooser.setMultiSelectionEnabled(false); fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); if(fileChooser.showOpenDialog(FileSystemBrowserWindow.this) == JFileChooser.APPROVE_OPTION) { try { String pathName = fileChooser.getSelectedFile().getCanonicalPath(); loadFS(pathName); } catch(IOException ioe) { ioe.printStackTrace(); JOptionPane.showMessageDialog(FileSystemBrowserWindow.this, "Count not resolve pathname!", "Error", JOptionPane.ERROR_MESSAGE); } catch(Exception e) { e.printStackTrace(); JOptionPane.showMessageDialog(FileSystemBrowserWindow.this, "Could not read contents of partition!", "Error", JOptionPane.ERROR_MESSAGE); } } } }); loadFSFromFileItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); */ JMenuItem openUDIFItem = new JMenuItem("Load file system from file..."); openUDIFItem.addActionListener(new ActionListener() { /* @Override */ public void actionPerformed(ActionEvent ae) { //JFileChooser fileChooser = new JFileChooser(); fileChooser.setMultiSelectionEnabled(false); fileChooser.setFileSelectionMode( JFileChooser.FILES_AND_DIRECTORIES); SimpleFileFilter dmgFilter = new SimpleFileFilter(); dmgFilter.addExtension("dmg"); dmgFilter.setDescription("Mac OS X disk images (*.dmg)"); fileChooser.addChoosableFileFilter(dmgFilter); SimpleFileFilter cdrFilter = new SimpleFileFilter(); cdrFilter.addExtension("iso"); cdrFilter.addExtension("cdr"); cdrFilter.setDescription("CD/DVD image (*.iso,*.cdr)"); fileChooser.addChoosableFileFilter(cdrFilter); FileFilter sparseBundleFilter = new FileFilter() { @Override public boolean accept(File file) { if(file.isDirectory() && file.getName().endsWith(".sparsebundle")) { return true; } else return false; } @Override public String getDescription() { return "Mac OS X sparse bundles (*.sparsebundle)"; } }; fileChooser.addChoosableFileFilter(sparseBundleFilter); SimpleFileFilter sparseimageFilter = new SimpleFileFilter(); sparseimageFilter.addExtension("sparseimage"); sparseimageFilter.setDescription("Mac OS X sparse images " + "(*.sparseimage)"); fileChooser.addChoosableFileFilter(sparseimageFilter); SimpleFileFilter imgFilter = new SimpleFileFilter(); imgFilter.addExtension("img"); imgFilter.setDescription("Raw disk image (*.img)"); fileChooser.addChoosableFileFilter(imgFilter); fileChooser.setFileFilter(fileChooser.getAcceptAllFileFilter()); int res = fileChooser.showOpenDialog(FileSystemBrowserWindow.this); if(res == JFileChooser.APPROVE_OPTION) { try { File selectedFile = fileChooser.getSelectedFile(); String pathName = selectedFile.getCanonicalPath(); loadFSWithUDIFAutodetect(pathName); } catch(IOException ioe) { ioe.printStackTrace(); JOptionPane.showMessageDialog(FileSystemBrowserWindow.this, "Count not resolve pathname!", "Error", JOptionPane.ERROR_MESSAGE); } catch(Exception e) { e.printStackTrace(); JOptionPane.showMessageDialog(FileSystemBrowserWindow.this, "Could not read contents of partition!", "Error", JOptionPane.ERROR_MESSAGE); } } fileChooser.resetChoosableFileFilters(); } }); openUDIFItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); JMenuItem loadFromPathItem = new JMenuItem("Load file system from path..."); loadFromPathItem.addActionListener(new ActionListener() { /* @Override */ public void actionPerformed(ActionEvent ae) { String path = JOptionPane.showInputDialog(FileSystemBrowserWindow.this, "Pathname to load:", "Load file system from path", JOptionPane.QUESTION_MESSAGE); if(path != null) { try { loadFSWithUDIFAutodetect(path); } catch(Exception e) { e.printStackTrace(); JOptionPane.showMessageDialog(FileSystemBrowserWindow.this, "Could not read contents of partition!", "Error", JOptionPane.ERROR_MESSAGE); } } } }); loadFromPathItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); /* JMenuItem openFromPosItem = new JMenuItem("Read file system from specified position in file..."); openFromPosItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent ae) { //JFileChooser fileChooser = new JFileChooser(); fileChooser.setMultiSelectionEnabled(false); fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); SimpleFileFilter sff = new SimpleFileFilter(); sff.addExtension("dmg"); sff.setDescription("Disk images"); fileChooser.setFileFilter(sff); int res = fileChooser.showOpenDialog(FileSystemBrowserWindow.this); if(res == JFileChooser.APPROVE_OPTION) { try { File selectedFile = fileChooser.getSelectedFile(); String pathName = selectedFile.getCanonicalPath(); String s = JOptionPane.showInputDialog(FileSystemBrowserWindow.this, "Enter the byte position of the start of the file system."); long pos = Long.parseLong(s); loadFSWithUDIFAutodetect(pathName, pos); } catch(IOException ioe) { ioe.printStackTrace(); JOptionPane.showMessageDialog(FileSystemBrowserWindow.this, "Count not resolve pathname!", "Error", JOptionPane.ERROR_MESSAGE); } catch(Exception e) { e.printStackTrace(); JOptionPane.showMessageDialog(FileSystemBrowserWindow.this, "Could not read contents of partition!", "Error", JOptionPane.ERROR_MESSAGE); } } fileChooser.resetChoosableFileFilters(); } }); */ JMenuItem debugConsoleItem = null; if(dcw != null) { debugConsoleItem = new JMenuItem("Debug console"); debugConsoleItem.addActionListener(new ActionListener() { /* @Override */ public void actionPerformed(ActionEvent ae) { dcw.setVisible(true); } }); } JMenuItem exitProgramItem = null; if(!System.getProperty("os.name").toLowerCase().startsWith("mac os x")) { exitProgramItem = new JMenuItem("Exit"); exitProgramItem.addActionListener(new ActionListener() { /* @Override */ public void actionPerformed(ActionEvent ae) { exitApplication(); } }); exitProgramItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); } JMenuItem volumeInfoItem = new JMenuItem("Volume information"); volumeInfoItem.addActionListener(new ActionListener() { /* @Override */ public void actionPerformed(ActionEvent ae) { if(ensureFileSystemLoaded()) { VolumeInfoWindow infoWindow = new VolumeInfoWindow(fsHandler.getFSView()); infoWindow.setVisible(true); CommonHFSVolumeHeader cvh = fsHandler.getFSView().getVolumeHeader(); if(cvh instanceof CommonHFSVolumeHeader.HFSPlusImplementation) { HFSPlusVolumeHeader vh = ((CommonHFSVolumeHeader.HFSPlusImplementation) cvh).getUnderlying(); //infoWindow.setVolumeFields(vh); /* if(vh.getAttributeVolumeJournaled()) { infoWindow.setJournalFields(fsHandler.getFSView().getJournalInfoBlock()); } * */ } /* else JOptionPane.showMessageDialog(FileSystemBrowserWindow.this, "Info window only supported for HFS+/HFSX.", "Error", JOptionPane.ERROR_MESSAGE); */ } } }); volumeInfoItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); toggleCachingItem = new JCheckBoxMenuItem("Use file system caching"); toggleCachingItem.setState(true); toggleCachingItem.addActionListener(new ActionListener() { /* @Override */ public void actionPerformed(ActionEvent ae) { if(fsHandler != null) { if(toggleCachingItem.getState()) { System.out.print("Enabling caching..."); fsHandler.getFSView().enableFileSystemCaching(); System.out.println("done!"); } else { System.out.print("Disabling caching..."); fsHandler.getFSView().disableFileSystemCaching(); System.out.println("done!"); } } } }); /* JMenuItem setFileReadOffsetItem = new JMenuItem("Set file read offset..."); setFileReadOffsetItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent ae) { String s = JOptionPane.showInputDialog(FileSystemBrowserWindow.this, "Enter offset:", BaseHFSFileSystemView.fileReadOffset); if(s != null) { BaseHFSFileSystemView.fileReadOffset = Long.parseLong(s); } } }); */ JMenuItem memoryStatisticsItem = new JMenuItem("Memory statistics"); memoryStatisticsItem.addActionListener(new ActionListener() { /* @Override */ public void actionPerformed(ActionEvent ae) { MemoryStatisticsPanel.createMemoryStatisticsWindow().setVisible(true); } }); JMenuItem createDiskImageItem = new JMenuItem("Create disk image..."); createDiskImageItem.addActionListener(new ActionListener() { /* @Override */ public void actionPerformed(ActionEvent ae) { if(ensureFileSystemLoaded()) { actionExtractToDiskImage(); } } }); JMenuItem startHelpBrowserItem = new JMenuItem("Help browser"); startHelpBrowserItem.addActionListener(new ActionListener() { /* @Override */ public void actionPerformed(ActionEvent ae) { try { URL url = new URL(FileSystemBrowserWindow.class. getProtectionDomain().getCodeSource().getLocation(), "../doc/html/index.html"); HelpBrowserPanel.showHelpBrowserWindow("HFSExplorer help " + "browser", url); } catch(MalformedURLException ex) { ex.printStackTrace(); Logger.getLogger(FileSystemBrowserWindow.class.getName()). log(Level.WARNING, null, ex); } } }); JMenuItem checkUpdatesItem = new JMenuItem("Check for updates..."); checkUpdatesItem.addActionListener(new ActionListener() { /* @Override */ public void actionPerformed(ActionEvent ae) { InputStream infoDictStream = null; for(String s : VERSION_INFO_DICTIONARY) { try { System.out.println("Retrieving version info from " + s + "..."); infoDictStream = new URL(s).openStream(); SimpleDictionaryParser sdp = new SimpleDictionaryParser(infoDictStream); String dictVersion = sdp.getValue("Version"); long dictBuildNumber = Long.parseLong(sdp.getValue("Build")); System.out.println(" Version: " + dictVersion); System.out.println(" Build number: " + dictBuildNumber); boolean dictVersionIsHigher = false; if(true) { dictVersionIsHigher = dictBuildNumber > BuildNumber.BUILD_NUMBER; } else { // Old disabled code char[] dictVersionArray = dictVersion.toCharArray(); char[] myVersionArray = HFSExplorer.VERSION.toCharArray(); int minArrayLength = Math.min(dictVersionArray.length, myVersionArray.length); boolean foundDifference = false; for(int i = 0; i < minArrayLength; ++i) { if(dictVersionArray[i] > myVersionArray[i]) { dictVersionIsHigher = true; foundDifference = true; break; } else if(dictVersionArray[i] < myVersionArray[i]) { dictVersionIsHigher = false; foundDifference = true; break; } } if(!foundDifference) { dictVersionIsHigher = dictVersionArray.length > myVersionArray.length; } } if(dictVersionIsHigher) { JOptionPane.showMessageDialog(FileSystemBrowserWindow.this, "There are updates available!\n" + "Latest version is: " + dictVersion + " (build number #" + dictBuildNumber + ")", "Information", JOptionPane.INFORMATION_MESSAGE); } else { JOptionPane.showMessageDialog(FileSystemBrowserWindow.this, "There are no updates available.", "Information", JOptionPane.INFORMATION_MESSAGE); } return; } catch(Exception e) { e.printStackTrace(); } } JOptionPane.showMessageDialog(FileSystemBrowserWindow.this, "Could not contact version URL.", "Error", JOptionPane.ERROR_MESSAGE); } }); JMenuItem aboutItem = null; if(!System.getProperty("os.name").toLowerCase().startsWith("mac os x")) { aboutItem = new JMenuItem("About..."); aboutItem.addActionListener(new ActionListener() { /* @Override */ public void actionPerformed(ActionEvent ae) { actionShowAboutDialog(); } }); } JMenu fileMenu = new JMenu("File"); JMenu infoMenu = new JMenu("Tools"); JMenu helpMenu = new JMenu("Help"); if(loadFSFromDeviceItem != null) { fileMenu.add(loadFSFromDeviceItem); } //fileMenu.add(loadFSFromFileItem); fileMenu.add(openUDIFItem); fileMenu.add(loadFromPathItem); //fileMenu.add(openFromPosItem); if(debugConsoleItem != null) { fileMenu.add(debugConsoleItem); } if(exitProgramItem != null) { fileMenu.add(exitProgramItem); } infoMenu.add(volumeInfoItem); infoMenu.add(toggleCachingItem); infoMenu.add(createDiskImageItem); //infoMenu.add(setFileReadOffsetItem); infoMenu.add(memoryStatisticsItem); helpMenu.add(startHelpBrowserItem); helpMenu.add(checkUpdatesItem); if(aboutItem != null) helpMenu.add(aboutItem); JMenuBar menuBar = new JMenuBar(); menuBar.add(fileMenu); menuBar.add(infoMenu); menuBar.add(helpMenu); setJMenuBar(menuBar); // /Menus } private boolean ensureFileSystemLoaded() { if(fsHandler != null) { return true; } else { JOptionPane.showMessageDialog(this, "No file system " + "loaded.", "Error", JOptionPane.ERROR_MESSAGE); return false; } } private void exitApplication() { boolean doExit = true; try { // Clean up temp files. if(tempFiles.size() > 0) { long totalFileSize = 0; for(File tempFile : tempFiles) { totalFileSize += tempFile.length(); } int res = JOptionPane.showConfirmDialog(this, "You have " + tempFiles.size() + " temporary files with a total size of " + totalFileSize + " bytes in:\n \"" + System.getProperty("java.io.tmpdir") + "\"\nDo you want to delete them now?", "Cleanup on program exit", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE); if(res == JOptionPane.YES_OPTION) { for(File tempFile : tempFiles) { if(!tempFile.exists()) { continue; } boolean delRes = tempFile.delete(); while(!delRes) { int res2 = JOptionPane.showConfirmDialog(this, "Could not delete file:\n \"" + tempFile.getAbsolutePath() + "\"\nTry again?", "Could not delete file", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.ERROR_MESSAGE); if(res2 == JOptionPane.YES_OPTION) { delRes = tempFile.delete(); } else if(res2 == JOptionPane.NO_OPTION) { break; } else { return; } } } } else if(res != JOptionPane.NO_OPTION) { doExit = false; return; } } setVisible(false); if(fsHandler != null) { fsHandler.close(); } } catch(Throwable t) { GUIUtil.displayExceptionDialog(t, 20, this, "Exception when exiting application"); } finally { if(doExit) { System.exit(0); } } } public void loadFSWithUDIFAutodetect(String filename) { loadFSWithUDIFAutodetect(filename, 0); } public void loadFSWithUDIFAutodetect(String filename, long pos) { ReadableRandomAccessStream fsFile; try { File f = new File(filename); if(f.isDirectory()) { fsFile = new ReadableSparseBundleStream(f); } else if(ReadableWin32FileStream.isSystemSupported()) { fsFile = new ReadableWin32FileStream(filename); } else { fsFile = new ReadableFileStream(filename); } try { System.err.println("Trying to detect CEncryptedEncoding structure..."); if(ReadableCEncryptedEncodingStream.isCEncryptedEncoding(fsFile)) { System.err.println("CEncryptedEncoding structure found! Creating filter stream..."); while(true) { char[] res = PasswordDialog.showDialog(null, "Reading encrypted disk image...", "You need to enter a password to unlock this disk image:"); if(res == null) return; else { try { ReadableCEncryptedEncodingStream stream = new ReadableCEncryptedEncodingStream(fsFile, res); try { stream.read(new byte[512]); fsFile = stream; break; } catch(Exception e) { Throwable cause = e.getCause(); if(!(cause instanceof InvalidKeyException)) { throw e; } JOptionPane.showMessageDialog(null, "If you were trying to load an " + "AES-256 encrypted image and\n" + "are using Sun/Oracle's Java " + "Runtime Environment, then \n" + "please check if you have " + "installed the Java " + "Cryptography\n" + "Extension (JCE) Unlimited " + "Strength Jurisdiction Policy\n" + "Files, which are required for " + "AES-256 support in Java.", "Unsupported AES key size", JOptionPane.ERROR_MESSAGE); break; } } catch(Exception e) { JOptionPane.showMessageDialog(null, "Incorrect password.", "Reading encrypted disk image...", JOptionPane.ERROR_MESSAGE); } } } } else { System.err.println("CEncryptedEncoding structure not found. Proceeding..."); } } catch(Exception e) { System.err.println("[INFO] Non-critical exception while trying to detect CEncryptedEncoding structure:"); e.printStackTrace(); } try { System.err.println("Detecting sparseimage structure..."); if(SparseImageRecognizer.isSparseImage(fsFile)) { System.err.println("sparseimage structure found! Creating " + "filter stream..."); try { ReadableSparseImageStream stream = new ReadableSparseImageStream(fsFile); fsFile = stream; } catch(Exception e) { System.err.println("Exception while creating readable " + "sparseimage stream:"); e.printStackTrace(); } } } catch(Exception e) { System.err.println("[INFO] Non-critical exception while " + "trying to detect sparseimage structure:"); e.printStackTrace(); } try { System.err.println("Trying to detect UDIF structure..."); if(UDIFDetector.isUDIFEncoded(fsFile)) { System.err.println("UDIF structure found! Creating filter stream..."); UDIFRandomAccessStream stream = null; try { stream = new UDIFRandomAccessStream(fsFile); } catch(Exception e) { e.printStackTrace(); if(e.getMessage().startsWith("java.lang.RuntimeException: No handler for block type")) { JOptionPane.showMessageDialog(FileSystemBrowserWindow.this, "UDIF file contains unsupported block types!\n" + "(The file was probably created with BZIP2 or ADC " + "compression, which is unsupported currently)", "Error", JOptionPane.ERROR_MESSAGE); } else { JOptionPane.showMessageDialog(FileSystemBrowserWindow.this, "UDIF file unsupported or damaged!", "Error", JOptionPane.ERROR_MESSAGE); } return; } if(stream != null) { fsFile = stream; } } else { System.err.println("UDIF structure not found. Proceeding..."); } } catch(Exception e) { System.err.println("[INFO] Non-critical exception while trying to detect UDIF structure:"); e.printStackTrace(); } if(pos != 0) { fsFile = new ReadableConcatenatedStream(fsFile, pos, fsFile.length() - pos); } String displayName; try { displayName = new File(filename).getCanonicalFile().getName(); } catch(Exception e) { displayName = filename; } SynchronizedReadableRandomAccessStream syncStream = new SynchronizedReadableRandomAccessStream(fsFile); try { loadFS(syncStream, displayName); } finally { syncStream.close(); } } catch(Exception e) { System.err.println("Could not open file! Exception thrown:"); e.printStackTrace(); JOptionPane.showMessageDialog(this, "Could not open file:\n \"" + filename + "\"", "Error", JOptionPane.ERROR_MESSAGE); } } public void loadFS(String filename) { ReadableRandomAccessStream fsFile; if(ReadableWin32FileStream.isSystemSupported()) { fsFile = new ReadableWin32FileStream(filename); } else { fsFile = new ReadableFileStream(filename); } SynchronizedReadableRandomAccessStream syncStream = new SynchronizedReadableRandomAccessStream(fsFile); try { loadFS(syncStream, new File(filename).getName()); } finally { syncStream.close(); } } public void loadFS(SynchronizedReadableRandomAccessStream fsFile, String displayName) { long fsOffset; long fsLength; // Detect partition system PartitionSystemType[] matchingTypes = PartitionSystemDetector.detectPartitionSystem(fsFile, false); if(matchingTypes.length > 1) { String message = "Multiple partition system types detected:"; for(PartitionSystemType type : matchingTypes) message += "\n" + type; JOptionPane.showMessageDialog(this, message, "Partition system detection error", JOptionPane.ERROR_MESSAGE); return; } else if(matchingTypes.length == 1) { PartitionSystemType psType = matchingTypes[0]; PartitionSystemHandlerFactory psFact = psType.createDefaultHandlerFactory(); if(psFact == null) { JOptionPane.showMessageDialog(this, "Can't find handler for " + "partition system type " + psType, "Unsupported partition system", JOptionPane.ERROR_MESSAGE); return; } PartitionSystemHandler psHandler = psFact.createHandler(new ReadableStreamDataLocator( new ReadableRandomAccessSubstream(fsFile))); Partition[] partitions; try { partitions = psHandler.getPartitions(); } finally { psHandler.close(); } if(partitions.length == 0) { // Proceed to detect file system fsOffset = 0; try { fsLength = fsFile.length(); } catch(Exception e) { e.printStackTrace(); fsLength = -1; } } else { /* Search for suitable partitions, and make the first one that * was found the default value for the dialog box. */ int defaultSelection = 0; for(int i = 0; i < partitions.length; ++i) { Partition p = partitions[i]; PartitionType pt = p.getType(); if(pt == PartitionType.APPLE_HFS_CONTAINER || pt == PartitionType.APPLE_HFSX) { defaultSelection = i; break; } } /* Prompt user to choose a partition to load. */ Partition selectedPartition = null; while(true) { Object selectedValue = JOptionPane.showInputDialog(this, "Select which partition to load", "Choose " + psType.getLongName() + " partition", JOptionPane.QUESTION_MESSAGE, null, partitions, partitions[defaultSelection]); if(selectedValue == null) return; // Abort the load process. else if(selectedValue instanceof Partition) { selectedPartition = (Partition) selectedValue; PartitionType pt = selectedPartition.getType(); if(pt == PartitionType.APPLE_HFS_CONTAINER || pt == PartitionType.APPLE_HFSX) { break; } else { JOptionPane.showMessageDialog(this, "Can't find handler for partition type \"" + selectedPartition.getType() + "\"", "Unknown partition type", JOptionPane.ERROR_MESSAGE); } } else throw new RuntimeException("selectedValue not " + "instanceof Partition! selectedValue: " + selectedValue.getClass()); } /* A selection was made. */ fsOffset = selectedPartition.getStartOffset(); fsLength = selectedPartition.getLength(); //System.err.println("DEBUG Selected partition:"); //selectedPartition.print(System.err, " "); } } else { fsOffset = 0; try { fsLength = fsFile.length(); } catch(Exception e) { e.printStackTrace(); fsLength = -1; } } // Detect HFS file system FileSystemType fsType = HFSCommonFileSystemRecognizer.detectFileSystem( fsFile, fsOffset); switch(fsType) { case HFS: case HFS_WRAPPED_HFS_PLUS: case HFS_PLUS: case HFSX: // Reset browser fsb.setRoot(null); if(fsHandler != null) { fsHandler.close(); fsHandler = null; } if(fsDataLocator != null) { fsDataLocator.close(); fsDataLocator = null; } final FileSystemMajorType fsMajorType; switch(fsType) { case HFS: fsMajorType = FileSystemMajorType.APPLE_HFS; break; case HFS_PLUS: case HFS_WRAPPED_HFS_PLUS: fsMajorType = FileSystemMajorType.APPLE_HFS_PLUS; break; case HFSX: fsMajorType = FileSystemMajorType.APPLE_HFSX; break; default: throw new RuntimeException("Unhandled type: " + fsType); } FileSystemHandlerFactory factory = fsMajorType.createDefaultHandlerFactory(); if(factory.isSupported(StandardAttribute.CACHING_ENABLED)) { factory.getCreateAttributes().setBooleanAttribute( StandardAttribute.CACHING_ENABLED, toggleCachingItem.getState()); } //System.err.println("loadFS(): fsFile=" + fsFile); //System.err.println("loadFS(): Creating ReadableConcatenatedStream..."); ReadableRandomAccessStream stage1; if(fsLength > 0) stage1 = new ReadableConcatenatedStream( new ReadableRandomAccessSubstream(fsFile), fsOffset, fsLength); else if(fsOffset == 0) stage1 = new ReadableRandomAccessSubstream(fsFile); else throw new RuntimeException("length undefined and offset " + "!= 0 (fsLength=" + fsLength + " fsOffset=" + fsOffset + ")"); //System.err.println("loadFS(): Creating ReadableStreamDataLocator..."); this.fsDataLocator = new ReadableStreamDataLocator(stage1); //System.err.println("loadFS(): Creating fsHandler..."); fsHandler = (HFSCommonFileSystemHandler) factory.createHandler(fsDataLocator); FSFolder rootRecord = fsHandler.getRoot(); //FSEntry[] rootContents = rootRecord.list(); populateFilesystemGUI(rootRecord); setTitle(TITLE_STRING + " - [" + displayName + "]"); //adjustTableWidth(); break; default: JOptionPane.showMessageDialog(this, "Invalid HFS type.\n" + "HFSExplorer supports:\n" + " " + FileSystemType.HFS_PLUS + "\n" + " " + FileSystemType.HFSX + "\n" + " " + FileSystemType.HFS_WRAPPED_HFS_PLUS + "\n" + " " + FileSystemType.HFS + "\n" + "\nDetected type is (" + fsType + ").", "Unsupported file system type", JOptionPane.ERROR_MESSAGE); break; } } private long extractForkToStream(FSFork theFork, OutputStream os, ProgressMonitor pm) throws IOException { ReadableRandomAccessStream forkFilter = theFork.getReadableRandomAccessStream(); //System.out.println("extractForkToStream working with a " + forkFilter.getClass()); final long originalLength = theFork.getLength(); long bytesToRead = originalLength; byte[] buffer = new byte[1*1024*1024]; while(bytesToRead > 0) { if(pm.cancelSignaled()) { break; //System.out.print("forkFilter.read([].length=" + buffer.length + ", 0, " + (bytesToRead < buffer.length ? (int)bytesToRead : buffer.length) + "..."); } int bytesRead = forkFilter.read(buffer, 0, (bytesToRead < buffer.length ? (int) bytesToRead : buffer.length)); //System.out.println("done. bytesRead = " + bytesRead); if(bytesRead < 0) { break; } else { //System.out.println("Read the following from the forkfilter (" + bytesRead + " bytes): "); //System.out.println(Util.byteArrayToHexString(buffer, 0, bytesRead)); pm.addDataProgress(bytesRead); os.write(buffer, 0, bytesRead); bytesToRead -= bytesRead; } } return originalLength - bytesToRead; } private long extractAdditionalForksToAppleDoubleStream(FSEntry entry, OutputStream os, ProgressMonitor pm) throws IOException { ByteArrayOutputStream baos = null; ReadableRandomAccessStream in = null; try { final LinkedList<Pair<String, byte[]>> attributeList = new LinkedList<Pair<String, byte[]>>(); byte[] finderInfoData = null; byte[] resourceForkData = null; final AppleSingleBuilder builder = new AppleSingleBuilder(FileType.APPLEDOUBLE, AppleSingleVersion.VERSION_2_0, FileSystem.MACOS_X); long extractedBytes = 0; for(FSFork f : entry.getAllForks()) { FSForkType forkType = f.getType(); if(forkType == FSForkType.MACOS_RESOURCE) { resourceForkData = IOUtil.readFully(f.getReadableRandomAccessStream()); extractedBytes += resourceForkData.length; } else if(forkType == FSForkType.MACOS_FINDERINFO) { finderInfoData = IOUtil.readFully(f.getReadableRandomAccessStream()); extractedBytes += finderInfoData.length; } else if(f.hasXattrName()) { final byte[] attributeData = IOUtil.readFully(f.getReadableRandomAccessStream()); attributeList.add(new Pair<String, byte[]>(f.getXattrName(), attributeData)); extractedBytes += attributeData.length; } } if(finderInfoData != null || attributeList.size() > 0) { builder.addFinderInfo(finderInfoData, attributeList); } if(resourceForkData != null) { builder.addResourceFork(resourceForkData); } else { builder.addEmptyResourceFork(); } if(extractedBytes > 0) { os.write(builder.getResult()); pm.addDataProgress(extractedBytes); } return extractedBytes; } finally { if(in != null) { try { in.close(); } catch(Exception e) {} } if(baos != null) { try { baos.close(); } catch(Exception e) {} } } } private void populateFilesystemGUI(FSFolder rootFolder) { FileSystemBrowser.Record<FSEntry> rootRecord = new FileSystemBrowser.Record<FSEntry>( FileSystemBrowser.RecordType.FOLDER, rootFolder.getName(), 0, rootFolder.getAttributes().getModifyDate(), false, rootFolder); fsb.setRoot(rootRecord); } private void actionDoubleClickFile(final String[] parentPath, final FSFile rec) { final JDialog fopFrame = new JDialog(this, rec.getName(), true); fopFrame.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); ActionListener alOpen = null; if(System.getProperty("os.name").toLowerCase().startsWith("windows")) { //System.err.println("Windows detected"); final String finalCommand = "cmd.exe /c start \"HFSExplorer invoker\" \"" + rec.getName() + "\""; alOpen = new ActionListener() { /* @Override */ public void actionPerformed(ActionEvent ae) { File tempDir = new File(System.getProperty("java.io.tmpdir")); LinkedList<String> errorMessages = new LinkedList<String>(); extract(parentPath, rec, tempDir, new SimpleGUIProgressMonitor(fopFrame), errorMessages, true); if(errorMessages.size() == 0) { tempFiles.add(new File(tempDir, rec.getName())); try { // System.err.print("Trying to execute:"); // for(String s : finalCommand) // System.err.print(" \"" + s + "\""); // System.err.println(" in directory \"" + tempDir + "\""); Process p = Runtime.getRuntime().exec(finalCommand, null, tempDir); fopFrame.dispose(); } catch(Exception e) { e.printStackTrace(); String stackTrace = e.toString() + "\n"; for(StackTraceElement ste : e.getStackTrace()) { stackTrace += " " + ste.toString() + "\n"; } JOptionPane.showMessageDialog(FileSystemBrowserWindow.this, "Open failed. Exception caught:\n" + stackTrace, "Error", JOptionPane.ERROR_MESSAGE); } } else { JOptionPane.showMessageDialog(FileSystemBrowserWindow.this, "Error while extracting file to temp dir.", "Error", JOptionPane.ERROR_MESSAGE); } } }; } else if(Java6Util.isJava6OrHigher() && Java6Util.canOpen()) { //System.err.println("Java 1.6 detected."); alOpen = new ActionListener() { /* @Override */ public void actionPerformed(ActionEvent ae) { File tempDir = new File(System.getProperty("java.io.tmpdir")); LinkedList<String> errorMessages = new LinkedList<String>(); extract(parentPath, rec, tempDir, new SimpleGUIProgressMonitor(fopFrame), errorMessages, true); if(errorMessages.size() == 0) { File extractedFile = new File(tempDir, rec.getName()); tempFiles.add(new File(tempDir, rec.getName())); try { Java6Util.openFile(extractedFile); fopFrame.dispose(); } catch(IOException e) { JOptionPane.showMessageDialog(FileSystemBrowserWindow.this, "Could not find a handler to open the file with.\n" + "The file remains in\n" + " \"" + tempDir + "\"\n" + "until you exit the program.", "Error", JOptionPane.ERROR_MESSAGE); } catch(Exception e) { e.printStackTrace(); String stackTrace = e.toString() + "\n"; for(StackTraceElement ste : e.getStackTrace()) { stackTrace += " " + ste.toString() + "\n"; } JOptionPane.showMessageDialog(FileSystemBrowserWindow.this, "Open failed. Exception caught:\n" + stackTrace, "Error", JOptionPane.ERROR_MESSAGE); } } } }; } else if(System.getProperty("os.name").toLowerCase().startsWith("mac os x")) { //System.err.println("OS X detected"); final String[] finalCommand = new String[]{"open", rec.getName()}; alOpen = new ActionListener() { /* @Override */ public void actionPerformed(ActionEvent ae) { File tempDir = new File(System.getProperty("java.io.tmpdir")); LinkedList<String> errorMessages = new LinkedList<String>(); extract(parentPath, rec, tempDir, new SimpleGUIProgressMonitor(fopFrame), errorMessages, true); if(errorMessages.size() == 0) { tempFiles.add(new File(tempDir, rec.getName())); try { // System.err.print("Trying to execute:"); // for(String s : finalCommand) // System.err.print(" \"" + s + "\""); // System.err.println(" in directory \"" + tempDir + "\""); Process p = Runtime.getRuntime().exec(finalCommand, null, tempDir); fopFrame.dispose(); } catch(Exception e) { e.printStackTrace(); String stackTrace = e.toString() + "\n"; for(StackTraceElement ste : e.getStackTrace()) { stackTrace += " " + ste.toString() + "\n"; } JOptionPane.showMessageDialog(FileSystemBrowserWindow.this, "Open failed. Exception caught:\n" + stackTrace, "Error", JOptionPane.ERROR_MESSAGE); } } else { JOptionPane.showMessageDialog(FileSystemBrowserWindow.this, "Error while extracting file to temp dir.", "Error", JOptionPane.ERROR_MESSAGE); } } }; } ActionListener alSave = new ActionListener() { /* @Override */ public void actionPerformed(ActionEvent ae) { actionExtractToDir(parentPath, rec); fopFrame.dispose(); } }; FileOperationsPanel fop = new FileOperationsPanel(fopFrame, rec.getName(), rec.getMainFork().getLength(), alOpen, alSave); fopFrame.add(fop, BorderLayout.CENTER); fopFrame.pack(); fopFrame.setLocationRelativeTo(null); fopFrame.setVisible(true); } private void actionExtractToDiskImage() { JFileChooser jfc = new JFileChooser(); jfc.setFileSelectionMode(JFileChooser.FILES_ONLY); jfc.setMultiSelectionEnabled(false); SimplerFileFilter ffDmg = new SimplerFileFilter(".dmg", "Mac OS X read/write disk image (.dmg)"); jfc.setFileFilter(ffDmg); if(jfc.showSaveDialog(FileSystemBrowserWindow.this) == JFileChooser.APPROVE_OPTION) { File selectedFile = jfc.getSelectedFile(); final File saveFile; FileFilter selectedFileFilter = jfc.getFileFilter(); if(selectedFileFilter instanceof SimplerFileFilter) { SimplerFileFilter sff = (SimplerFileFilter)selectedFileFilter; if(!selectedFile.getName().endsWith(sff.getExtension())) saveFile = new File(selectedFile.getParentFile(), selectedFile.getName() + sff.getExtension()); else saveFile = selectedFile; } else { saveFile = selectedFile; } if(saveFile.exists()) { int res = JOptionPane.showConfirmDialog(this, "The file:\n " + saveFile.getPath() + "\nAlready exists. Do you want to overwrite?", "Confirm overwrite", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if(res != JOptionPane.YES_OPTION) return; } final javax.swing.ProgressMonitor pm = new javax.swing.ProgressMonitor(FileSystemBrowserWindow.this, "Extracting file system data to disk image...", "Starting extraction...", 0, Integer.MAX_VALUE); pm.setMillisToDecideToPopup(0); pm.setMillisToPopup(0); pm.setProgress(0); Runnable r = new Runnable() { /* @Override */ public void run() { ReadableRandomAccessStream fsStream = fsDataLocator.createReadOnlyFile(); FileOutputStream fileOut = null; try { fileOut = new FileOutputStream(saveFile); DecimalFormat df = new DecimalFormat("0.00"); CommonHFSVolumeHeader vh = fsHandler.getFSView().getVolumeHeader(); final long bytesToExtract = vh.getFileSystemEnd(); String bytesToExtractString = SpeedUnitUtils.bytesToBinaryUnit(bytesToExtract, df); long lastUpdateTimestamp = 0; long bytesExtracted = 0; byte[] buffer = new byte[64 * 1024]; // We read in 64 KiB blocks while(bytesExtracted < bytesToExtract && !pm.isCanceled()) { long bytesLeftToRead = bytesToExtract - bytesExtracted; int curBytesToRead = (int)(bytesLeftToRead < buffer.length ? bytesLeftToRead : buffer.length); int bytesRead = fsStream.read(buffer, 0, curBytesToRead); if(bytesRead > 0) { fileOut.write(buffer, 0, bytesRead); bytesExtracted += bytesRead; // Update user progress (not too often) long currentTimestamp = System.currentTimeMillis(); long millisSinceLastUpdate = currentTimestamp - lastUpdateTimestamp; if(millisSinceLastUpdate >= 40) { pm.setProgress((int) ((bytesExtracted / (double) bytesToExtract) * Integer.MAX_VALUE)); pm.setNote("Extracted " + SpeedUnitUtils.bytesToBinaryUnit(bytesExtracted, df) + " / " + bytesToExtractString + " ..."); lastUpdateTimestamp = currentTimestamp; } } else { throw new RuntimeException("Unexpectedly reached end of file!" + " fp=" + fsStream.getFilePointer() + " length=" + fsStream.length() + " bytesExtracted=" + bytesExtracted + " bytesToExtract=" + bytesToExtract); } } } catch(Exception e) { e.printStackTrace(); GUIUtil.displayExceptionDialog(e, 15, FileSystemBrowserWindow.this, "Exception while extracting data!"); } finally { pm.close(); try { fsStream.close(); if(fileOut != null) { fileOut.close(); } } catch(Exception e) { e.printStackTrace(); GUIUtil.displayExceptionDialog(e, FileSystemBrowserWindow.this); } } } }; new Thread(r).start(); } } private void actionExtractToDir(final String[] parentPath, FSEntry entry) { actionExtractToDir(parentPath, Arrays.asList(entry)); } private void actionExtractToDir(final String[] parentPath, List<FSEntry> selection) { actionExtractToDir(parentPath, selection, true, false); } private void actionExtractToDir(final String[] parentPath, final List<FSEntry> selection, final boolean dataFork, final boolean additionalForks) { if(!dataFork && !additionalForks) { throw new IllegalArgumentException("Can't choose to extract nothing!"); } try { //final List<FSEntry> selection = fsb.getUserObjectSelection(); if(selection.size() > 0) { fileChooser.setMultiSelectionEnabled(false); fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); fileChooser.setSelectedFiles(new File[0]); if(fileChooser.showDialog(FileSystemBrowserWindow.this, "Extract here") == JFileChooser.APPROVE_OPTION) { final File outDir = fileChooser.getSelectedFile(); final ExtractProgressDialog progress = new ExtractProgressDialog(this); Runnable r = new Runnable() { /* @Override */ public void run() { // Caching is now turned on or off manually, either for the entire device/file, or not at all. //fsView.retainCatalogFile(); // Cache the catalog file to speed up operations //fsView.enableFileSystemCache(); try { LinkedList<String> dirStack = new LinkedList<String>(); if(parentPath != null) { //System.err.println("parentPath: " + Util.concatenateStrings(parentPath, "/")); for(String pathComponent : parentPath) dirStack.addLast(pathComponent); } int res = JOptionPane.showConfirmDialog(progress, "Do you want to follow symbolic links while extracting?\n" + "Following symbolic links means that the extracted tree will " + "more closely match the percieved file system tree, but it\n" + "increases the size of the extracted data, the time that it " + "takes to extract it, and puts a lot of identical files at\n" + "different locations in your target folder.", "Follow symbolic links?", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE); final boolean followSymlinks; if(res == JOptionPane.YES_OPTION) followSymlinks = true; else if(res == JOptionPane.NO_OPTION) followSymlinks = false; else return; /* System.err.println("TEST traversing with traverseTree"); traverseTree(parentPath, selection, new NullTreeVisitor(), followSymlinks); if(true) return; */ long dataSize = calculateForkSizeRecursive(parentPath, selection, progress, dataFork, additionalForks, followSymlinks); if(false) { if(progress.cancelSignaled()) System.err.println("Size calculation aborted. Calculated size: " + dataSize + " bytes"); else System.err.println("Size calculation completed. Total size: " + dataSize + " bytes"); return; } //JOptionPane.showMessageDialog(progress, "dataSize = " + dataSize); if(progress.cancelSignaled()) progress.confirmCancel(); else { progress.setDataSize(dataSize); LinkedList<String> errorMessages = new LinkedList<String>(); extract(parentPath, selection, outDir, progress, errorMessages, followSymlinks, dataFork, additionalForks); if(progress.cancelSignaled()) errorMessages.addLast("User aborted extraction."); if(!progress.cancelSignaled()) { if(errorMessages.size() == 0) { JOptionPane.showMessageDialog(progress, "Extraction finished.", "Information", JOptionPane.INFORMATION_MESSAGE); } else { ErrorSummaryPanel.createErrorSummaryDialog(progress, errorMessages).setVisible(true); /* JOptionPane.showMessageDialog(progress, errorMessages.size() + " errors were encountered during the extraction.", "Information", JOptionPane.WARNING_MESSAGE); */ } } else { JOptionPane.showMessageDialog(progress, "Extraction was aborted.\n" + "Please remove the extracted files manually.", "Aborted extraction", JOptionPane.WARNING_MESSAGE); progress.confirmCancel(); } } } catch(Throwable t) { t.printStackTrace(); GUIUtil.displayExceptionDialog(t, progress); } finally { progress.dispose(); //fsView.disableFileSystemCache(); //fsView.releaseCatalogFile(); // Always release memory } } }; new Thread(r).start(); progress.setVisible(true); } } else if(selection.size() == 0) { JOptionPane.showMessageDialog(this, "No file or folder selected.", "Information", JOptionPane.INFORMATION_MESSAGE); } else { throw new RuntimeException("wtf?"); // ;-) } } catch(RuntimeException re) { re.printStackTrace(); GUIUtil.displayExceptionDialog(re, this); } // There is trouble with this approach in OS X... we don't get a proper DIRECTORIES_ONLY dialog. One idea is to use java.awt.FileDialog and see if it works better. (probably not) //java.awt.FileDialog fd = new java.awt.FileDialog(FileSystemBrowserWindow.this, "Extract here", SAVE); //File oldDir = fileChooser.getCurrentDirectory(); //JFileChooser fileChooser2 = new JFileChooser(); //fileChooser.setCurrentDirectory(oldDir); //fileChooser.setMultiSelectionEnabled(false); //fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); //System.err.println("curdir: " + fileChooser.getCurrentDirectory()); //fileChooser.setSelectedFiles(new File[0]); //fileChooser.setCurrentDirectory(fileChooser.getCurrentDirectory()); } /** * Calculates the combined size of the forks of types <code>forkTypes</code> for the selection, * including for all files in subdirectories, recursively. If <code>forkTypes</code> is empty, * all forks are included in the calculation. * * @param parentPath the parent path of the entries in <code>selection</code>. * @param selection the source entries for the calculation. * @param progress the progress monitor that recieves updates about our current state and * decides whether or not to abort. * @param calculateDataForkSize * whether the size of the data forks should be included. * @param calculateAdditionalForksSize * whether the size of the non-data data forks should be included. * @param followSymlinks whether or not symbolic links should be followed in the tree traversal. * @return the combined size of the forks of types <code>forkTypes</code> for the selection, * including for all files in subdirectories, recursively. */ private long calculateForkSizeRecursive(String[] parentPath, List<FSEntry> selection, ExtractProgressMonitor progress, boolean calculateDataForkSize, boolean calculateAdditionalForksSize, boolean followSymlinks) { CalculateTreeSizeVisitor sizeVisitor = new CalculateTreeSizeVisitor(progress, calculateDataForkSize, calculateAdditionalForksSize); traverseTree(parentPath, selection, sizeVisitor, followSymlinks); return sizeVisitor.getSize(); } private void traverseTree(String[] parentPath, List<FSEntry> entries, TreeVisitor visitor, boolean followSymbolicLinks) { LinkedList<String[]> absPathsStack = new LinkedList<String[]>(); LinkedList<String> pathStack = new LinkedList<String>(); if(parentPath != null) { absPathsStack.addLast(parentPath); for(String pathComponent : parentPath) pathStack.addLast(pathComponent); } FSEntry[] children = entries.toArray(new FSEntry[entries.size()]); traverseTreeRecursive(children, pathStack, absPathsStack, visitor, followSymbolicLinks); } private void traverseTreeRecursive(final FSEntry[] selection, final LinkedList<String> pathStack, final LinkedList<String[]> absPathsStack, final TreeVisitor visitor, final boolean followSymbolicLinks) { if(visitor.cancelTraversal()) { return; } //System.err.println("calculateForkSizeRecursive") String[] pathStackArray = pathStack.toArray(new String[pathStack.size()]); String pathStackString = Util.concatenateStrings(pathStack, "/"); //System.err.print("Directory: \""); //System.err.print(pathStackString); //System.err.println("\"..."); for(FSEntry curEntry : selection) { if(visitor.cancelTraversal()) { break; } String curEntryString = (pathStackString.length() > 0 ? pathStackString + "/" : "") + curEntry.getName(); //System.err.println("Processing \"" + curEntryString + "\"..."); String[] linkTargetPath = null; if(followSymbolicLinks && curEntry instanceof FSLink) { FSLink curLink = (FSLink)curEntry; //System.err.print(" Getting link target for \"" + curEntryString + "\"..."); String[] targetPath = fsHandler.getTargetPath(curLink, pathStackArray); if(targetPath != null) { if(Util.contains(absPathsStack, targetPath)) { String msg = "Circular symlink detected: \"" + curEntryString + "\" -> \"" + curLink.getLinkTargetString() + "\""; System.err.println(); System.err.println("traverseTreeRecursive: " + msg); System.err.println(); visitor.traversalError(msg); continue; } FSEntry linkTarget = fsHandler.getEntry(targetPath); if(linkTarget != null) { //System.err.println(" Happily resolved link \"" + curLink.getLinkTargetString() + "\" to an FSEntry by the name \"" + linkTarget.getName() + "\""); curEntry = linkTarget; linkTargetPath = targetPath; } else { String msg = "Could not get link target entry \"" + curLink.getLinkTargetString() + "\""; System.err.println("WARNING: " + msg); visitor.traversalError(msg); } } else { String msg = "Could not resolve link \"" + curEntryString + "\" -> \"" + curLink.getLinkTargetString() + "\""; System.err.println("WARNING: " + msg); visitor.traversalError(msg); } } final String[] absolutePath; if(linkTargetPath != null) absolutePath = linkTargetPath; else { if(absPathsStack.size() > 0) absolutePath = Util.concatenate(absPathsStack.getLast(), curEntry.getName()); else absolutePath = new String[0]; } if(curEntry instanceof FSFile) { visitor.file((FSFile) curEntry); } else if(curEntry instanceof FSFolder) { FSFolder curFolder = (FSFolder) curEntry; if(absPathsStack.size() > 0) pathStack.addLast(curFolder.getName()); absPathsStack.addLast(absolutePath); try { if(visitor.startDirectory(pathStackArray, curFolder)) { traverseTreeRecursive(curFolder.listEntries(), pathStack, absPathsStack, visitor, followSymbolicLinks); visitor.endDirectory(pathStackArray, curFolder); } } finally { absPathsStack.removeLast(); if(absPathsStack.size() > 0) pathStack.removeLast(); } } else if(curEntry instanceof FSLink) { FSLink curLink = (FSLink) curEntry; if(followSymbolicLinks) { String msg = "Unresolved link \"" + curEntryString + "\" -> \"" + curLink.getLinkTargetString() + "\""; System.err.println(msg); //visitor.traversalError(msg); } visitor.link((FSLink) curEntry); } else { throw new RuntimeException("Unexpected FSEntry subclass: " + curEntry.getClass()); } } } private void actionShowAboutDialog() { String message = ""; message += "HFSExplorer " + HFSExplorer.VERSION + " Build #" + BuildNumber.BUILD_NUMBER + "\n"; message += HFSExplorer.COPYRIGHT + "\n"; for(String notice : HFSExplorer.NOTICES) { message += notice + "\n"; } message += "\nOperating system: " + System.getProperty("os.name") + " " + System.getProperty("os.version"); message += "\nArchitecture: " + System.getProperty("os.arch"); message += "\nJava Runtime Environment: " + System.getProperty("java.version"); message += "\nVirtual machine: " + System.getProperty("java.vm.vendor") + " " + System.getProperty("java.vm.name") + " " + System.getProperty("java.vm.version"); JOptionPane.showMessageDialog(this, message, "About", JOptionPane.INFORMATION_MESSAGE); } private void actionGetInfo(String[] parentPath, List<FSEntry> entries) { if(entries.size() != 1) { JOptionPane.showMessageDialog(this, "Get info for multiple selections not yet possible.\n" + "Please select one item at a time.", "Error", JOptionPane.ERROR_MESSAGE); return; } FSEntry entry = entries.get(0); if(entry instanceof FSFile || entry instanceof FSLink || entry instanceof FSFolder) { FileInfoWindow fiw = new FileInfoWindow(entry, parentPath); fiw.setVisible(true); } else { JOptionPane.showMessageDialog(this, "[actionGetInfo()] Record data has unexpected type (" + entry.getClass() + ").\nReport bug to developer.", "Error", JOptionPane.ERROR_MESSAGE); } } /** <code>progressDialog</code> may NOT be null. */ protected void extract(String[] parentPath, FSEntry rec, File outDir, ExtractProgressMonitor progressDialog, LinkedList<String> errorMessages, boolean followSymbolicLinks) { extract(parentPath, Arrays.asList(rec), outDir, progressDialog, errorMessages, followSymbolicLinks, true, false); } /** <code>progressDialog</code> may NOT be null. */ /* protected void extract(String[] parentPath, FSEntry rec, File outDir, ExtractProgressMonitor progressDialog, LinkedList<String> errorMessages, boolean dataFork, boolean resourceFork) { extract(parentPath, Arrays.asList(rec), outDir, progressDialog, errorMessages, dataFork, resourceFork); } */ /** <code>progressDialog</code> may NOT be null. */ /* protected void extract(String[] parentPath, FSEntry[] recs, File outDir, ExtractProgressMonitor progressDialog, LinkedList<String> errorMessages) { extract(parentPath, Arrays.asList(recs), outDir, progressDialog, errorMessages, true, false); } */ /** <code>progressDialog</code> may NOT be null. */ /* protected void extract(String[] parentPath, FSEntry[] recs, File outDir, ExtractProgressMonitor progressDialog, LinkedList<String> errorMessages, boolean dataFork, boolean resourceFork) { extract(parentPath, Arrays.asList(recs), outDir, progressDialog, errorMessages, dataFork, resourceFork); } */ /** <code>progressDialog</code> may NOT be null. */ /* protected void extract(String[] parentPath, List<FSEntry> recs, File outDir, ExtractProgressMonitor progressDialog, LinkedList<String> errorMessages) { extract(parentPath, recs, outDir, progressDialog, errorMessages, true, false); } */ /** * Utility method that checks for the existence of a file. This method tries * to overcome some limitations of Java. For instance, the File.exists() * method trims spaces in filenames automatically in Windows, which is * undesirable. */ private static boolean deepExists(File f) { if(!f.exists()) return false; // We trust that Java never returns false negatives. File parentDir = f.getParentFile(); if(parentDir == null) { /* There is no parent, so this must be one of the file system * roots (which definitely exists). */ return true; } for(File child : parentDir.listFiles()) { if(child.getName().equals(f.getName())) return true; } return false; } protected void extract(String[] parentPath, List<FSEntry> recs, File outDir, ExtractProgressMonitor progressDialog, LinkedList<String> errorMessages, boolean followSymbolicLinks, boolean extractMainFork, boolean extractAdditionalForks) { if(!deepExists(outDir)) { String[] options = new String[]{"Create directory", "Cancel"}; int reply = JOptionPane.showOptionDialog(this, "Target " + "directory:\n \"" + outDir.getAbsolutePath() + "\"\n" + "does not exist. Do you want to create this directory?", "Warning", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null, options, options[0]); if(reply != 0) { //++errorCount; errorMessages.addLast("Skipping all files in " + outDir.getAbsolutePath() + " as user chose not to " + "create directory."); progressDialog.signalCancel(); return; } else { if(!outDir.mkdirs() || !deepExists(outDir)) { JOptionPane.showMessageDialog(this, "Could not create " + "directory:\n \"" + outDir.getAbsolutePath() + "\"\n", "Error", JOptionPane.ERROR_MESSAGE); errorMessages.addLast("Could not create directory \"" + outDir.getAbsolutePath() + "\"."); progressDialog.signalCancel(); return; } } } else if(!outDir.isDirectory()) { JOptionPane.showMessageDialog(this, "Target directory is a file:" + "\n \"" + outDir.getAbsolutePath() + "\"", "Error", JOptionPane.ERROR_MESSAGE); errorMessages.addLast("Could not create directory \"" + outDir.getAbsolutePath() + "\", since a file was in the " + "way."); progressDialog.signalCancel(); return; } ExtractVisitor ev = new ExtractVisitor(progressDialog, errorMessages, outDir, extractMainFork, extractAdditionalForks); traverseTree(parentPath, recs, ev, followSymbolicLinks); } /* private void extractRecursive(FSEntry rec, LinkedList<String> pathStack, LinkedList<String[]> absPathsStack, File outDir, ExtractProgressMonitor progressDialog, LinkedList<String> errorMessages, ObjectContainer<Boolean> overwriteAll, boolean dataFork, boolean resourceFork) { if(!dataFork && !resourceFork) { throw new IllegalArgumentException("Neither data fork nor resource fork were selected for extraction. Won't do nothing..."); } if(progressDialog.cancelSignaled()) { //progressDialog.confirmCancel(); // Done by caller. return; } //int errorCount = 0; String[] absolutePath = null; if(rec instanceof FSLink) { FSLink curLink = (FSLink) rec; String[] pathStackArray = pathStack.toArray(new String[pathStack.size()]); String[] targetPath = fsHandler.getTargetPath(curLink, pathStackArray); if(targetPath != null) { if(Util.contains(absPathsStack, targetPath)) { System.err.println(); System.err.println("extractRecursive: CIRCULAR SYMLINK DETECTED!"); System.err.println(); errorMessages.addLast("Detected circular soft link \"" + curLink.getName() + "\" in directory \"" + Util.concatenateStrings(pathStackArray, "/") + "\"... skipping this entry."); return; } FSEntry linkTarget = fsHandler.getEntry(targetPath); if(linkTarget != null) { rec = linkTarget; absolutePath = targetPath; } else { errorMessages.addLast("Could not get entry for link target \"" + Util.concatenateStrings(targetPath, "/") + "\"... skipping this entry."); return; } } else { errorMessages.addLast("Could not resolve soft link \"" + curLink.getLinkTargetString() + "\" from directory \"" + Util.concatenateStrings(pathStackArray, "/") + "\"... skipping this entry."); return; } //FSEntry linkTarget = curLink.getLinkTarget(pathStackArray); } if(rec instanceof FSFile) { if(dataFork) { extractFile((FSFile) rec, outDir, progressDialog, errorMessages, overwriteAll, FSForkType.DATA); } if(resourceFork) { extractFile((FSFile) rec, outDir, progressDialog, errorMessages, overwriteAll, FSForkType.MACOS_RESOURCE); } } else if(rec instanceof FSFolder) { String curDirName = rec.getName(); progressDialog.updateCurrentDir(curDirName); FSEntry[] contents = ((FSFolder) rec).listEntries(); //System.out.println("folder: \"" + curDirName + "\" valence: " + contents.length + " range: " + fractionLowLimit + "-" + fractionHighLimit); // We now have the contents of the requested directory File thisDir = new File(outDir, curDirName); if(!overwriteAll.o && thisDir.exists()) { String[] options = new String[]{"Continue", "Cancel"}; int reply = JOptionPane.showOptionDialog(this, "Warning! Directory:\n \"" + thisDir.getAbsolutePath() + "\"\n" + "already exists. Do you want to continue extracting to this directory?", "Warning", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null, options, options[0]); if(reply != 0) { //++errorCount; errorMessages.addLast("Skipping all files in \"" + thisDir.getAbsolutePath() + "\" due to user interaction."); progressDialog.signalCancel(); return; } } if(thisDir.mkdir() || thisDir.exists()) { pathStack.addLast(rec.getName()); if(absolutePath != null) absPathsStack.addLast(absolutePath); try { System.err.println("extractRecursive: pathStack=" + Util.concatenateStrings(pathStack, "/")); System.err.println("extractRecursive: absPathsStack:"); for(String[] cur : absPathsStack) { System.err.println(" " + Util.concatenateStrings(cur, "/")); } for(FSEntry outRec : contents) { extractRecursive(outRec, pathStack, absPathsStack, thisDir, progressDialog, errorMessages, overwriteAll, dataFork, resourceFork); } } finally { if(absolutePath != null) absPathsStack.removeLast(); pathStack.removeLast(); } } else { int reply = JOptionPane.showConfirmDialog(this, "Could not create directory:\n " + thisDir.getAbsolutePath() + "\nDo you want to " + "continue? (All files under this directory will be " + "skipped)", "Error", JOptionPane.YES_NO_OPTION, JOptionPane.ERROR_MESSAGE); if(reply == JOptionPane.NO_OPTION) { progressDialog.signalCancel(); } else errorMessages.addLast("Could not create directory \"" + thisDir.getAbsolutePath() + "\". All files under this directory will be skipped."); return; } } // else // System.out.println("thread with range: " + fractionLowLimit + "-" + fractionHighLimit); } */ private void setExtractedEntryAttributes(File outNode, FSEntry entry, final LinkedList<String> errorMessages) { if(entry.getAttributes().hasPOSIXFileAttributes() && Java7Util.isJava7OrHigher()) { POSIXFileAttributes attrs = entry.getAttributes().getPOSIXFileAttributes(); try { Java7Util.setPosixPermissions(outNode.getPath(), attrs.canUserRead(), attrs.canUserWrite(), attrs.canUserExecute(), attrs.canGroupRead(), attrs.canGroupWrite(), attrs.canGroupExecute(), attrs.canOthersRead(), attrs.canOthersWrite(), attrs.canOthersExecute()); } catch(Exception e) { System.err.println("Got " + e.getClass().getName() + " when " + "attempting to set Java 7 POSIX permissions for " + "\"" + outNode.getPath() + "\":"); e.printStackTrace(); errorMessages.addLast("Got " + e.getClass().getName() + " " + "when attempting to set Java 7 POSIX permissions for " + "\"" + outNode.getPath() + "\" (see stack trace for " + "more info)."); e.printStackTrace(); } try { Java7Util.setPosixOwners(outNode.getPath(), (int) attrs.getUserID(), (int) attrs.getGroupID()); } catch(Exception e) { final String message = "Got " + e.getClass().getName() + " when attempting " + "to set Java 7 POSIX ownership for " + "\"" + outNode.getPath() + "\""; System.err.println(message + ":"); e.printStackTrace(); errorMessages.addLast(message + " (see stack trace for more " + "info)."); } } Long createTime = null; Long lastAccessTime = null; Long lastModifiedTime = null; if(entry.getAttributes().hasCreateDate()) { createTime = entry.getAttributes().getCreateDate().getTime(); } if(entry.getAttributes().hasAccessDate()) { lastAccessTime = entry.getAttributes().getAccessDate().getTime(); } if(entry.getAttributes().hasModifyDate()) { lastModifiedTime = entry.getAttributes().getModifyDate().getTime(); } boolean fileTimesSet = false; if(Java7Util.isJava7OrHigher()) { try { Java7Util.setFileTimes(outNode.getPath(), createTime != null ? new Date(createTime) : null, lastAccessTime != null ? new Date(lastAccessTime) : null, lastModifiedTime != null ? new Date(lastModifiedTime) : null); fileTimesSet = true; } catch(Exception e) { System.err.println("Got " + e.getClass().getName() + " when " + "attempting to set Java 7 file times for " + "\"" + outNode.getPath() + "\":"); e.printStackTrace(); errorMessages.addLast("Got " + e.getClass().getName() + " " + "when attempting to set Java 7 file times for " + "\"" + outNode.getPath() + "\" (see stack trace for " + "more info)."); } } if(!fileTimesSet && lastModifiedTime != null) { boolean setLastModifiedResult; if(lastModifiedTime < 0) { errorMessages.addLast("Cannot to set last modified time for " + "\"" + outNode.getPath() + "\" to pre-1970 date. " + "Adjusting last modified time from " + new Date(lastModifiedTime) + " to " + new Date(0) + "."); lastModifiedTime = (long) 0; } setLastModifiedResult = outNode.setLastModified(lastModifiedTime); if(!setLastModifiedResult) { errorMessages.addLast("Failed to set last modified time for " + "\"" + outNode.getPath() + "\" to " + new Date(lastModifiedTime) + " (raw: " + lastModifiedTime + ")."); } } } private void extractEntry(final FSEntry rec, final File outDir, final ExtractProgressMonitor progressDialog, final LinkedList<String> errorMessages, final ExtractProperties extractProperties, final ObjectContainer<Boolean> skipDirectory, final boolean extractAdditionalForks) { //int errorCount = 0; final String originalFileName; if(!extractAdditionalForks) { originalFileName = rec.getName(); } else { originalFileName = "._" + rec.getName(); // Special syntax for resource forks in foreign file systems } CreateFileFailedAction defaultCreateFileFailedAction = extractProperties.getCreateFileFailedAction(); FileExistsAction defaultFileExistsAction = extractProperties.getFileExistsAction(); UnhandledExceptionAction defaultUnhandledExceptionAction = extractProperties.getUnhandledExceptionAction(); String fileName = originalFileName; while(fileName != null) { String curFileName = fileName; fileName = null; //System.out.println("file: \"" + filename + "\" range: " + fractionLowLimit + "-" + fractionHighLimit); long totalForkSize; if(extractAdditionalForks) { totalForkSize = 0; for(FSFork f : rec.getAllForks()) { if(f.getType() == FSForkType.DATA) { continue; } totalForkSize += f.getLength(); } if(totalForkSize == 0) { /* Don't create empty AppleDouble files. */ return; } } else if(rec instanceof FSFile) { totalForkSize = ((FSFile) rec).getMainFork().getLength(); } else if(rec instanceof FSLink) { totalForkSize = 0; } else { /* Nothing to extract. */ return; } progressDialog.updateCurrentFile(curFileName, totalForkSize); final File outFile = new File(outDir, curFileName); //progressDialog.updateTotalProgress(fractionLowLimit); /* Note: We may want to use deepExists here like in the directory * case, but it's less urgent here so I'll pass for now. */ if(defaultFileExistsAction != FileExistsAction.OVERWRITE && outFile.exists()) { FileExistsAction a; if(defaultFileExistsAction == FileExistsAction.PROMPT_USER) a = progressDialog.fileExists(outFile); else { a = defaultFileExistsAction; defaultFileExistsAction = FileExistsAction.PROMPT_USER; } if(a == FileExistsAction.OVERWRITE) { if(!outFile.delete()) { continue; } } else if(a == FileExistsAction.OVERWRITE_ALL) { if(!outFile.delete()) { continue; } extractProperties.setFileExistsAction(FileExistsAction.OVERWRITE); defaultFileExistsAction = FileExistsAction.OVERWRITE; } else if(a == FileExistsAction.SKIP_FILE) { errorMessages.addLast("Skipped extracting file \"" + outFile.getAbsolutePath() + "\" due to user interaction."); break; } else if(a == FileExistsAction.SKIP_DIRECTORY) { errorMessages.addLast("Skipping entire directory \"" + outDir.getAbsolutePath() + "\" due to user interaction."); skipDirectory.o = true; break; } else if(a == FileExistsAction.RENAME) { fileName = progressDialog.displayRenamePrompt(curFileName, outDir); if(fileName == null) fileName = curFileName; continue; } else if(a == FileExistsAction.AUTO_RENAME) { fileName = FileNameTools.autoRenameIllegalFilename(curFileName, outDir, false); if(fileName == null) fileName = curFileName; continue; } else if(a == FileExistsAction.CANCEL) { progressDialog.signalCancel(); break; } else { throw new RuntimeException("Internal error! Did not expect a: " + a); } } FileOutputStream fos = null; boolean extracted = false; try { // try { // PrintStream p = System.out; // File f = outFile; // p.println("Printing some information about the output file: "); // p.println("f.getParent(): \"" + f.getParent() + "\""); // p.println("f.getName(): \"" + f.getName() + "\""); // p.println("f.getAbsolutePath(): \"" + f.getAbsolutePath() + "\""); // p.println("f.exists(): \"" + f.exists() + "\""); // p.println("f.getCanonicalPath(): \"" + f.getCanonicalPath() + "\""); // //p.println("f.getParent(): \"" + f.getParent() + "\""); // } catch(Exception e) { e.printStackTrace(); } // Test that outFile is valid. try { outFile.getCanonicalPath(); } catch(Exception e) { throw new FileNotFoundException(); } if(!outFile.getParentFile().equals(outDir) || !outFile.getName().equals(curFileName)) { throw new FileNotFoundException(); } if(extractAdditionalForks) { fos = new FileOutputStream(outFile); extractAdditionalForksToAppleDoubleStream(rec, fos, progressDialog); } else if(rec instanceof FSFile) { fos = new FileOutputStream(outFile); extractForkToStream(((FSFile) rec).getMainFork(), fos, progressDialog); } else if(rec instanceof FSLink) { if(Java7Util.isJava7OrHigher()) { Java7Util.createSymbolicLink(outFile.getPath(), ((FSLink) rec).getLinkTargetString()); } else { /* Create the link in OS-specific way? Need native code * for that... unless we create a new 'ln -s' process, * but it will be slow... UNLESS we thread it out, and * don't wait for it to finish. OK, flooding the OS with * ln processes isn't good either... */ } } extracted = true; if(fos != null) { fos.close(); fos = null; } if(curFileName != (Object) originalFileName && !curFileName.equals(originalFileName)) errorMessages.addLast("File \"" + originalFileName + "\" was renamed to \"" + curFileName + "\" in parent folder \"" + outDir.getAbsolutePath() + "\"."); } catch(FileNotFoundException fnfe) { // <Debug messages> System.out.println("Could not create file \"" + outFile + "\". The following exception was thrown:"); fnfe.printStackTrace(); char[] filenameChars = curFileName.toCharArray(); System.out.println("Filename in hex (" + filenameChars.length + " UTF-16BE units):"); System.out.print(" 0x"); for(char c : filenameChars) { System.out.print(" " + Util.toHexStringBE(c)); } System.out.println(); // </Debug messages> // <Prompt user for action, if needed> CreateFileFailedAction a; if(defaultCreateFileFailedAction == CreateFileFailedAction.PROMPT_USER) a = progressDialog.createFileFailed(curFileName, outDir); else { a = defaultCreateFileFailedAction; defaultCreateFileFailedAction = CreateFileFailedAction.PROMPT_USER; } if(a == CreateFileFailedAction.SKIP_FILE) { errorMessages.addLast("Skipped extracting file \"" + outFile.getAbsolutePath() + "\" due to user interaction."); break; } else if(a == CreateFileFailedAction.SKIP_DIRECTORY) { errorMessages.addLast("Skipping entire directory \"" + outDir.getAbsolutePath() + "\" due to user interaction."); skipDirectory.o = true; break; } else if(a == CreateFileFailedAction.RENAME) { fileName = progressDialog.displayRenamePrompt(curFileName, outDir); if(fileName == null) fileName = curFileName; continue; } else if(a == CreateFileFailedAction.AUTO_RENAME) { fileName = FileNameTools.autoRenameIllegalFilename(curFileName, outDir, false); if(fileName == null) fileName = curFileName; continue; } else if(a == CreateFileFailedAction.CANCEL) { progressDialog.signalCancel(); break; } else { throw new RuntimeException("Internal error! Did not expect a: " + a); } // </Prompt user for action, if needed> } catch(IOException ioe) { final String message = "Encountered an I/O exception while " + "trying to write to file " + "\"" + outFile.getPath() + "\""; System.err.println(message + ":"); ioe.printStackTrace(); errorMessages.addLast(message + ". See debug console for " + "more info."); String exceptionMessage = ioe.getMessage(); int reply = JOptionPane.showConfirmDialog(this, "Encountered " + "an I/O exception while attempting to write to file " + "\"" + curFileName + "\" in folder:\n " + outDir.getAbsolutePath() + (exceptionMessage != null ? "\nSystem message: " + "\"" + exceptionMessage + "\"" : "") + "\nDo you want to continue?", "I/O Error", JOptionPane.YES_NO_OPTION, JOptionPane.ERROR_MESSAGE); if(reply == JOptionPane.NO_OPTION) { progressDialog.signalCancel(); } } catch(Throwable e) { final String message = "An unhandled exception occurred when " + "extracting to file \"" + outFile.getPath() + "\""; System.err.println(message + ":"); e.printStackTrace(); errorMessages.addLast(message + ". See debug console for " + "more info."); UnhandledExceptionAction a; if(defaultUnhandledExceptionAction == UnhandledExceptionAction.PROMPT_USER) { a = progressDialog.unhandledException(curFileName, e); } else { a = defaultUnhandledExceptionAction; } if(a == UnhandledExceptionAction.ABORT) { progressDialog.signalCancel(); } else if(a == UnhandledExceptionAction.CONTINUE || a == UnhandledExceptionAction.ALWAYS_CONTINUE) { if(a == UnhandledExceptionAction.ALWAYS_CONTINUE) { extractProperties.setUnhandledExceptionAction( UnhandledExceptionAction.CONTINUE); defaultUnhandledExceptionAction = UnhandledExceptionAction.CONTINUE; } } else { throw new RuntimeException("Internal error! Did not " + "expect a " + a + " here."); } } finally { if(fos != null) { try { fos.close(); } catch(IOException ex) { ex.printStackTrace(); } fos = null; } if(extracted) { setExtractedEntryAttributes(outFile, rec, errorMessages); } } break; } //return errorCount; } /** * An interface for visitors that can be used in the traverseTree method. */ public interface TreeVisitor { /** * * @param parentPath * @param folder * @return whether tree traversal should enter this directory or not. If the visitor returns * false for a directory, it will not get an endDirectory event for that directory. */ public boolean startDirectory(String[] parentPath, FSFolder folder); public void endDirectory(String[] parentPath, FSFolder folder); public void file(FSFile fsf); public void link(FSLink fsl); /** * This method is called when the traversal engine encounters a non-critical error. * @param message */ public void traversalError(String message); /** * Implement this to return true when the traversal process is to be aborted. * @return true if the visitor requests that the tree traversal be aborted. */ public boolean cancelTraversal(); } public class NullTreeVisitor implements TreeVisitor { /* @Override */ public boolean startDirectory(String[] parentPath, FSFolder folder) { return true; } /* @Override */ public void endDirectory(String[] parentPath, FSFolder folder) {} /* @Override */ public void file(FSFile fsf) {} /* @Override */ public void link(FSLink fsl) {} /* @Override */ public void traversalError(String message) {} /* @Override */ public boolean cancelTraversal() { return false; } } public class CalculateTreeSizeVisitor extends NullTreeVisitor { private final ExtractProgressMonitor pm; private final boolean includeMainFork; private final boolean includeAdditionalForks; private final StringBuilder sb = new StringBuilder(); private long size = 0; //private LinkedList<String> errorMessages = new LinkedList<String>(); public CalculateTreeSizeVisitor(ExtractProgressMonitor pm, boolean includeMainFork, boolean includeAdditionalForks) { this.pm = pm; this.includeMainFork = includeMainFork; this.includeAdditionalForks = includeAdditionalForks; if(this.pm == null) throw new IllegalArgumentException("pm == null"); if(!includeMainFork && !includeAdditionalForks) { throw new IllegalArgumentException("No fork types to extract."); } } public long getSize() { return size; } @Override public boolean startDirectory(String[] parentPath, FSFolder folder) { sb.setLength(0); for(String s : parentPath) sb.append(s).append("/"); sb.append(folder.getName()); pm.updateCalculateDir(sb.toString()); return true; } @Override public void file(FSFile file) { for(FSFork fork : file.getAllForks()) { final boolean isMainFork = fork.getType() == FSForkType.DATA; if((isMainFork && includeMainFork) || (!isMainFork && includeAdditionalForks)) { size += fork.getLength(); } } } @Override public boolean cancelTraversal() { return pm.cancelSignaled(); } } private class ExtractVisitor extends NullTreeVisitor { private final ExtractProgressMonitor pm; private final LinkedList<String> errorMessages; private final File outRootDir; //private final ObjectContainer<Boolean> overwriteAll = new ObjectContainer<Boolean>(false); private final ObjectContainer<Boolean> skipDirectory = new ObjectContainer<Boolean>(false); private final ExtractProperties extractProperties; private final boolean extractMainFork; private final boolean extractAdditionalForks; private final LinkedList<File> outDirStack = new LinkedList<File>(); public ExtractVisitor(ExtractProgressMonitor pm, LinkedList<String> errorMessages, File outDir, boolean extractMainFork, boolean extractAdditionalForks) { this.pm = pm; this.errorMessages = errorMessages; this.outRootDir = outDir; this.extractMainFork = extractMainFork; this.extractAdditionalForks = extractAdditionalForks; this.extractProperties = this.pm.getExtractProperties(); if(this.pm == null) throw new IllegalArgumentException("pm == null"); if(this.errorMessages == null) throw new IllegalArgumentException("errorMessages == null"); if(this.outRootDir == null) throw new IllegalArgumentException("outDir == null"); if(!extractMainFork && !extractAdditionalForks) { throw new IllegalArgumentException("No fork types to extract."); } outDirStack.addLast(outDir); } @Override public boolean startDirectory(String[] parentPath, FSFolder folder) { //System.err.println("startDirectory(" + Util.concatenateStrings(parentPath, "/") + ", " + folder.getName()); //if(skipDirectory.o) { // System.err.println(" skipping..."); // return false; //} //System.err.println("outDirStack.getLast()=" + outDirStack.getLast()); final File outDir = outDirStack.getLast(); final CreateDirectoryFailedAction originalCreateDirectoryFailedAction = extractProperties.getCreateDirectoryFailedAction(); final DirectoryExistsAction originalDirectoryExistsAction = extractProperties.getDirectoryExistsAction(); CreateDirectoryFailedAction defaultCreateDirectoryFailedAction = originalCreateDirectoryFailedAction; DirectoryExistsAction defaultDirectoryExistsAction = originalDirectoryExistsAction; final String originalDirName = folder.getName(); String dirName = originalDirName; while(dirName != null) { String curDirName = dirName; dirName = null; pm.updateCurrentDir(curDirName); File thisDir = new File(outDir, curDirName); if(defaultDirectoryExistsAction != DirectoryExistsAction.CONTINUE && deepExists(thisDir)) { DirectoryExistsAction a; if(defaultDirectoryExistsAction == DirectoryExistsAction.PROMPT_USER) a = pm.directoryExists(thisDir); else a = defaultDirectoryExistsAction; boolean resetLoop = false; switch(a) { case CONTINUE: break; case ALWAYS_CONTINUE: extractProperties.setDirectoryExistsAction( DirectoryExistsAction.CONTINUE); break; case RENAME: dirName = pm.displayRenamePrompt(curDirName, outDir); if(dirName == null) dirName = curDirName; resetLoop = true; break; case AUTO_RENAME: dirName = FileNameTools.autoRenameIllegalFilename(curDirName, outDir, true); if(dirName == null) dirName = curDirName; resetLoop = true; break; case SKIP_DIRECTORY: resetLoop = true; break; case CANCEL: resetLoop = true; pm.signalCancel(); break; default: throw new RuntimeException("Internal error! Did not expect a: " + a); } if(resetLoop) continue; } /* If the directory already exists, then fine. If not, we create * it and double check that it exists afterwards (to avoid * unexpected side effects, like in Windows). */ if(deepExists(thisDir) || (thisDir.mkdir() && deepExists(thisDir))) { if(curDirName != (Object)originalDirName && !curDirName.equals(originalDirName)) errorMessages.addLast("Directory \"" + originalDirName + "\" was renamed to \"" + curDirName + "\" in parent folder \"" + outDir.getAbsolutePath() + "\"."); /* Set attributes for directory right after creation, even * though the directory is likely to have its attributes * modified before we are done with it. This is done so that * any created files will have a sane ownership in case * setting ownership manually fails. */ setExtractedEntryAttributes(thisDir, folder, errorMessages); outDirStack.addLast(thisDir); return true; } else { CreateDirectoryFailedAction a; if(defaultCreateDirectoryFailedAction == CreateDirectoryFailedAction.PROMPT_USER) a = pm.createDirectoryFailed(curDirName, outDir); else { a = defaultCreateDirectoryFailedAction; // Only perform the default action once... or else we would have an endless loop defaultCreateDirectoryFailedAction = CreateDirectoryFailedAction.PROMPT_USER; } switch(a) { case SKIP_DIRECTORY: errorMessages.addLast("Could not create directory \"" + thisDir.getAbsolutePath() + "\". All files under this directory will be skipped."); break; case RENAME: dirName = pm.displayRenamePrompt(curDirName, outDir); if(dirName == null) dirName = curDirName; break; case AUTO_RENAME: dirName = FileNameTools.autoRenameIllegalFilename(curDirName, outDir, true); if(dirName == null) { dirName = curDirName; /* if(originalCreateDirectoryFailedAction == CreateDirectoryFailedAction.AUTO_RENAME) { // If we got here by the default action, we don't want to bother the user... errorMessages.addLast("Auto-rename failed for dir name \"" + curDirName + "\" in parent directory \"" + outDir.getAbsolutePath() + "\". All files under this directory will be skipped."); defaultCreateDirectoryFailedAction = CreateDirectoryFailedAction.SKIP_DIRECTORY; } */ } break; case CANCEL: pm.signalCancel(); break; default: throw new RuntimeException("Internal error! Did not expect a: " + a); } } } return false; } @Override public void endDirectory(String[] parentPath, FSFolder folder) { File outDir = outDirStack.removeLast(); /* Extract any extended attributes into an AppleDouble file in * outDir's parent. */ if(extractAdditionalForks) { extractEntry(folder, outDirStack.getLast(), pm, errorMessages, extractProperties, skipDirectory, true); } /* Finally reset the attributes of the directory to the attributes * of the FSFolder to make sure that times, mode, ownership, etc. * matches what we have in the file system (provided that there is * support for these attributes in the target file system). */ setExtractedEntryAttributes(outDir, folder, errorMessages); skipDirectory.o = false; } @Override public void file(FSFile fsf) { if(skipDirectory.o) return; File outDir = outDirStack.getLast(); if(extractMainFork) { extractEntry(fsf, outDir, pm, errorMessages, extractProperties, skipDirectory, false); } if(extractAdditionalForks) { extractEntry(fsf, outDir, pm, errorMessages, extractProperties, skipDirectory, true); } } @Override public void link(FSLink fsl) { File outDir = outDirStack.getLast(); extractEntry(fsl, outDir, pm, errorMessages, extractProperties, skipDirectory, false); /* Extract any extended attributes belonging to the link itself. */ if(extractAdditionalForks) { extractEntry(fsl, outDir, pm, errorMessages, extractProperties, skipDirectory, true); } } @Override public void traversalError(String message) { errorMessages.addLast(message); } @Override public boolean cancelTraversal() { return pm.cancelSignaled(); } } private class FileSystemProvider implements FileSystemBrowser.FileSystemProvider<FSEntry> { /* @Override */ public void actionDoubleClickFile(List<Record<FSEntry>> recordPath) { if(recordPath.size() < 1) throw new IllegalArgumentException("Empty path to file!"); String[] parentPath = new String[recordPath.size() - 1]; int i = 0; for(Record<FSEntry> curEntry : recordPath) { if(i < parentPath.length) parentPath[i++] = curEntry.getUserObject().getName(); else break; } Record<FSEntry> record = recordPath.get(recordPath.size()-1); FSEntry entry = record.getUserObject(); if(entry instanceof FSFile) { FileSystemBrowserWindow.this.actionDoubleClickFile(parentPath, (FSFile) entry); } else if(entry instanceof FSLink) { FSLink link = (FSLink)entry; FSEntry linkTarget = link.getLinkTarget(parentPath); if(linkTarget == null) JOptionPane.showMessageDialog(FileSystemBrowserWindow.this, "The link you clicked is broken.", "Error", JOptionPane.ERROR_MESSAGE); else if(linkTarget instanceof FSFile) FileSystemBrowserWindow.this.actionDoubleClickFile(parentPath, (FSFile) linkTarget); else throw new RuntimeException("Unexpected FSEntry link target: " + entry.getClass()); } else { throw new RuntimeException("Unexpected FSEntry type: " + entry.getClass()); } } /* @Override */ public void actionExtractToDir(List<Record<FSEntry>> parentPathList, List<Record<FSEntry>> recordList) { String[] parentPath = getFSPath(parentPathList); List<FSEntry> fsEntryList = new ArrayList<FSEntry>(recordList.size()); for(Record<FSEntry> rec : recordList) { fsEntryList.add(rec.getUserObject()); } FileSystemBrowserWindow.this.actionExtractToDir(parentPath, fsEntryList, true, false); } /* @Override */ public void actionGetInfo(final List<Record<FSEntry>> parentPathList, final List<Record<FSEntry>> recordList) { List<FSEntry> entryList = new ArrayList<FSEntry>(recordList.size()); for(Record<FSEntry> rec : recordList) entryList.add(rec.getUserObject()); FileSystemBrowserWindow.this.actionGetInfo(getFSPath(parentPathList), entryList); } /* @Override */ public JPopupMenu getRightClickRecordPopupMenu(final List<Record<FSEntry>> parentPathList, final List<Record<FSEntry>> recordList) { final String[] parentPath = getFSPath(parentPathList); final ArrayList<FSEntry> userObjectList = new ArrayList<FSEntry>(recordList.size()); for(Record<FSEntry> rec : recordList) userObjectList.add(rec.getUserObject()); JPopupMenu jpm = new JPopupMenu(); JMenuItem infoItem = new JMenuItem("Information"); infoItem.addActionListener(new ActionListener() { /* @Override */ public void actionPerformed(ActionEvent e) { FileSystemBrowserWindow.this.actionGetInfo(parentPath, userObjectList); } }); jpm.add(infoItem); JMenuItem dataExtractItem = new JMenuItem("Extract data"); dataExtractItem.addActionListener(new ActionListener() { /* @Override */ public void actionPerformed(ActionEvent e) { FileSystemBrowserWindow.this.actionExtractToDir(parentPath, userObjectList, true, false); } }); jpm.add(dataExtractItem); JMenuItem xattrExtractItem = new JMenuItem("Extract extended attributes"); xattrExtractItem.addActionListener(new ActionListener() { /* @Override */ public void actionPerformed(ActionEvent e) { FileSystemBrowserWindow.this.actionExtractToDir(parentPath, userObjectList, false, true); } }); jpm.add(xattrExtractItem); JMenuItem bothExtractItem = new JMenuItem("Extract data and extended attributes"); bothExtractItem.addActionListener(new ActionListener() { /* @Override */ public void actionPerformed(ActionEvent e) { FileSystemBrowserWindow.this.actionExtractToDir(parentPath, userObjectList, true, true); } }); jpm.add(bothExtractItem); return jpm; } /* @Override */ public boolean isFileSystemLoaded() { return fsHandler != null; } /* @Override */ public List<Record<FSEntry>> getFolderContents(List<Record<FSEntry>> folderRecordPath) { FSEntry lastEntry = folderRecordPath.get(folderRecordPath.size() - 1).getUserObject(); // Resolve any links first if(lastEntry instanceof FSLink) { String[] parentPath = getFSPath(folderRecordPath, folderRecordPath.size()-1); FSEntry linkTarget = ((FSLink)lastEntry).getLinkTarget(parentPath); if(linkTarget == null) { JOptionPane.showMessageDialog(FileSystemBrowserWindow.this, "The link you clicked is broken.", "Error", JOptionPane.ERROR_MESSAGE); throw new RuntimeException("Broken link"); } else if(linkTarget instanceof FSFolder) lastEntry = linkTarget; else throw new RuntimeException("Tried to get folder contents for link target type " + lastEntry.getClass()); } // Then check for folder if(lastEntry instanceof FSFolder) { String[] folderPath = getFSPath(folderRecordPath); FSEntry[] entryArray = ((FSFolder) lastEntry).listEntries(); ArrayList<Record<FSEntry>> entryList = new ArrayList<Record<FSEntry>>(entryArray.length); for(FSEntry entry : entryArray) { Record<FSEntry> rec = new FSEntryRecord(entry, folderPath); entryList.add(rec); } return entryList; } else { throw new RuntimeException("Tried to get folder contents for type " + lastEntry.getClass()); } } /* @Override */ public String getAddressPath(List<String> pathComponents) { StringBuilder sb = new StringBuilder("/"); for(String name : pathComponents) { sb.append(fsHandler.generatePosixPathnameComponent(name)); sb.append("/"); } return sb.toString(); } /* @Override */ public String[] parseAddressPath(String targetAddress) { if(!targetAddress.startsWith("/")) { return null; } else { String remainder = targetAddress.substring(1); if(remainder.length() == 0) { return new String[0]; } else { String[] res = remainder.split("/"); for(int i = 0; i < res.length; ++i) res[i] = fsHandler.parsePosixPathnameComponent(res[i]); return res; } } } /** * Converts a FileSystemBrowser parent path into a FSFramework compatible raw string path. * * @param parentPathList * @return */ private String[] getFSPath(List<Record<FSEntry>> fsbPathList) { if(fsbPathList == null) return null; else return getFSPath(fsbPathList, fsbPathList.size()); } private String[] getFSPath(List<Record<FSEntry>> fsbPathList, int len) { if(len < 1) throw new IllegalArgumentException("A FileSystemBrowser parent path list must " + "have at least one component (the root folder)."); else if(fsbPathList == null) return null; String[] res = new String[len-1]; Iterator<Record<FSEntry>> it = fsbPathList.iterator(); it.next(); // Skip over the root entry for(int i = 0; i < res.length; ++i) { res[i] = it.next().getUserObject().getName(); } while(it.hasNext()) it.next(); // Finish the iterator return res; } } private static class FSEntryRecord extends Record<FSEntry> { public FSEntryRecord(FSEntry entry, String[] parentDirPath) { super(entryTypeToRecordType(entry, parentDirPath), entry.getName(), getEntrySize(entry, parentDirPath), entry.getAttributes().getModifyDate(), entry.isCompressed(), entry); } public static RecordType entryTypeToRecordType(FSEntry entry, String[] parentDirPath) { if(entry instanceof FSFile) { return RecordType.FILE; } else if(entry instanceof FSFolder) { return RecordType.FOLDER; } else if(entry instanceof FSLink) { FSLink fsl = (FSLink)entry; FSEntry linkTarget = fsl.getLinkTarget(parentDirPath); if(linkTarget == null) { return RecordType.BROKEN_LINK; } if(linkTarget instanceof FSFile) { return RecordType.FILE_LINK; } else if(linkTarget instanceof FSFolder) { return RecordType.FOLDER_LINK; } else throw new IllegalArgumentException("Unsupported FSEntry link target: " + entry.getClass()); } else { throw new IllegalArgumentException("Unsupported FSEntry type: " + entry.getClass()); } } public static long getEntrySize(FSEntry entry, String[] parentDirPath) { if(entry instanceof FSFile) { return ((FSFile) entry).getMainFork().getLength(); } else if(entry instanceof FSFolder) { return 0; } else if(entry instanceof FSLink) { FSLink fsl = (FSLink)entry; FSEntry linkTarget = fsl.getLinkTarget(parentDirPath); if(linkTarget == null) { return 0; } else if(linkTarget instanceof FSFile) { return ((FSFile)linkTarget).getMainFork().getLength(); } else if(linkTarget instanceof FSFolder) { return 0; } else throw new IllegalArgumentException("Unsupported FSEntry link target: " + entry.getClass()); } else { throw new IllegalArgumentException("Unsupported FSEntry type: " + entry.getClass()); } } } public static void main(String[] args) { if(System.getProperty("os.name").toLowerCase().startsWith("mac os x")) System.setProperty("apple.laf.useScreenMenuBar", "true"); /* * Description of look&feels: * http://java.sun.com/docs/books/tutorial/uiswing/misc/plaf.html */ String lookAndFeelClassName = javax.swing.UIManager.getSystemLookAndFeelClassName(); System.out.println("System L&F class name: " + lookAndFeelClassName); if(lookAndFeelClassName.equals( "javax.swing.plaf.metal.MetalLookAndFeel") || lookAndFeelClassName.equals( "com.sun.java.swing.plaf.motif.MotifLookAndFeel")) { /* Metal and Motif are really quite terrible L&Fs. Try the GTK+ L&F * instead. */ try { System.err.println("Attempting to force GTK+ L&F..."); javax.swing.UIManager.setLookAndFeel( "com.sun.java.swing.plaf.gtk.GTKLookAndFeel"); lookAndFeelClassName = null; } catch(Exception e) { /* Non-critical. */ } } if(lookAndFeelClassName != null) { try { javax.swing.UIManager.setLookAndFeel(lookAndFeelClassName); } catch(Exception e) { /* Non-critical. */ } } int parsedArgs = 0; final FileSystemBrowserWindow fsbWindow; if(args.length > 0 && args[0].equals(DEBUG_CONSOLE_ARG)) { DebugConsoleWindow dcw = new DebugConsoleWindow(System.err); System.setOut(new PrintStream(dcw.getDebugStream())); System.setErr(new PrintStream(dcw.getDebugStream())); fsbWindow = new FileSystemBrowserWindow(dcw); ++parsedArgs; } else { fsbWindow = new FileSystemBrowserWindow(); /* System.err.println(FileSystemBrowserWindow.class.getName() + ".main invoked."); for(int i = 0; i < args.length; ++i) System.err.println(" args[" + i + "]: \"" + args[i] + "\""); System.err.println(); System.err.println("java.library.path=\"" + System.getProperty("java.library.path") + "\""); */ } fsbWindow.setVisible(true); if(args.length > parsedArgs) { String filename = args[parsedArgs]; try { String pathNameTmp; try { pathNameTmp = new File(filename).getCanonicalPath(); } catch(Exception e) { pathNameTmp = filename; // Just swallow } final String pathName = pathNameTmp; SwingUtilities.invokeLater(new Runnable() { /* @Override */ public void run() { fsbWindow.loadFSWithUDIFAutodetect(pathName); } }); } catch(Exception ioe) { if(ioe.getMessage().equals("Could not open file.")) { JOptionPane.showMessageDialog(fsbWindow, "Failed to open file:\n\"" + filename + "\"", "Error", JOptionPane.ERROR_MESSAGE); } else { ioe.printStackTrace(); String msg = "Exception while loading file:\n" + " \"" + filename + "\"\n" + ioe.toString(); for(StackTraceElement ste : ioe.getStackTrace()) { msg += "\n" + ste.toString(); } JOptionPane.showMessageDialog(fsbWindow, msg, "Exception while loading file", JOptionPane.ERROR_MESSAGE); } } } } }