1 package com.melloware.jukes.gui.view.dialogs;
2
3 import java.awt.BorderLayout;
4 import java.awt.EventQueue;
5 import java.awt.Frame;
6 import java.awt.event.ActionEvent;
7 import java.io.File;
8 import java.io.IOException;
9 import java.util.ArrayList;
10 import java.util.Collection;
11 import java.util.Enumeration;
12 import java.util.HashMap;
13 import java.util.Iterator;
14 import java.util.Map;
15
16 import javax.swing.AbstractAction;
17 import javax.swing.Action;
18 import javax.swing.DefaultListModel;
19 import javax.swing.JButton;
20 import javax.swing.JComponent;
21 import javax.swing.JFileChooser;
22 import javax.swing.JLabel;
23 import javax.swing.JList;
24 import javax.swing.JPanel;
25 import javax.swing.JScrollPane;
26 import javax.swing.JTextField;
27 import javax.swing.ListSelectionModel;
28 import javax.swing.text.JTextComponent;
29
30 import org.apache.commons.io.FileUtils;
31 import org.apache.commons.io.filefilter.DirectoryFileFilter;
32 import org.apache.commons.logging.Log;
33 import org.apache.commons.logging.LogFactory;
34
35 import com.jgoodies.forms.builder.PanelBuilder;
36 import com.jgoodies.forms.factories.Borders;
37 import com.jgoodies.forms.factories.ButtonBarFactory;
38 import com.jgoodies.forms.layout.CellConstraints;
39 import com.jgoodies.forms.layout.FormLayout;
40 import com.jgoodies.uif.AbstractDialog;
41 import com.jgoodies.uif.action.ActionManager;
42 import com.jgoodies.uif.util.ResourceUtils;
43 import com.jgoodies.uif.util.Worker;
44 import com.jgoodies.uifextras.panel.HeaderPanel;
45 import com.jgoodies.validation.Severity;
46 import com.melloware.jukes.db.HibernateDao;
47 import com.melloware.jukes.db.HibernateUtil;
48 import com.melloware.jukes.exception.InfrastructureException;
49 import com.melloware.jukes.file.MusicDirectory;
50 import com.melloware.jukes.file.filter.FilterFactory;
51 import com.melloware.jukes.gui.tool.Actions;
52 import com.melloware.jukes.gui.tool.Resources;
53 import com.melloware.jukes.gui.tool.Settings;
54 import com.melloware.jukes.gui.view.component.ComponentFactory;
55 import com.melloware.jukes.gui.view.component.MessageCellRenderer;
56 import com.melloware.jukes.util.GuiUtil;
57 import com.melloware.jukes.util.JukesValidationMessage;
58 import com.melloware.jukes.util.MessageUtil;
59
60
61
62
63
64
65
66
67
68
69
70 @SuppressWarnings("unchecked")
71 public final class DiscFindDialog extends AbstractDialog {
72
73 private static final Log LOG = LogFactory.getLog(DiscFindDialog.class);
74 private static final String HQL_DISC_LOCATIONS = ResourceUtils.getString("hql.disc.locations");
75 private final Map mapDiscs = new HashMap();
76 private DefaultListModel listModel;
77 private JButton buttonSave;
78 private JButton buttonApply;
79 private JButton buttonCancel;
80 private JButton buttonClose;
81 private JComponent buttonBar;
82 private JList list;
83 private JPanel panel;
84 private JLabel currentDirectory;
85 private JTextComponent directory;
86 private final Settings settings;
87 private Worker worker;
88 private static boolean closeDialog = false;
89
90
91
92
93
94 public DiscFindDialog(Frame owner, Settings settings) {
95 super(owner);
96 LOG.debug("Disc Finder created.");
97 this.settings = settings;
98
99
100 HibernateUtil.setCompact(true);
101
102
103 loadAllDiscs();
104 }
105
106 public DiscFindDialog(Frame owner, Settings settings, boolean startFinder) {
107 super(owner);
108 LOG.debug("Disc Finder created.");
109 DiscFindDialog.closeDialog = startFinder;
110 this.settings = settings;
111
112
113 HibernateUtil.setCompact(true);
114
115
116 loadAllDiscs();
117
118 if (startFinder) {
119 this.build();
120 doApply();
121 }
122 }
123
124
125
126
127
128 public void doApply() {
129 LOG.debug("Apply pressed.");
130 buttonCancel.setEnabled(true);
131 buttonApply.setEnabled(false);
132 buttonClose.setEnabled(false);
133 buttonSave.setEnabled(false);
134 GuiUtil.setBusyCursor(this, true);
135 LOG.info("[START] Disc Finder");
136
137
138
139
140
141
142
143 worker = new Worker() {
144 public Object construct() {
145 return doWork();
146 }
147
148 public void finished() {
149 if (closeDialog) {
150 ActionManager.get(Actions.REFRESH_ID).actionPerformed(null);
151 LOG.info("[CLOSE] Disc Finder");
152 dispose();
153 } else {
154 threadFinished(get());
155 }
156 }
157 };
158 worker.start();
159
160 }
161
162
163
164
165
166 public void doCancel() {
167 LOG.debug("Cancel Pressed.");
168 if (worker != null) {
169 worker.interrupt();
170 }
171 buttonCancel.setEnabled(false);
172 buttonApply.setEnabled(true);
173 buttonClose.setEnabled(true);
174 buttonSave.setEnabled(true);
175 }
176
177
178
179
180
181 protected JComponent buildContent() {
182 final JPanel content = new JPanel(new BorderLayout());
183 content.add(buildMainPanel(), BorderLayout.CENTER);
184 content.add(buttonBar, BorderLayout.SOUTH);
185 return content;
186 }
187
188
189
190
191
192 protected JComponent buildHeader() {
193 return new HeaderPanel("Disc Finder ", "Pick a root directory and this will attempt to add all discs found\n"
194 + "under the directory specified Please be patient this could take a long time.\n",
195 Resources.DISC_FINDER_ICON);
196 }
197
198
199
200
201
202 protected JComponent buildMainPanel() {
203 final JButton[] buttons = new JButton[4];
204 final JButton button = createApplyButton();
205 button.setText("Find");
206 final Action export = new AbstractAction("Save Error Report", Resources.FILE_TEXT_ICON) {
207
208 public void actionPerformed(ActionEvent evt) {
209 saveErrorReport(evt);
210 }
211 };
212 buttonSave = new JButton(export);
213 buttonCancel = createCancelButton();
214 buttonApply = button;
215 buttonClose = createCloseButton(true);
216 buttonCancel.setEnabled(false);
217 buttons[0] = buttonApply;
218 buttons[1] = buttonCancel;
219 buttons[2] = buttonSave;
220 buttons[3] = buttonClose;
221 buttonBar = ButtonBarFactory.buildRightAlignedBar(buttons);
222 final FormLayout layout = new FormLayout("fill:pref:grow", "p, p, p, p");
223 final PanelBuilder builder = new PanelBuilder(layout);
224 builder.setDefaultDialogBorder();
225 final CellConstraints cc = new CellConstraints();
226 int row = 1;
227 builder.addSeparator("Find", cc.xy(1, row++));
228 builder.add(buildDirectoryPanel(), cc.xy(1, row++));
229 builder.add(buildListPanel(), cc.xy(1, row));
230 panel = builder.getPanel();
231 panel.setBorder(Borders.DIALOG_BORDER);
232 return panel;
233 }
234
235
236
237
238
239
240 protected void saveErrorReport(final ActionEvent aEvt) {
241 LOG.debug("Save Error Report pressed.");
242 final JFileChooser chooser = new JFileChooser();
243 chooser.setDialogTitle("Save Report");
244
245 chooser.setFileFilter(FilterFactory.textFileFilter());
246 chooser.setMultiSelectionEnabled(false);
247 chooser.setFileHidingEnabled(true);
248 final int returnVal = chooser.showSaveDialog(this);
249 if (returnVal != JFileChooser.APPROVE_OPTION) {
250 return;
251 }
252 File file = chooser.getSelectedFile();
253 if (LOG.isDebugEnabled()) {
254 LOG.debug("Absolute: " + file.getAbsolutePath());
255 }
256
257
258 file = FilterFactory.forceTextExtension(file);
259
260 try {
261
262 final ArrayList results = new ArrayList();
263 final Enumeration enumeration = listModel.elements();
264 while (enumeration.hasMoreElements()) {
265 final JukesValidationMessage message = (JukesValidationMessage) enumeration.nextElement();
266 if ((message.severity() == Severity.ERROR) || (message.severity() == Severity.WARNING)) {
267
268 results.add("DIR: " + message.formattedText());
269
270 results.add(message.severity().toString().toUpperCase() + ": " + message.getToolTip());
271
272 results.add(" ");
273 }
274 }
275
276 FileUtils.writeLines(file, null, results);
277
278 MessageUtil.showInformation(this, "Report saved successfully.");
279 } catch (IOException ex) {
280 LOG.error("Error writing file: \n\n" + ex.getMessage(), ex);
281 } catch (InfrastructureException ex) {
282 LOG.error("Error writing file: \n\n" + ex.getMessage(), ex);
283 } catch (Exception ex) {
284 LOG.error("Unexpected error writing file.", ex);
285 }
286 }
287
288
289
290
291
292 protected void doCloseWindow() {
293 super.doClose();
294 }
295
296
297
298
299
300
301 private JComponent buildDirectoryPanel() {
302 directory = new JTextField();
303 currentDirectory = new JLabel();
304 ((JTextField) directory).setColumns(50);
305 directory.setText(this.settings.getStartInDirectory().getAbsolutePath());
306 final FormLayout layout = new FormLayout(
307 "right:max(14dlu;pref), 4dlu, left:min(60dlu;pref):grow, pref, 40dlu, ,pref, pref", "p, 4px, p, 4px");
308
309
310
311
312
313 final PanelBuilder builder = new PanelBuilder(layout);
314 final CellConstraints cc = new CellConstraints();
315
316 builder.addLabel("Search Directory:", cc.xy(1, 1));
317 builder.add(directory, cc.xyw(3, 1, 3));
318 builder.add(ComponentFactory.createDirectoryChooserButton(directory), cc.xy(6, 1));
319 builder.addLabel("Processing:", cc.xy(1, 3));
320 builder.add(currentDirectory, cc.xyw(3, 3, 3));
321
322 return builder.getPanel();
323 }
324
325
326
327
328
329
330 private JComponent buildListPanel() {
331 listModel = new DefaultListModel();
332
333 list = new JList(listModel);
334 list.setFocusable(false);
335 list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
336 list.setSelectedIndex(0);
337 list.setCellRenderer(new MessageCellRenderer());
338 list.setVisibleRowCount(19);
339 return new JScrollPane(list);
340 }
341
342
343
344
345
346 private Object doWork() {
347 Object result = null;
348 try {
349
350 listModel.removeAllElements();
351
352
353 final String directory = this.directory.getText();
354 final File dir = new File(directory);
355
356 if ((!dir.isDirectory()) || (!dir.exists())) {
357 LOG.error("Please select a valid directory");
358 throw new InterruptedException();
359 }
360
361 recurseDirectories(dir);
362
363 if (Thread.interrupted()) {
364 LOG.debug("Thread interrupted.");
365 throw new InterruptedException();
366 }
367
368 } catch (InterruptedException e) {
369 return result;
370 }
371 return result;
372 }
373
374
375
376
377
378
379
380
381 private boolean hasDiscAlready(final String aDirectory) {
382 return mapDiscs.containsKey(aDirectory);
383 }
384
385
386
387
388 private void loadAllDiscs() {
389 final Collection discs = HibernateDao.findByQuery(HQL_DISC_LOCATIONS);
390
391 for (final Iterator iter = discs.iterator(); iter.hasNext();) {
392 final Object queryResult = (Object) iter.next();
393 mapDiscs.put(queryResult, queryResult);
394 }
395 }
396
397
398
399
400
401
402
403
404
405 private void recurseDirectories(final File aDirectory) throws InterruptedException {
406 final String[] files = aDirectory.list(DirectoryFileFilter.INSTANCE);
407
408 if (Thread.interrupted()) {
409 LOG.debug("Thread interrupted.");
410 throw new InterruptedException();
411 }
412
413
414 if (files.length > 0) {
415 for (int i = 0; i < files.length; i++) {
416 final File f = new File(aDirectory, files[i]);
417 if (f.isDirectory()) {
418 recurseDirectories(f);
419 }
420 }
421 } else {
422
423 updateProcessing(aDirectory.getAbsolutePath());
424 if (hasDiscAlready(aDirectory.getAbsolutePath())) {
425 return;
426 }
427 final JukesValidationMessage message = MusicDirectory.loadDiscFromDirectory(aDirectory, this.settings
428 .isUpdateTags());
429 if (message.getSeverity() == Severity.OK) {
430 message.setMessage(aDirectory.getAbsolutePath());
431 } else if (message.getSeverity() == Severity.WARNING) {
432 message.setMessage(aDirectory.getAbsolutePath());
433 } else {
434 message.setMessage("ERROR " + aDirectory.getAbsolutePath());
435 }
436 updateList(message);
437 }
438 }
439
440
441
442
443
444
445 private void threadFinished(Object result) {
446 if (LOG.isDebugEnabled()) {
447 LOG.debug("Thread Finished");
448 LOG.debug(result);
449 }
450 GuiUtil.setBusyCursor(this, false);
451 buttonCancel.setEnabled(false);
452 buttonApply.setEnabled(true);
453 buttonClose.setEnabled(true);
454 buttonSave.setEnabled(true);
455
456
457 ActionManager.get(Actions.REFRESH_ID).actionPerformed(null);
458 LOG.info("[STOP] Disc Finder");
459 }
460
461
462
463
464
465
466 private void updateList(final JukesValidationMessage aMessage) {
467 final Runnable updateList = new Runnable() {
468 public void run() {
469 listModel.addElement(aMessage);
470 final int index = listModel.indexOf(aMessage);
471 list.setSelectedIndex(index);
472 list.ensureIndexIsVisible(index);
473 }
474 };
475 EventQueue.invokeLater(updateList);
476 }
477
478
479
480
481
482
483 private void updateProcessing(final String aDirectory) {
484 final Runnable updateList = new Runnable() {
485 public void run() {
486 currentDirectory.setText(aDirectory);
487 }
488 };
489 EventQueue.invokeLater(updateList);
490 }
491
492 }