/*- * Copyright (C) 2008 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.gui; import java.awt.Component; import java.awt.Window; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.text.DecimalFormat; import javax.swing.SwingUtilities; import org.catacombae.util.ObjectContainer; import org.catacombae.hfsexplorer.SpeedUnitUtils; import org.catacombae.util.Util; import org.catacombae.storage.fs.FSAttributes; import org.catacombae.storage.fs.FSEntry; import org.catacombae.storage.fs.FSFile; import org.catacombae.storage.fs.FSFolder; import org.catacombae.storage.fs.FSLink; /** * @author <a href="http://www.catacombae.org/" target="_top">Erik Larsson</a> */ public class FSEntrySummaryPanel extends javax.swing.JPanel implements ChainedPanel { private static final boolean DEBUG = Util.booleanEnabledByProperties(false, "org.catacombae.debug", "org.catacombae.hfsexplorer.debug", "org.catacombae.hfsexplorer.gui.debug", "org.catacombae.hfsexplorer.gui." + FSEntrySummaryPanel.class.getSimpleName() + ".debug"); private volatile boolean cancelSignaled = false; private DecimalFormat sizeFormatter = new DecimalFormat("0.00"); FSEntrySummaryPanel() { initComponents(); } /* NOTES * * An FSEntry always has: * - Name (every FSEntry must have a name, even if it may be blank) * - Location (the parent to the FSEntry). A POSIX pathname (possibly translated) must also * exist. * - Type (Folder, Link or File) * - Size can be derived from all FSEntries (through recursive search or by looking up a file * entry) and should be considered a must-have field. * - Size on disk. * * An FSEntry may optionally have: * - Various date variables. * - POSIX permissions. * - Global attributes such as "hidden", "write protected", "bundle"... * * An FSFile always has: * - A number of forks (possibly 0), where each fork has * - Identifier * - Length * * An FSFolder always has: * - Valence * * An FSLink always has: * - Link target * * Note to self: The file system model does not support forks tied to folders. This is a * limitation. Some file system out there may be using this method to store metadata about * folders. */ /** Creates new form FSEntrySummaryPanel */ public FSEntrySummaryPanel(Window window, FSEntry entry, String[] parentPath) { this(); window.addWindowListener(new WindowAdapter() { /* @Override public void windowOpened(WindowEvent e) { throw new UnsupportedOperationException("Not supported yet."); } */ @Override public void windowClosing(WindowEvent e) { //System.err.println("Window closing. Signaling any calculate process to stop."); cancelSignaled = true; } /* @Override public void windowClosed(WindowEvent e) { throw new UnsupportedOperationException("Not supported yet."); } @Override public void windowIconified(WindowEvent e) { throw new UnsupportedOperationException("Not supported yet."); } @Override public void windowDeiconified(WindowEvent e) { throw new UnsupportedOperationException("Not supported yet."); } @Override public void windowActivated(WindowEvent e) { throw new UnsupportedOperationException("Not supported yet."); } @Override public void windowDeactivated(WindowEvent e) { throw new UnsupportedOperationException("Not supported yet."); } */ }); nameField.setText(entry.getName()); final String typeString; final String sizeString; final String occupiedSizeString; if(entry instanceof FSFile) { FSFile file = (FSFile) entry; typeString = "File"; sizeString = getSizeString(file.getMainFork().getLength()); occupiedSizeString = getSizeString(file.getMainFork().getOccupiedSize()); } else if(entry instanceof FSFolder) { FSFolder folder = (FSFolder) entry; typeString = "Folder"; sizeString = "Calculating..."; occupiedSizeString = "Calculating..."; startFolderSizeCalculation(folder); } else if(entry instanceof FSLink) { FSLink link = (FSLink) entry; FSEntry linkTarget = link.getLinkTarget(parentPath); if(linkTarget == null) { typeString = "Symbolic link (broken)"; sizeString = "- (broken link)"; occupiedSizeString = "- (broken link)"; } else if(linkTarget instanceof FSFile) { FSFile file = (FSFile) linkTarget; typeString = "Symbolic link (file)"; sizeString = getSizeString(file.getMainFork().getLength()); occupiedSizeString = getSizeString(file.getMainFork().getOccupiedSize()); } else if(linkTarget instanceof FSFolder) { FSFolder folder = (FSFolder) linkTarget; typeString = "Symbolic link (folder)"; sizeString = "Calculating..."; occupiedSizeString = "Calculating..."; startFolderSizeCalculation(folder); } else { typeString = "Symbolic link (unknown [" + linkTarget.getClass() + "])"; sizeString = "- (unknown type)"; occupiedSizeString = "- (unknown type)"; } } else { typeString = "Unknown [" + entry.getClass() + "]"; sizeString = "- (unknown type)"; occupiedSizeString = "- (unknown type)"; } typeField.setText(typeString); sizeField.setText(sizeString); occupiedSizeField.setText(occupiedSizeString); FSAttributes attrs = entry.getAttributes(); ChainedPanel currentChain = this; if(entry instanceof FSLink) { LinkTargetPanel ltp = new LinkTargetPanel((FSLink)entry); currentChain.setChainedContents(ltp); currentChain = ltp; } DateSummaryPanel dsp = new DateSummaryPanel(attrs); currentChain.setChainedContents(dsp); currentChain = dsp; if(attrs.hasPOSIXFileAttributes()) { POSIXAttributesPanel attributesPanel = new POSIXAttributesPanel(attrs.getPOSIXFileAttributes()); currentChain.setChainedContents(attributesPanel); currentChain = attributesPanel; } } /* @Override */ public void setChainedContents(Component c) { extendedInfoStackPanel.removeAll(); extendedInfoStackPanel.add(c); } /** This method is called from within the constructor to * initialize the form. * WARNING: Do NOT modify this code. The content of this method is * always regenerated by the Form Editor. */ @SuppressWarnings("unchecked") // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents private void initComponents() { jLabel1 = new javax.swing.JLabel(); nameField = new javax.swing.JTextField(); jLabel2 = new javax.swing.JLabel(); typeField = new javax.swing.JTextField(); jLabel3 = new javax.swing.JLabel(); sizeField = new javax.swing.JTextField(); occupiedSizeLabel = new javax.swing.JLabel(); occupiedSizeField = new javax.swing.JTextField(); jSeparator1 = new javax.swing.JSeparator(); extendedInfoStackPanel = new javax.swing.JPanel(); jLabel1.setText("Name:"); nameField.setEditable(false); nameField.setText("jTextField1"); nameField.setOpaque(false); jLabel2.setText("Type:"); typeField.setEditable(false); typeField.setText("jTextField2"); typeField.setBorder(null); typeField.setOpaque(false); jLabel3.setText("Size:"); sizeField.setEditable(false); sizeField.setText("jTextField3"); sizeField.setBorder(null); sizeField.setOpaque(false); occupiedSizeLabel.setText("Size on disk:"); occupiedSizeField.setEditable(false); occupiedSizeField.setText("occupiedSizeField"); occupiedSizeField.setBorder(null); occupiedSizeField.setOpaque(false); extendedInfoStackPanel.setLayout(new javax.swing.BoxLayout(extendedInfoStackPanel, javax.swing.BoxLayout.PAGE_AXIS)); org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(org.jdesktop.layout.GroupLayout.TRAILING, layout.createSequentialGroup() .addContainerGap() .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING) .add(org.jdesktop.layout.GroupLayout.LEADING, extendedInfoStackPanel, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 340, Short.MAX_VALUE) .add(org.jdesktop.layout.GroupLayout.LEADING, jSeparator1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 340, Short.MAX_VALUE) .add(org.jdesktop.layout.GroupLayout.LEADING, layout.createSequentialGroup() .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(jLabel1) .add(jLabel2) .add(jLabel3) .add(occupiedSizeLabel)) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(occupiedSizeField, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 305, Short.MAX_VALUE) .add(sizeField, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 305, Short.MAX_VALUE) .add(typeField, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 305, Short.MAX_VALUE) .add(nameField, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 305, Short.MAX_VALUE)))) .addContainerGap()) ); layout.setVerticalGroup( layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(layout.createSequentialGroup() .addContainerGap() .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) .add(jLabel1) .add(nameField, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) .add(jLabel2) .add(typeField, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) .add(jLabel3) .add(sizeField, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) .add(occupiedSizeLabel) .add(occupiedSizeField, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(jSeparator1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(extendedInfoStackPanel, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 119, Short.MAX_VALUE) .addContainerGap()) ); }// </editor-fold>//GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JPanel extendedInfoStackPanel; private javax.swing.JLabel jLabel1; private javax.swing.JLabel jLabel2; private javax.swing.JLabel jLabel3; private javax.swing.JSeparator jSeparator1; private javax.swing.JTextField nameField; private javax.swing.JTextField occupiedSizeField; private javax.swing.JLabel occupiedSizeLabel; private javax.swing.JTextField sizeField; private javax.swing.JTextField typeField; // End of variables declaration//GEN-END:variables private String getSizeString(long result) { String baseString = Long.toString(result); if(result >= 1000) { String spacedString = Util.addUnitSpaces(baseString, 3); if(result >= 1024) { return SpeedUnitUtils.bytesToBinaryUnit(result, sizeFormatter) + " (" + spacedString + " bytes" + ")"; } else return spacedString + " bytes"; } else return baseString + " bytes"; } private void startFolderSizeCalculation(final FSFolder folder) { Runnable r = new Runnable () { /* @Override */ public void run() { String sizeResultString; String occupiedSizeResultString; try { ObjectContainer<Long> sizeResult = new ObjectContainer<Long>((long)0); ObjectContainer<Long> occupiedSizeResult = new ObjectContainer<Long>((long)0); calculateFolderSize(folder, sizeResult, occupiedSizeResult); sizeResultString = getSizeString(sizeResult.o); occupiedSizeResultString = getSizeString(occupiedSizeResult.o); } catch(Exception e) { e.printStackTrace(); sizeResultString = "Exception while calculating! See debug console " + "for info..."; occupiedSizeResultString = "Exception while calculating! See debug console " + "for info..."; } final String finalSizeResultString; final String finalOccupiedSizeResultString; if(!cancelSignaled) { finalSizeResultString = sizeResultString; finalOccupiedSizeResultString = occupiedSizeResultString; } else { finalSizeResultString = "Canceled"; finalOccupiedSizeResultString = "Canceled"; } SwingUtilities.invokeLater(new Runnable() { /* @Override */ public void run() { sizeField.setText(finalSizeResultString); occupiedSizeField.setText( finalOccupiedSizeResultString); } }); } }; new Thread(r).start(); } private void calculateFolderSize(FSFolder folder, ObjectContainer<Long> sizeResult, ObjectContainer<Long> occupiedSizeResult) { if(cancelSignaled) { if(DEBUG) { System.err.println("Calculate process stopping for folder " + "\"" + folder.getName() + "\""); } return; } for(FSEntry entry : folder.listEntries()) { if(cancelSignaled) { if(DEBUG) { System.err.println("Calculate process stopping for " + "folder \"" + folder.getName() + "\", entry " + "\"" + entry.getName() + "\""); } return; } if(entry instanceof FSFile) { sizeResult.o = sizeResult.o + ((FSFile) entry).getMainFork().getLength(); occupiedSizeResult.o = occupiedSizeResult.o + ((FSFile) entry).getMainFork().getOccupiedSize(); } else if(entry instanceof FSFolder) { calculateFolderSize((FSFolder) entry, sizeResult, occupiedSizeResult); } else if(entry instanceof FSLink) { /* Do nothing. Symbolic link targets aren't part of the folder. */ } else System.err.println("FSEntrySummaryPanel.calculateFolderSize():" + " unexpected type " + entry.getClass()); } } /* public static void main(String[] args) { JFrame jf = new JFrame("Test"); jf.add(new JScrollPane(new FSEntrySummaryPanel())); jf.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); jf.pack(); jf.setLocationRelativeTo(null); jf.setVisible(true); } */ }