1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
66
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
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
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
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
352 hash = createSHA1String(file);
353 }
354 catch (Exception e) {
355 logger.debug("Error while calculating hash", e);
356 return;
357 }
358
359
360 if (libraryFile != null) {
361 libraryFile.put(SearchResult.URN, hash);
362 }
363 }
364 }
365
366
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
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);
409 Thread.sleep(interval*2);
410 }
411 } finally {
412 fis.close();
413 }
414
415 byte[] sha1 = md.digest();
416
417
418
419
420
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 }