View Javadoc

1   /*
2    *  XNap - A P2P framework and client.
3    *
4    *  See the file AUTHORS for copyright information.
5    *
6    *  This program is free software; you can redistribute it and/or modify
7    *  it under the terms of the GNU General Public License as published by
8    *  the Free Software Foundation.
9    *
10   *  This program is distributed in the hope that it will be useful,
11   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   *  GNU General Public License for more details.
14   *
15   *  You should have received a copy of the GNU General Public License
16   *  along with this program; if not, write to the Free Software
17   *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18   */
19  
20  package org.xnap.plugin.bitzi;
21  
22  import java.awt.Color;
23  import java.awt.event.ActionEvent;
24  import java.io.File;
25  import java.io.FileInputStream;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.security.MessageDigest;
29  import java.security.NoSuchAlgorithmException;
30  
31  import javax.swing.AbstractAction;
32  import javax.swing.Action;
33  import javax.swing.Box;
34  import javax.swing.BoxLayout;
35  import javax.swing.Icon;
36  import javax.swing.JComponent;
37  import javax.swing.JLabel;
38  import javax.swing.JPanel;
39  import javax.swing.JScrollPane;
40  import javax.swing.JSeparator;
41  import javax.swing.SwingUtilities;
42  
43  import net.n3.nanoxml.IXMLElement;
44  import net.n3.nanoxml.IXMLParser;
45  import net.n3.nanoxml.IXMLReader;
46  import net.n3.nanoxml.StdXMLReader;
47  import net.n3.nanoxml.XMLParserFactory;
48  
49  import org.apache.log4j.Logger;
50  import org.xnap.XNap;
51  import org.xnap.gui.XNapFrame;
52  import org.xnap.gui.component.DefaultDialog;
53  import org.xnap.gui.util.GUIHelper;
54  import org.xnap.gui.viewer.Viewer;
55  import org.xnap.gui.viewer.ViewerManager;
56  import org.xnap.io.Library;
57  import org.xnap.io.MetaInfoFile;
58  import org.xnap.net.HttpConnection;
59  import org.xnap.plugin.AbstractPlugin;
60  import org.xnap.search.SearchResult;
61  import org.xnap.search.SearchResultActionProvider;
62  import org.xnap.util.AbstractPluginPreferences;
63  
64  import com.bitzi.util.Base32;
65  //import com.limegroup.gnutella.UrnType;
66  //import com.limegroup.gnutella.util.IntWrapper;
67  
68  /***
69   * BitziPlugin
70   */
71  public class BitziPlugin extends AbstractPlugin 
72  	implements SearchResultActionProvider, Viewer {
73  
74      private static final Logger logger = Logger.getLogger(BitziPlugin.class);
75  	private static final String BITZI_BASE_URL = "http://ticket.bitzi.com/rdf/";
76  	
77  	private JComponent viewerComponent;
78      static BitziPreferences bitziPrefs;
79  	
80      /***
81       * @see org.xnap.plugin.Plugin#start()
82       */
83      public void start() throws Exception 
84      {
85  		// nothing to do.
86      }
87  
88      /***
89       * @see org.xnap.plugin.Plugin#startGUI()
90       */
91      public void startGUI() 
92      {
93      	bitziPrefs = new BitziPreferences();
94      	
95  		XNapFrame.getInstance().getSearchPanel().registerActionProvider(this);
96  		ViewerManager.getInstance().register(this);
97      }
98  
99      /***
100      * @see org.xnap.plugin.Plugin#stop()
101      */
102     public void stop() 
103     {
104 		// nothing to do.
105     }
106 
107     /***
108      * @see org.xnap.plugin.Plugin#stopGUI()
109      */
110     public void stopGUI() 
111     {
112 		ViewerManager.getInstance().unregister(this);
113 		XNapFrame.getInstance().getSearchPanel().deregisterActionProvider(this);
114 		BitziDialog.disposeInstance();
115 		
116 		bitziPrefs = null;
117     }
118     
119     private void lookup(SearchResult[] results) 
120     {
121 		for (int i = 0; i < results.length; i++) {
122 			logger.debug("bitzi lookup for URN: "+ results[i].get("URN"));
123 
124 			BitziItemPanel panel = new BitziItemPanel();
125 			panel.setFilename(results[i].getFilename());
126 			
127 			String hash = (String)results[i].get(SearchResult.URN);
128 			if (hash == null) {
129 				hash = (String)results[i].get(SearchResult.SHA1);
130 			}
131 
132 			LookupRunner runner = new LookupRunner(hash, panel);
133 			BitziDialog.getInstance().add(panel);
134 
135 			Thread t = new Thread(runner, "BitziLookup");
136 			t.start();
137 		}
138 		BitziDialog.getInstance().setVisible(true);	
139     }
140     /***
141      * @see org.xnap.search.SearchResultActionProvider#getActions(org.xnap.search.SearchResult)
142      */
143     public Action[] getActions(SearchResult[] results) 
144     {
145 		return new Action[] { new LookupAction(results) };
146     }
147 
148     /***
149      * @see org.xnap.gui.viewer.Viewer#getIcon()
150      */
151     public Icon getIcon() 
152     {
153         return null;
154     }
155 
156     /***
157      * @see org.xnap.gui.viewer.Viewer#getComponent()
158      */
159     public JComponent getComponent() 
160     {
161         if (viewerComponent == null) {
162         	viewerComponent = new BitziItemPanel();
163         }
164         return viewerComponent;
165     }
166 
167     /***
168      * @see org.xnap.gui.viewer.Viewer#getName()
169      */
170     public String getName() 
171     {
172         return XNap.tr("Bitzi Lookup");
173     }
174 
175     /***
176      * @see org.xnap.gui.viewer.Viewer#open(java.io.File)
177      */
178     public void open(File file) 
179     {
180     	BitziItemPanel panel = (BitziItemPanel)getComponent();
181 		
182 		panel.setFilename(file.getName());
183 
184 		LookupRunner runner = new LookupRunner(file, panel);
185 		Thread t = new Thread(runner, "BitziLookup");
186 		t.start();
187     }
188 
189     /***
190      * @see org.xnap.gui.viewer.Viewer#close()
191      */
192     public void close() 
193     {
194     }
195 
196 	private static class BitziDialog extends DefaultDialog
197 	{
198 		private JLabel content;
199 		private JPanel lookups;
200 		private static BitziDialog instance;
201 		
202 		private BitziDialog()
203 		{
204 			super(DefaultDialog.BUTTON_CLOSE, true);
205 			setTitle(XNap.tr("Bitzi Lookup"));
206 
207 			content = new JLabel();
208 			
209 			lookups = new JPanel();
210 			lookups.setLayout(new BoxLayout(lookups, BoxLayout.Y_AXIS));
211 			lookups.setBorder(GUIHelper.createEmptyBorder(10));
212 			lookups.setBackground(Color.white);
213 
214 			JScrollPane jsp = new JScrollPane(lookups);
215 			jsp.getVerticalScrollBar().setUnitIncrement(25);
216 
217 			setMainComponent(jsp);
218 			pack();	
219 		}
220 		
221 		/***
222 		 * 
223 		 */
224 		public static void disposeInstance()
225 		{
226 			if (instance != null) {		
227 				bitziPrefs.set("dialogWidth", instance.getWidth()); 
228 				bitziPrefs.set("dialogHeight", instance.getHeight());
229 				bitziPrefs.set("dialogX", instance.getX());
230 				bitziPrefs.set("dialogY", instance.getY());
231 	
232 				instance.dispose();
233 				instance = null;
234 			}
235 		}
236 
237 		public static BitziDialog getInstance()
238 		{
239 			if (instance == null) {
240 				instance = new BitziDialog();
241 				instance.setSize(bitziPrefs.getInt("dialogWidth"), 
242 							     bitziPrefs.getInt("dialogHeight"));
243 				instance.setLocation(bitziPrefs.getInt("dialogX"),
244 									 bitziPrefs.getInt("dialogY"));
245 				instance.setLocationRelativeTo(XNapFrame.getInstance());
246 			}
247 			
248 			return instance;	
249 		}
250 
251 		public void add(BitziItemPanel panel)
252 		{
253 			lookups.add(Box.createVerticalStrut(10), 0);
254 			lookups.add(new JSeparator(), 0);
255 			lookups.add(panel, 0);
256 		}
257 		
258 		public void addBitziInfo(final String filename, final IXMLElement bits)
259 		{
260 			SwingUtilities.invokeLater(new Runnable() {
261 				public void run() {
262 					add(new BitziItemPanel(filename, bits));
263 					lookups.updateUI();
264 				}
265 			});
266 		}
267 	}
268 
269 	public class LookupAction extends AbstractAction
270 	{
271 		private SearchResult[] results;
272 		
273 		public LookupAction(SearchResult[] results)
274 		{
275 			this.results = results;
276 			
277 			putValue(Action.NAME, XNap.tr("Bitzi Lookup"));
278 			putValue(Action.SHORT_DESCRIPTION, 
279 					 XNap.tr("Looks up meta data."));
280 			
281 			boolean enabled = false;
282 			for (int i = 0; i < results.length; i++) {
283 				enabled |= (results[i].get(SearchResult.URN) != null 
284 							|| results[i].get(SearchResult.SHA1) != null);
285 			}
286 			setEnabled(enabled);
287 		}
288 
289 		public void actionPerformed(ActionEvent event)
290 		{
291 			lookup(results);
292 		}
293 	}
294 	
295 	private static class BitziPreferences extends AbstractPluginPreferences {
296 		
297 		public static final int VERSION = 1;
298 
299 		public BitziPreferences()
300 		{
301 			super("plugin.bitzi", VERSION);
302 
303 			setDefault("dialogHeight", "400");
304 			setDefault("dialogWidth", "400");
305 			setDefault("dialogX", "50");
306 			setDefault("dialogY", "50");
307 		}
308 
309 	}
310 
311     private class LookupRunner implements Runnable {
312 	
313     	private BitziItemPanel monitor;
314 		private String hash;
315 		private File file;
316     	
317     	public LookupRunner(String hash, BitziItemPanel monitor)
318     	{
319     		if (hash == null) {
320     			throw new IllegalArgumentException("Hash must not be null.");
321     		}
322 
323     		this.hash = hash;
324     		this.monitor = monitor;
325     	}
326 
327     	public LookupRunner(File file, BitziItemPanel monitor)
328     	{
329     		if (file == null) {
330     			throw new IllegalArgumentException("File must not be null.");
331     		}
332     		
333     		this.file = file;
334     		this.monitor = monitor;
335     	}
336 
337     	public void run()
338     	{
339     		if (hash == null) {
340     			// lookup in library
341                 MetaInfoFile libraryFile = Library.getInstance().get(file);
342                 if (libraryFile != null) {
343                 	Object urn = libraryFile.get(SearchResult.URN);
344                 	if (urn instanceof String) {
345                 		this.hash = (String)urn;
346                 	}
347                 }
348 
349                 if (hash == null) {
350 	    			try {
351 	    				// create hash from file
352 	    				hash = createSHA1String(file);
353 	    			}
354 	    			catch (Exception e) {
355 	    				logger.debug("Error while calculating hash", e);
356 	    				return;
357 	    			}
358 	    			
359 	    			// save to library
360 	    			if (libraryFile != null) {
361 	    				libraryFile.put(SearchResult.URN, hash);
362 	    			}
363                 }
364     		}
365     		
366     		// connect to bitzi
367 			final IXMLElement element = lookup(hash);
368 			
369 			Runnable runner = new Runnable() {
370 				public void run() {
371 					monitor.setBitziInfo(element);
372 					monitor.updateUI();
373 				}
374 			};
375 			SwingUtilities.invokeLater(runner);
376     	}
377 		
378     	
379 		/***
380 		 * Create a new SHA1 hash string for the specified file on disk.
381 		 *
382 		 * @param file the file to construct the hash from
383 		 * @return the SHA1 hash string
384 		 * @throws <tt>IOException</tt> if there is an error creating the hash
385 		 *  or if the specified algorithm cannot be found
386 		 * @throws <tt>InterruptedException</tt> if the calling thread was 
387 		 *  interrupted while hashing.  (This method can take a while to
388 		 *  execute.)
389 		 */
390 		private String createSHA1String(final File file) 
391 					throws IOException, InterruptedException {
392 			FileInputStream fis = new FileInputStream(file);   		
393 			// we can only calculate SHA1 for now
394 			MessageDigest md = null;
395 			try {
396 				md = MessageDigest.getInstance("SHA");
397 			} catch(NoSuchAlgorithmException e) {
398 				throw new IOException("NO SUCH ALGORITHM");
399 			}
400 	        
401 	        try {
402 				byte[] buffer = new byte[16384];
403 				int read;
404 				while ((read=fis.read(buffer))!=-1) {
405 					long start = System.currentTimeMillis();
406 					md.update(buffer,0,read);
407 					long end = System.currentTimeMillis();
408 					long interval = Math.max(0, end-start);   //ensure non-negative
409 					Thread.sleep(interval*2);                 //throws InterruptedException 
410 				}
411 			} finally {		
412 				fis.close();
413 			}
414 	
415 			byte[] sha1 = md.digest();
416 	
417 			// preferred casing: lowercase "urn:sha1:", uppercase encoded value
418 			// note that all URNs are case-insensitive for the "urn:<type>:" part,
419 			// but some MAY be case-sensitive thereafter (SHA1/Base32 is case 
420 			// insensitive)
421 			return "urn:"+"sha1:"+Base32.encode(sha1);
422 		}
423     
424 	    private IXMLElement lookup(String hash) 
425 	    {
426 			HttpConnection conn = new HttpConnection();
427 			IXMLElement xml = null;
428 			
429 			try {
430 				conn.connect(BITZI_BASE_URL + hash);
431 				InputStream in = conn.getInputStream();
432 				IXMLParser parser = XMLParserFactory.createDefaultXMLParser();
433 				IXMLReader reader = new StdXMLReader(in);
434 				parser.setReader(reader);
435 	
436 				xml = (IXMLElement)parser.parse();
437 			} catch (Exception e) {
438 				logger.debug("Could not fetch meta data.", e);
439 			} finally {
440 				conn.close();
441 			}
442 				
443 			return xml;
444 		}
445     }
446     
447 }