/*
* A CCNx command line utility.
*
* Copyright (C) 2008, 2009, 2012 Palo Alto Research Center, Inc.
*
* This work is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License version 2 as published by the
* Free Software Foundation.
* This work 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, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
package org.ccnx.ccn.utils.explorer;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Set;
import java.util.logging.Level;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JEditorPane;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextField;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import org.ccnx.ccn.CCNHandle;
import org.ccnx.ccn.config.ConfigurationException;
import org.ccnx.ccn.impl.support.Log;
import org.ccnx.ccn.profiles.nameenum.BasicNameEnumeratorListener;
import org.ccnx.ccn.profiles.nameenum.CCNNameEnumerator;
import org.ccnx.ccn.profiles.security.access.group.GroupAccessControlManager;
import org.ccnx.ccn.protocol.ContentName;
import org.ccnx.ccn.protocol.MalformedContentNameStringException;
/**
* The ContentExplorer is an experimental app still under development. This
* application explores ContentObjects that are available in a GUI. The
* ContentExplorer uses CCNNameEnumeration to populate the GUI and can open .txt
* and .text files in a preview pane or separate window. The ContentExplorer can
* also be used to store files in a repository. Finally, the ContentExplorer is
* intended to be used as a first test of AccessControl functionality with CCN.
* This is in an extremely early state and will be updated in future releases.
*
*/
public class ContentExplorer extends JFrame implements BasicNameEnumeratorListener, ActionListener {
private static ContentName root;
private static boolean accessControlOn = false;
protected static boolean showVersions = false;
protected static boolean debugMode = false;
private static GroupAccessControlManager gacm = null;
private static String userName = null;
private static boolean previewTextFiles = true;
private CCNNameEnumerator _nameEnumerator = null;
protected static CCNHandle _handle = null;
private static boolean useSystemLookAndFeel = false;
private static final long serialVersionUID = 1L;
static java.net.URL netURL = ContentExplorer.class.getResource("Network.png");
public static final ImageIcon ICON_COMPUTER = new ImageIcon(getScaledImage(
(new ImageIcon(netURL)).getImage(), 32, 32));
static java.net.URL compURL = ContentExplorer.class.getResource("Computer.png");
public static final ImageIcon ICON_DISK = new ImageIcon(getScaledImage(
(new ImageIcon(compURL)).getImage(), 32, 32));
static java.net.URL imageURL = ContentExplorer.class.getResource("Folder.png");
public static final ImageIcon ICON_FOLDER = new ImageIcon(getScaledImage(
(new ImageIcon(imageURL)).getImage(), 32, 32));
static java.net.URL docURL = ContentExplorer.class.getResource("Document.png");
public static final ImageIcon ICON_EXPANDEDFOLDER = new ImageIcon(getScaledImage(
(new ImageIcon(docURL)).getImage(), 32, 32));
public static final ImageIcon ICON_DOCUMENT = new ImageIcon(getScaledImage(
(new ImageIcon(docURL)).getImage(), 32, 32));
private final JEditorPane htmlPane;
public String selectedPrefix;
public String selectedPath;
protected JTree tree;
protected DefaultTreeModel m_model;
//protected JTextField m_display;
private final JTextField textArea;
private JButton openACL = null;
private JButton openGroup = null;
private ContentName currentPrefix = null;
private DefaultMutableTreeNode usableRoot = null;
private DirExpansionListener dirExpansionListener = null;
private DirSelectionListener dirSelectionListener = null;
protected JPopupMenu tree_popup;
protected Action tree_popupaction;
protected boolean vlcSupported = false;
/**
* Constructor for ContentExplorer application. This sets up the swing elements and listeners for the GUI.
* The constructor also initializes the CCNHandle and name enumeration.
*/
public ContentExplorer() {
super("CCN Content Explorer");
if (userName != null) this.setTitle("CCN Content Explorer for " + userName);
//vlcSupported = checkVLCsupport();
setupNameEnumerator();
setSize(400, 300);
ContentName slash = null;
slash = ContentName.ROOT;
DefaultMutableTreeNode top = new DefaultMutableTreeNode(new IconData(
ICON_FOLDER, null, new Name(slash.component(0), null, true)));
DefaultMutableTreeNode node = top;
DefaultMutableTreeNode newNode = null;
// add each component of the root
Log.fine("root = " + root.toString());
for (int i = 0; i < root.count(); i++) {
Log.fine("adding component: " + root.stringComponent(i));
// add each component to the tree
newNode = new DefaultMutableTreeNode(new IconData(ICON_FOLDER,
null, new Name(root.component(i), root.cut(i), true)));
node.add(newNode);
node = newNode;
}
usableRoot = top;
m_model = new DefaultTreeModel(top);
tree = new JTree(m_model);
tree.putClientProperty("JTree.lineStyle", "Angled");
TreeCellRenderer renderer = new IconCellRenderer();
tree.setCellRenderer(renderer);
dirExpansionListener = new DirExpansionListener();
tree.addTreeExpansionListener(dirExpansionListener);
dirSelectionListener = new DirSelectionListener();
tree.addTreeSelectionListener(dirSelectionListener);
tree_popup = new JPopupMenu();
tree_popupaction = new AbstractAction() {
private static final long serialVersionUID = 9114007083621952181L;
public void actionPerformed(ActionEvent e){
TreePath p = (TreePath)(tree_popupaction.getValue("PATH"));
if(p==null) {
//System.err.println("path is null");
return;
}
else if(tree.isExpanded(p)) {
tree.collapsePath(p);
}
else {
if (e.getActionCommand().equals("Expand")) {
TreeExpansionEvent t = new TreeExpansionEvent(tree, p);
dirExpansionListener.treeExpanded(t);
tree.expandPath(p);
} else if (e.getActionCommand().equals("Select")) {
tree.setSelectionPath(p);
}
}
}
};
tree_popup.add(tree_popupaction);
tree_popup.addSeparator();
Action displayName = new AbstractAction("Display Full Prefix") {
private static final long serialVersionUID = 6373543410642021178L;
public void actionPerformed(ActionEvent e){
tree.repaint();
TreePath p = (TreePath)(tree_popupaction.getValue("PATH"));
Name node = getNameNode((DefaultMutableTreeNode) p.getLastPathComponent());
ContentName n = null;
if(node.name == null && node.path == null)
n = new ContentName();
else
n = new ContentName(node.path, node.name);
htmlPane.setText(n.toString());
}
};
tree_popup.add(displayName);
Action saveFile = new AbstractAction("Save File") {
private static final long serialVersionUID = -3770094703319020441L;
public void actionPerformed(ActionEvent e){
tree.repaint();
TreePath p = (TreePath)(tree_popupaction.getValue("PATH"));
Name node = getNameNode((DefaultMutableTreeNode) p.getLastPathComponent());
ContentName name = null;
if(node.name == null && node.path == null)
name = new ContentName();
else
name = new ContentName(node.path, node.name);
LocalSaveContentRetriever localsave = new LocalSaveContentRetriever(_handle, name, htmlPane);
Thread t = new Thread(localsave);
t.start();
}
};
tree_popup.add(saveFile);
Action playFile = new AbstractAction("Play File") {
private static final long serialVersionUID = -2932828512965050415L;
public void actionPerformed(ActionEvent e){
tree.repaint();
htmlPane.setText("play file with VLC not implemented yet");
}
};
if(vlcSupported)
tree_popup.add(playFile);
Action showVersions = new AbstractAction("Show Versions") {
private static final long serialVersionUID = -827879841202976452L;
public void actionPerformed(ActionEvent e){
tree.repaint();
htmlPane.setText("display versions of file not implemented yet");
String toDisplay = "";
TreePath p = (TreePath)(tree_popupaction.getValue("PATH"));
Name node = getNameNode((DefaultMutableTreeNode) p.getLastPathComponent());
Set<ContentName> versions = node.getVersions();
synchronized(versions) {
for(ContentName c: versions) {
toDisplay = toDisplay+c.toString()+"\n";
}
}
if (toDisplay.equals("")) {
htmlPane.setText("Version numbers are currently not available for this name. \nThis can occur if the node was not previously selected.");
} else {
htmlPane.setText(toDisplay);
}
}
};
tree_popup.add(showVersions);
tree.add(tree_popup);
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
tree.setShowsRootHandles(true);
tree.setEditable(false);
String filename = "~/";
JFileChooser fc = new JFileChooser(new File(filename));
class OpenFileAction extends AbstractAction {
private static final long serialVersionUID = 1L;
JFrame frame;
JFileChooser chooser;
OpenFileAction(JFrame frame, JFileChooser chooser) {
super("Send to Repo...");
this.chooser = chooser;
this.frame = frame;
}
public void actionPerformed(ActionEvent evt) {
// if we don't have a file then we should show a file chooser
// otherwise give an error message "Please select a folder"
//commented out this behavior. we have many things that do have a . in them. also
//when showing versions, they may have characters that are interpreted as a .
/*if (((selectedPrefix.toString()).split("\\.")).length > 2) {
* JOptionPane.showMessageDialog(this.frame,
* "Please Select a Directory to add a file",
* "Select Directory", JOptionPane.ERROR_MESSAGE);
* } else {
*
*/
// Show dialog; this method does not return until dialog is
// closed
int returnVal = chooser.showOpenDialog(frame);
// Get the selected file
File file = chooser.getSelectedFile();
if (file == null || file.getName().equals("")) {
Log.fine("the user did not select a file");
return;
}
// what if the user hits cancel...
if (returnVal != JFileChooser.APPROVE_OPTION) {
Log.fine("user cancelled the send to repo option... returning");
return;
}
Log.info("Writing a file to the repo " + file.getAbsolutePath() + " " + file.getName());
Log.fine("Selected Node is " + selectedPrefix);
try {
ContentName contentName = ContentName.fromURI(selectedPrefix).append(file.getName());
ContentName temp = null;
while (temp==null) {
String name = JOptionPane.showInputDialog("Send File to Repo As:", contentName.toString());
if (name == null) {
Log.fine("user selected cancel, returning");
return;
}
Log.info("user entered [{0}]", name);
//System.out.println("user entered ["+name+"]");
try {
if (name.startsWith("ccnx:/"))
name = name.replaceFirst("ccnx:/", "/");
temp = ContentName.fromURI(name);
//temp = ContentName.fromNative(name);
contentName = temp;
Log.info("saving as [{0}]", contentName);
//System.out.println("saving as ["+contentName+"]");
}
catch (Exception e) {
Log.fine("User entered invalid name for save: {0}", e.getMessage());
if(name.equals(""))
JOptionPane.showMessageDialog(chooser, "Please enter a CCNx name for the content that starts with \"/\".");
else
JOptionPane.showMessageDialog(chooser, (name + " is not a valid CCNx name. Please be sure it starts with \"/\""));
}
}
sendFile(file, contentName);
} catch (MalformedContentNameStringException e) {
Log.logException("could not create content name for selected file: "+file.getName(), e);
//}
}
}
}
;
Action openAction = new OpenFileAction(this, fc);
JButton openButton = new JButton(openAction);
openACL = new JButton("Manage Permissions");
openACL.addActionListener(this);
openGroup = new JButton("Open Group Manager");
openGroup.addActionListener(this);
// Create the scroll pane and add the tree to it.
JScrollPane treeView = new JScrollPane(tree);
// Create the HTML viewing pane.
htmlPane = new JEditorPane();
htmlPane.setEditable(false);
initHelp();
JScrollPane htmlView = new JScrollPane(htmlPane);
textArea = new JTextField();
textArea.setEditable(false);
textArea.setText("here is where names will appear");
//Component c1 = new ExplorerPanel();
//Component c2 = new TextPanel();
// Add the scroll panes to a split pane.
JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
splitPane.setContinuousLayout(true);
splitPane.setOneTouchExpandable(true);
splitPane.setTopComponent(treeView);
splitPane.setBottomComponent(htmlView);
//splitPane.setBottomComponent(textArea);
Dimension minimumSize = new Dimension(100, 50);
htmlView.setMinimumSize(minimumSize);
treeView.setMinimumSize(minimumSize);
splitPane.setDividerLocation(250);
splitPane.setPreferredSize(new Dimension(500, 300));
setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.BOTH;
c.gridx = 0;
c.gridy = 1;
if (accessControlOn)
getContentPane().add(openACL, c);
c.fill = GridBagConstraints.BOTH;
c.gridx = 1;
c.gridy = 1;
if (accessControlOn)
getContentPane().add(openGroup, c);
c.fill = GridBagConstraints.BOTH;
c.gridwidth = 2;
c.gridx = 0;
c.gridy = 0;
getContentPane().add(openButton, c);
c.weightx = 1;
c.weighty = 1;
c.fill = GridBagConstraints.BOTH;
c.gridwidth = 2;
c.gridx = 0;
c.gridy = 2;
getContentPane().add(splitPane, c);
pack();
Log.fine("setting selectionPath: " + node.getPath()+ " node: " + node.toString());
tree.expandPath(new TreePath(node.getPath()));
tree.setSelectionPath(new TreePath(node.getPath()));
tree.addMouseListener(new MouseActions());
WindowListener wndCloser = new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
};
addWindowListener(wndCloser);
setVisible(true);
}
/**
* Method to trigger a thread to retrieve a file. A new thread is created to get the file.
* This method displays a message about the file to retrieve in the preview pane and displays
* the text of the file when the download is complete.
*
* @param name Name of the file to retrieve
* @param txtPopup True if the file should be opened in a text popup
*/
public void retrieveFromRepo(String name, boolean textPopup) {
htmlPane.setText("Retrieving content... "+name);
HTMLPaneContentRetriever retriever = new HTMLPaneContentRetriever(_handle, htmlPane, name);
retriever.setTextPopup(textPopup);
Thread t = new Thread(retriever);
t.start();
}
/**
* Method to trigger a thread to retrieve a file. A new thread is created to get the file.
* This method displays a message about the file to retrieve in the preview pane and displays
* the text of the file when the download is complete.
*
* @param name Name of the file to retrieve
*/
public void retrieveFromRepo(String name) {
retrieveFromRepo(name, false);
}
/**
* Method to store a file in a repository. A new thread is created for this process.
* A message indicating the file the is being written displays in the preview pane.
* When the upload is complete, or an error occurs, the preview pane is updated to show
* the new state.
*
* @param file
* @param ccnName
*/
public void sendFile(File file, ContentName ccnName) {
htmlPane.setText("Writing " + file.getName() + " to CCN as: " + ccnName);
ContentWriter writer = new ContentWriter(_handle, ccnName, file, htmlPane);
Thread t = new Thread(writer);
t.start();
}
/**
* Simple instruction message to display when another file or status message is not displayed.
*/
private void initHelp() {
htmlPane.setText("Please expand folder names you would like to enumerate. You may also select text files to be displayed in this window.");
}
/**
* Method to get Swing components used for storing the name hierarchy. Returns null if the
* prefix is not found. Uses the ContentExplorer.find() method to recursively search through the tree.
*
* @param ccnContentName Prefix to retrieve in the tree.
* @return DefaultMutableTreeNode The node in the tree representing the supplied prefix.
*/
DefaultMutableTreeNode getTreeNode(ContentName ccnContentName) {
Log.fine("handling returned names!!! prefix = "+ ccnContentName.toString());
TreePath prefixPath = new TreePath(usableRoot);
Log.fine("prefix path: " + prefixPath.toString());
String[] names = new String[ccnContentName.count()];
int ind = 0;
for (byte[] n : ccnContentName) {
Log.fine("adding n: "+new String(n));
ContentName newName = new ContentName(n);
Log.fine("newName = "+newName+" "+newName.toString().replace("/", ""));
names[ind] = newName.toString();
ind++;
}
DefaultMutableTreeNode p = find(prefixPath, 0, names);
if(p == null)
Log.fine("returning null could not find: "+prefixPath.toString());
return p;
}
/**
* Method for traversing the tree to find a particular node corresponding to a name.
*
* @param parent TreePath for the node we are able to explore
* @param depth Current depth of the tree (int)
* @param names String[] representing the name we are looking for in components
* @return DefaultMutableTreeNode Returns the desired node or null if it is not found.
*/
private DefaultMutableTreeNode find(TreePath parent, int depth, String[] names) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) parent.getLastPathComponent();
String nodeName = node.toString().replace("/", "");
Log.fine("check nodeName: [" + nodeName + "] node: [" + node.toString()+ "]");
Log.fine("depth = "+depth +" names.length = "+names.length);
if (names.length <= depth) {
// we don't have to look far... this matches the root
Log.fine("this is the root, and we want the root... returning the root");
return node;
}
if(node.equals(usableRoot)) {
Log.fine("this is the usable root");
node = findMatchingChild(parent, node, names[depth]);
nodeName = node.toString();
Log.fine("using root child: "+nodeName);
}
String nameToCheck = names[depth].replace("/","");
Log.fine("names[depth] " + names[depth]);
Log.fine("nameToCheck: "+nameToCheck);
// we added an extra empty slash at the top... need to account for this
if (nameToCheck.equals(nodeName)) {
Log.fine("we have a match!");
if (depth == names.length) {
Log.fine("we are at the right depth! returning this node!");
return node;
} else {
Log.fine("need to keep digging...");
if (node.getChildCount() > 0) {
Log.fine("we have children: " + node.getChildCount());
DefaultMutableTreeNode result = null;
if (names.length > depth + 1)
result = findMatchingChild(parent, node, names[depth + 1]);
if (result == null) {
Log.fine("no matching child... returning this node");
return node;
} else {
Log.fine("result was not null... we have a matching child");
}
TreePath path = parent.pathByAddingChild(result);
return find(path, depth + 1, names);
} else {
Log.fine("did not have any children...");
return node;
}
}
} else {
Log.fine("not a match...");
}
return null;
}
/**
* Method to find a child with a specific name.
*
* @param parent TreePath for the parent we are searching
* @param n Node to look for children
* @param name Name for the child we are looking for
*
* @return DefaultMutableTreeNode Returns the child we are looking for,
* or null if it does not exist.
*/
DefaultMutableTreeNode findMatchingChild(TreePath parent, DefaultMutableTreeNode n, String name) {
name = name.replace("/", "");
if (n.getChildCount() == 0)
return null;
else {
DefaultMutableTreeNode c = null;
for (int i = 0; i < n.getChildCount(); i++) {
c = (DefaultMutableTreeNode) n.getChildAt(i);
Log.fine("child name: " + c.toString() + " name: " + name);
if (c.toString().equals(name)) {
Log.fine("child names are equal... returning child");
return c;
}
else
Log.fine("child names not equal");
}
}
return null;
}
/**
* Method to get the DefaultMutableTreeNode for a given path.
*
* @param path TreePath for the node we are looking for
*
* @return DefaultMutableTreeNode Node we are looking for
*/
DefaultMutableTreeNode getTreeNode(TreePath path) {
return (DefaultMutableTreeNode) (path.getLastPathComponent());
}
/**
* Method to get the user object as a Name from a DefaultMutableTreeNode.
*
* @param node The node we need the name of
* @return Name The name of the node. Returns null if the node is null.
*/
Name getNameNode(DefaultMutableTreeNode node) {
if (node == null)
return null;
Object obj = node.getUserObject();
if (obj instanceof IconData)
obj = ((IconData) obj).getObject();
if (obj instanceof Name)
return (Name) obj;
else
return null;
}
/**
* Method to get the user object as a FileNode.
*
* @param node The node we want a FileNode from
* @return FileNode The user object casted to a FileNode if it is one, null otherwise.
*/
FileNode getFileNode(DefaultMutableTreeNode node) {
if (node == null)
return null;
Object obj = node.getUserObject();
if (obj instanceof IconData)
obj = ((IconData) obj).getObject();
if (obj instanceof FileNode)
return (FileNode) obj;
else
return null;
}
/**
* Experimental code - not tested
*
*/
class MouseActions implements MouseListener {
/**
* Class to handle mouse events. This includes a single click to begin enumeration for folders.
* If the selected item is a .txt or .text file, it will preview in the lower pane.
* @param e MouseEvent
*/
public void mousePressed(MouseEvent e) {
int selRow = tree.getRowForLocation(e.getX(), e.getY());
TreePath selPath = tree.getPathForLocation(e.getX(), e.getY());
if (selRow != -1) {
if (e.getClickCount() == 2) {
myDoubleClick(selRow, selPath);
}
}
if(e.isPopupTrigger()) {
popup(selPath, e.getX(), e.getY(), tree.getRowForLocation(e.getX(), e.getY()));
}
}
public void mouseReleased(MouseEvent e) {
if(e.isPopupTrigger()) {
popup(tree.getPathForLocation(e.getX(), e.getY()), e.getX(), e.getY(), tree.getRowForLocation(e.getX(), e.getY()));
}
}
public void mouseClicked(MouseEvent e) {
if(e.isPopupTrigger()) {
popup(tree.getPathForLocation(e.getX(), e.getY()), e.getX(), e.getY(), tree.getRowForLocation(e.getX(), e.getY()));
}
}
private void popup(TreePath selPath, int x, int y, int row){
tree_popupaction.putValue("PATH", selPath);
if(selPath!=null) {
if(tree.isExpanded(selPath)) {
tree_popupaction.putValue(Action.NAME, "Collapse");
} else {
if(tree.isRowSelected(row)) {
//if this is the first selection, the node needs to be selected first
//TreeExpansionEvent t = new TreeExpansionEvent(tree, selPath);
//dirExpansionListener.treeExpanded(t);
tree_popupaction.putValue(Action.NAME, "Expand");
}
else {
tree_popupaction.putValue(Action.NAME, "Select");
}
}
tree_popup.show(tree, x, y);
//tree_clickedpath = selPath;
}
}
/**
* Double click action. If a .txt or .text file is double clicked, it will open in a separate window.
* The file is obtained through CCN. If the file is no longer available, a message will appear in
* the preview pane.
*
* @param selRow Swing row selection
* @param selPath Path selected
*/
private void myDoubleClick(int selRow, TreePath selPath) {
final Name node = getNameNode((DefaultMutableTreeNode) selPath.getLastPathComponent());
if (node.name == null && node.equals(getNameNode(usableRoot))) {
if (usableRoot.getChildCount() == 0 )
JOptionPane.showMessageDialog(ContentExplorer.this,
"Namespace is not available at this time",
"Cannot Open Namespace",
JOptionPane.ERROR_MESSAGE);
return;
}
if (node.name == null)
return; // XXX shouldn't happen?
ContentName cn = new ContentName(node.name);
String name = cn.toString();
if (name.toLowerCase().endsWith(".txt") || name.toLowerCase().endsWith(".text")) {
if (node.path.count() == 0) {
//retrieveFromRepo("/" + name);
retrieveFromRepo(name, true);
}
else {
//retrieveFromRepo(node.path.toString() + "/" + name);
retrieveFromRepo(node.path.toString() + name, true);
}
} else {
// Can't handle filetype
JOptionPane.showMessageDialog(ContentExplorer.this, "Cannot Open file "
+ cn + " Currently only opens .txt and .text files.",
"Only handles .txt and .text files at this time.",
JOptionPane.ERROR_MESSAGE);
}
}
public void mouseEntered(MouseEvent e) {
// we do not have actions for this yet
}
public void mouseExited(MouseEvent e) {
// we do not have actions for this yet
}
}
/**
* Class to handle directory actions - expand and collapse.
*
* @see TreeExpansionListener
*/
class DirExpansionListener implements TreeExpansionListener {
/**
* Method called when a tree node is expanded, currently not used.
*
* @param event Swing TreeExpansionEvent object
* @return void
*/
public void treeExpanded(TreeExpansionEvent event) {
DefaultMutableTreeNode node = getTreeNode(event.getPath());
Name fnode = getNameNode(node);
if (fnode != null) {
getNodes(fnode);
m_model.reload(node);
}
}
/**
* Method to handle tree collapse events. When a tree is collapsed, all name enumerations under
* the collapsed node are canceled.
*
* @param event TreeExpansionEvent object for the event
*
* @return void
*/
public void treeCollapsed(TreeExpansionEvent event) {
DefaultMutableTreeNode node = getTreeNode(event.getPath());
Name nodeName = getNameNode(node);
Log.fine("nodeName: " + nodeName.toString());
ContentName prefixToCancel = ContentName.ROOT;
if (nodeName.path == null) {
Log.fine("collapsed the tree at the root");
} else {
prefixToCancel = new ContentName(nodeName.path, nodeName.name);
Log.fine("tree collapsed at: " + prefixToCancel.toString());
}
Log.fine("cancelling prefix: " + prefixToCancel);
_nameEnumerator.cancelEnumerationsWithPrefix(prefixToCancel);
}
}
/**
* Class to handle tree component selections.
*
*/
class DirSelectionListener implements TreeSelectionListener {
/**
* Method to handle selection events. If a node is selected for the first time and is a folder,
* name enumeration will begin under that prefix. This event is also triggered as the tree is collapsed.
* In this case, we do not want to reselect nodes that were already canceled since
* enumeration will be started again. To avoid this, the method checks if the path has any parent collapsed,
* if so, the event is not processed so name enumeration will not be restarted.
*
* @param event TreeSelectionEvent object to handle.
* @return void
*/
public void valueChanged(TreeSelectionEvent event) {
final DefaultMutableTreeNode node = getTreeNode(event.getPath());
// if the tree is not collapsed already, it is already being
// enumerated, so we don't need to reselect it
// if the row is -1, that means a parent is collapsed and this node
// is being
// selected as part of a collapse, so we don't want to re-register
// it for enumerating
if (tree.getRowForPath(event.getPath()) > -1) {
tree.setSelectionPath(event.getPath());
Thread runner = new Thread() {
@Override
public void run() {
Log.fine("getting name node: " + node.toString());
Name fnode = getNameNode(node);
if (fnode == null) {
Log.fine("fnode path is null...");
// selected top component, switch to top
// usable node
fnode = getNameNode(usableRoot);
}
Log.fine("In the tree selection listener with " + fnode.name + " and " + node.toString());
String p = getNodes(fnode);
selectedPath = p;
selectedPrefix = p;
}
};
runner.start();
}
}
}
/**
* Main method for the ContentExplorer GUI. The GUI defaults to exploring "/" but takes a -root option for exploring
* alternate namespaces.
*
* @param args String[] of the arguments for the GUI. (path to explore and optional experimental access control GUI)
*/
public static void main(String[] args) {
Log.setDefaultLevel(Level.WARNING);
if (args.length > 0) {
// we have some options
String extraUsage = "";
for (int i = 0; i < args.length; i++) {
if (i == 0 && args[0].startsWith("[")) {
extraUsage = args[0];
continue;
}
if (args[i].equals("-h")) {
usage(extraUsage);
}
String s = args[i];
if (s.equalsIgnoreCase("-root")) {
i++;
try {
root = ContentName.fromURI(args[i]);
} catch (MalformedContentNameStringException e) {
Log.warning("Could not parse root path: " + args[i] + " (exiting)");
System.err.println("Could not parse root path: " + args[i] + " (exiting)");
System.exit(1);
}
} else if (s.equals("-accessControl")) {
accessControlOn = true;
} else if (s.equals("-showVersions")) {
showVersions = true;
} else if (s.equals("-debugMode")) {
debugMode = true;
} else {
usage(extraUsage);
}
}
}
if (root == null) {
try {
root = ContentName.fromNative("/");
} catch (MalformedContentNameStringException e) {
Log.warning("Could not parse root path: / (exiting)");
System.err.println("Could not parse root path: / (exiting)");
System.exit(1);
}
}
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
/**
* Usage for ContentExplorer GUI.
*/
public static void usage(String extraUsage) {
System.out.println("Content Explorer usage: " + extraUsage + "[-root /pathToExplore] [-accessControl] [-showVersions] [-debugMode]");
System.exit(1);
}
/**
* Method to get the node selected with the SelectionListener
*
* @param fnode Name node to select
* @return String the full name for the selected node
*/
public String getNodes(Name fnode) {
if (fnode.path == null)
Log.fine("the path is null");
else
Log.fine("fnode: " + new ContentName(fnode.name) + " path: " + fnode.path.toString());
ContentName toExpand = null;
if (fnode.path == null)
toExpand = ContentName.ROOT;
else
toExpand = new ContentName(fnode.path, fnode.name);
String p = toExpand.toString();
Log.fine("toExpand: " + toExpand + " p: " + p);
if (fnode.name != null && previewTextFiles && (new ContentName(fnode.name).toString().endsWith(".txt") || new ContentName(fnode.name).toString().endsWith(".text"))) {
// get the file from the repo
Log.fine("Retrieve from Repo: " + p);
retrieveFromRepo(p);
}
// this is a directory that we want to enumerate... if it is a text file, we will still want to get the versions
if (fnode.path == null)
Log.fine("the path is null");
else
Log.fine("this is the path: " + fnode.path.toString() + " this is the name: " + new ContentName(fnode.name));
Log.info("Registering Prefix: " + p);
registerPrefix(p);
initHelp();
return p;
}
/**
* Static method to create and display the GUI.
*/
public static void createAndShowGUI() {
if (useSystemLookAndFeel) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
Log.warning("Couldn't use system look and feel.");
}
}
new ContentExplorer();
}
/**
* Method to handle CCNNameEnumeration callbacks. This implementation assumes the
* application handles duplicates. This method creates an instance of the Runnable
* AddChildren class to process the names returned through CCNNameEnumeration.
*
* @param prefix ContentName of the prefix for returned names
* @param n ArrayList<ContentNames> of children returned by enumeration.
*/
public int handleNameEnumerator(ContentName prefix, ArrayList<ContentName> n) {
if (Log.getLevel()==Level.FINE) {
Log.fine("got a callback! Here are the returned names: ");
for (ContentName cn : n) {
if (!prefix.equals(ContentName.ROOT))
Log.fine(cn.toString() + " (" + prefix.toString() + cn.toString() + ")");
else
Log.fine(cn.toString() + " (" + cn.toString() + ")");
}
}
AddChildren adder = new AddChildren(this, n, prefix);
Thread t = new Thread(adder);
t.start();
return 0;
}
/**
* Method to register a prefix for name enumeration with CCNNameEnumerator
*
* @param prefix String representation of the name to enumerate
*/
public void registerPrefix(String prefix) {
Log.fine("registering prefix: " + prefix);
try {
currentPrefix = ContentName.fromURI(prefix);
_nameEnumerator.registerPrefix(currentPrefix);
} catch (IOException e) {
Log.warning("error registering prefix");
Log.warningStackTrace(e);
} catch (MalformedContentNameStringException e) {
Log.warning("error with prefix string :" + prefix);
Log.warningStackTrace(e);
}
}
/**
* Method to get an instance of a CCNHandle and CCNNameEnumerator.
*
* @return void
*/
private void setupNameEnumerator() {
try {
_handle = CCNHandle.open();
_nameEnumerator = new CCNNameEnumerator(_handle, this);
} catch (ConfigurationException e) {
Log.warningStackTrace(e);
} catch (IOException e) {
Log.warningStackTrace(e);
}
}
/**
* Method to get the scaled images for displaying in the GUI.
*
* @param srcImg
* @param w
* @param h
* @return
*/
private static Image getScaledImage(Image srcImg, int w, int h) {
BufferedImage resizedImg = new BufferedImage(w, h, BufferedImage.BITMASK);
Graphics2D g2 = resizedImg.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(srcImg, 0, 0, w, h, null);
g2.dispose();
return resizedImg;
}
/**
* Experimental code for access control GUI.
*
* @param e ActionEvent
* @return void
*/
public void actionPerformed(ActionEvent e) {
if (openACL == e.getSource()) {
Log.fine("Path is " + selectedPrefix);
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
ACLManager dialog = new ACLManager(selectedPrefix, gacm);
if (dialog.hasACL()) dialog.setVisible(true);
else {
dialog.setVisible(false);
dialog.dispose();
}
} catch (Exception e) {
Log.warningStackTrace(e);
}
}
});
} else if (openGroup == e.getSource()) {
Log.fine("Path is " + selectedPrefix);
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
GroupManagerGUI dialog = new GroupManagerGUI(selectedPrefix, gacm);
dialog.setVisible(true);
} catch (Exception e) {
Log.warningStackTrace(e);
}
}
});
}
}
/**
* Method to return the CCNNameEnumerator for the ContentExplorer application
*
* @return CCNNameEnumerator
*/
public CCNNameEnumerator getNameEnumerator() {
return _nameEnumerator;
}
/**
* Method to check for CCN Plugin for VLC. Returns true if the ccn plugin is
* installed for VLC. If it is not found, the "Play File" option is disabled
* for files.
*
* Currently not tested on non-linux platforms.
*
* @return boolean True if the text ccn is found in the vlc --list output.
*/
public boolean checkVLCsupport() {
InputStream output = null;
//InputStream stderr = null;
boolean check = false;
String[] cmd = {"/bin/sh", "-c", "vlc --list | grep ccn"};
try {
Process p = Runtime.getRuntime().exec(cmd);
output = p.getInputStream();
//stderr = p.getErrorStream();
String line = null;
BufferedReader brCleanUp = new BufferedReader (new InputStreamReader (output));
while ((line = brCleanUp.readLine ()) != null) {
//Log.fine ("[Stdout] " + line);
if(line.toLowerCase().contains("ccn")) {
check = true;
Log.fine("ContentExplorer found CCN VLC plugin, enabling play option");
}
}
brCleanUp.close();
//brCleanUp = new BufferedReader (new InputStreamReader (stderr));
//while ((line = brCleanUp.readLine ()) != null) {
//}
//brCleanUp.close();
} catch (IOException e) {
Log.warning("ContentExplorer could not check for CCN VLC plugin, disabling play file option");
Log.logException("Error checking for VLC CCN plugin", e);
}
return check;
}
public static void setRoot(ContentName r) {
root = r;
}
public static void setAccessControl(boolean ac) {
accessControlOn = ac;
}
public static void setShowVersions(boolean sv) {
showVersions = sv;
}
public static void setDebugMode(boolean dm) {
debugMode = dm;
}
public static void setGroupAccessControlManager(GroupAccessControlManager acm) {
gacm = acm;
}
public static void setUsername(String name) {
userName = name;
}
public static void setPreviewTextfiles(boolean ptf) {
previewTextFiles = ptf;
}
}