/*-
* 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);
}
*/
}