Adding features to "History"

Table-based view.
Tracks # of images in a rip, and dates.
Sortable.
Double-clicking URL allows copying the URL to clipboard.
Can selectively re-rip only albums that are 'checked'
'Checked' albums are persisted when RipMe is closed & reopened.

For #147 (selective re-ripping)
This commit is contained in:
4pr0n 2015-01-11 04:31:30 -08:00
parent 066b0e9f87
commit dd55cd1b43
3 changed files with 167 additions and 42 deletions

View File

@ -35,7 +35,7 @@ public abstract class AlbumRipper extends AbstractRipper {
@Override @Override
public int getCount() { public int getCount() {
return itemsCompleted.size(); return itemsCompleted.size() + itemsErrored.size();
} }
public boolean addURLToDownload(URL url, File saveAs, String referrer, Map<String,String> cookies) { public boolean addURLToDownload(URL url, File saveAs, String referrer, Map<String,String> cookies) {

View File

@ -5,7 +5,9 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
import java.util.List; import java.util.List;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
@ -15,6 +17,13 @@ import org.json.JSONObject;
public class History { public class History {
private List<HistoryEntry> list = new ArrayList<HistoryEntry>(); private List<HistoryEntry> list = new ArrayList<HistoryEntry>();
private static final String[] COLUMNS = new String[] {
"URL",
"created",
"modified",
"#",
""
};
public void add(HistoryEntry entry) { public void add(HistoryEntry entry) {
list.add(entry); list.add(entry);
@ -22,9 +31,60 @@ public class History {
public void remove(HistoryEntry entry) { public void remove(HistoryEntry entry) {
list.remove(entry); list.remove(entry);
} }
public void remove(int index) {
list.remove(index);
}
public void clear() { public void clear() {
list.clear(); list.clear();
} }
public HistoryEntry get(int index) {
return list.get(index);
}
public String getColumnName(int index) {
return COLUMNS[index];
}
public int getColumnCount() {
return COLUMNS.length;
}
public Object getValueAt(int row, int col) {
HistoryEntry entry = this.list.get(row);
switch (col) {
case 0:
return entry.url;
case 1:
return dateToHumanReadable(entry.startDate);
case 2:
return dateToHumanReadable(entry.modifiedDate);
case 3:
return entry.count;
case 4:
return entry.selected;
default:
return null;
}
}
private String dateToHumanReadable(Date date) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
return sdf.format(date);
}
public boolean containsURL(String url) {
for (HistoryEntry entry : this.list) {
if (entry.url.equals(url)) {
return true;
}
}
return false;
}
public HistoryEntry getEntryByURL(String url) {
for (HistoryEntry entry : this.list) {
if (entry.url.equals(url)) {
return entry;
}
}
throw new RuntimeException("Could not find URL " + url + " in History");
}
public void fromJSON(JSONArray jsonArray) { public void fromJSON(JSONArray jsonArray) {
JSONObject json; JSONObject json;

View File

@ -43,6 +43,7 @@ import javax.swing.JOptionPane;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.JProgressBar; import javax.swing.JProgressBar;
import javax.swing.JScrollPane; import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField; import javax.swing.JTextField;
import javax.swing.JTextPane; import javax.swing.JTextPane;
import javax.swing.ListSelectionModel; import javax.swing.ListSelectionModel;
@ -53,6 +54,7 @@ import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener; import javax.swing.event.DocumentListener;
import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener; import javax.swing.event.ListDataListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.text.BadLocationException; import javax.swing.text.BadLocationException;
import javax.swing.text.SimpleAttributeSet; import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants; import javax.swing.text.StyleConstants;
@ -89,10 +91,11 @@ public class MainWindow implements Runnable, RipStatusHandler {
// History // History
private static JButton optionHistory; private static JButton optionHistory;
private static final History HISTORY = new History();
private static JPanel historyPanel; private static JPanel historyPanel;
private static JList historyList; private static JTable historyTable;
private static DefaultListModel historyListModel; private static AbstractTableModel historyTableModel;
private static JScrollPane historyListScroll; private static JScrollPane historyTableScrollPane;
private static JPanel historyButtonPanel; private static JPanel historyButtonPanel;
private static JButton historyButtonRemove, private static JButton historyButtonRemove,
historyButtonClear, historyButtonClear,
@ -305,20 +308,66 @@ public class MainWindow implements Runnable, RipStatusHandler {
historyPanel.setBorder(emptyBorder); historyPanel.setBorder(emptyBorder);
historyPanel.setVisible(false); historyPanel.setVisible(false);
historyPanel.setPreferredSize(new Dimension(300, 250)); historyPanel.setPreferredSize(new Dimension(300, 250));
historyListModel = new DefaultListModel(); historyTableModel = new AbstractTableModel() {
historyList = new JList(historyListModel); private static final long serialVersionUID = 1L;
historyList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); @Override
historyListScroll = new JScrollPane(historyList, public String getColumnName(int col) {
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, return HISTORY.getColumnName(col);
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); }
public Class<? extends Object> getColumnClass(int c) {
return getValueAt(0, c).getClass();
}
@Override
public Object getValueAt(int row, int col) {
return HISTORY.getValueAt(row, col);
}
@Override
public int getRowCount() {
return HISTORY.toList().size();
}
@Override
public int getColumnCount() {
return HISTORY.getColumnCount();
}
@Override
public boolean isCellEditable(int row, int col) {
return (col == 0 || col == 4);
}
@Override
public void setValueAt(Object value, int row, int col) {
if (col == 4) {
HISTORY.get(row).selected = (Boolean) value;
historyTableModel.fireTableDataChanged();
}
}
};
historyTable = new JTable(historyTableModel);
historyTable.setAutoCreateRowSorter(true);
for (int i = 0; i < historyTable.getColumnModel().getColumnCount(); i++) {
int width = 130; // Default
switch (i) {
case 0: // URL
width = 270;
break;
case 3:
width = 40;
break;
case 4:
width = 15;
break;
}
historyTable.getColumnModel().getColumn(i).setPreferredWidth(width);
}
historyTableScrollPane = new JScrollPane(historyTable);
historyButtonRemove = new JButton("Remove"); historyButtonRemove = new JButton("Remove");
historyButtonClear = new JButton("Clear"); historyButtonClear = new JButton("Clear");
historyButtonRerip = new JButton("Re-rip All"); historyButtonRerip = new JButton("Re-rip Checked");
gbc.gridx = 0; gbc.gridx = 0;
JPanel historyListPanel = new JPanel(new GridBagLayout()); // History List Panel
historyListPanel.add(historyListScroll, gbc); JPanel historyTablePanel = new JPanel(new GridBagLayout());
historyTablePanel.add(historyTableScrollPane, gbc);
gbc.ipady = 180; gbc.ipady = 180;
historyPanel.add(historyListPanel, gbc); historyPanel.add(historyTablePanel, gbc);
gbc.ipady = 0; gbc.ipady = 0;
historyButtonPanel = new JPanel(new GridBagLayout()); historyButtonPanel = new JPanel(new GridBagLayout());
historyButtonPanel.setPreferredSize(new Dimension(300, 10)); historyButtonPanel.setPreferredSize(new Dimension(300, 10));
@ -531,17 +580,24 @@ public class MainWindow implements Runnable, RipStatusHandler {
historyButtonRemove.addActionListener(new ActionListener() { historyButtonRemove.addActionListener(new ActionListener() {
@Override @Override
public void actionPerformed(ActionEvent event) { public void actionPerformed(ActionEvent event) {
int[] indices = historyList.getSelectedIndices(); int[] indices = historyTable.getSelectedRows();
for (int i = indices.length - 1; i >= 0; i--) { for (int i = indices.length - 1; i >= 0; i--) {
historyListModel.remove(indices[i]); int modelIndex = historyTable.convertRowIndexToModel(indices[i]);
HISTORY.remove(modelIndex);
} }
try {
historyTableModel.fireTableDataChanged();
} catch (Exception e) { }
saveHistory(); saveHistory();
} }
}); });
historyButtonClear.addActionListener(new ActionListener() { historyButtonClear.addActionListener(new ActionListener() {
@Override @Override
public void actionPerformed(ActionEvent event) { public void actionPerformed(ActionEvent event) {
historyListModel.clear(); HISTORY.clear();
try {
historyTableModel.fireTableDataChanged();
} catch (Exception e) { }
saveHistory(); saveHistory();
} }
}); });
@ -550,11 +606,29 @@ public class MainWindow implements Runnable, RipStatusHandler {
historyButtonRerip.addActionListener(new ActionListener() { historyButtonRerip.addActionListener(new ActionListener() {
@Override @Override
public void actionPerformed(ActionEvent event) { public void actionPerformed(ActionEvent event) {
for (int i = 0; i < historyListModel.size(); i++) { if (HISTORY.toList().size() == 0) {
HistoryEntry entry = (HistoryEntry) historyListModel.get(i); JOptionPane.showMessageDialog(null,
"There are no history entries to re-rip. Rip some albums first",
"RipMe Error",
JOptionPane.ERROR_MESSAGE);
return;
}
int added = 0;
for (HistoryEntry entry : HISTORY.toList()) {
if (entry.selected) {
added++;
queueListModel.addElement(entry.url); queueListModel.addElement(entry.url);
} }
} }
if (added == 0) {
JOptionPane.showMessageDialog(null,
"No history entries have been 'Checked'\n" +
"Check an entry by clicking the checkbox to the right of the URL",
"RipMe Error",
JOptionPane.ERROR_MESSAGE);
return;
}
}
}); });
configUpdateButton.addActionListener(new ActionListener() { configUpdateButton.addActionListener(new ActionListener() {
@Override @Override
@ -794,32 +868,25 @@ public class MainWindow implements Runnable, RipStatusHandler {
} }
private void loadHistory() { private void loadHistory() {
History history = new History();
File historyFile = new File("history.json"); File historyFile = new File("history.json");
HISTORY.clear();
if (historyFile.exists()) { if (historyFile.exists()) {
try { try {
logger.info("Loading history from history.json"); logger.info("Loading history from history.json");
history.fromFile("history.json"); HISTORY.fromFile("history.json");
} catch (IOException e) { } catch (IOException e) {
logger.error("Failed to load history from file history.json", e); logger.error("Failed to load history from file " + historyFile, e);
} }
} }
else { else {
logger.info("Loading history from configuration"); logger.info("Loading history from configuration");
history.fromList(Utils.getConfigList("download.history")); HISTORY.fromList(Utils.getConfigList("download.history"));
}
for (HistoryEntry entry : history.toList()) {
historyListModel.addElement(entry);
} }
} }
private void saveHistory() { private void saveHistory() {
History history = new History();
for (int i = 0; i < historyListModel.size(); i++) {
history.add( (HistoryEntry) historyListModel.get(i) );
}
try { try {
history.toFile("history.json"); HISTORY.toFile("history.json");
} catch (IOException e) { } catch (IOException e) {
logger.error("Failed to save history to file history.json", e); logger.error("Failed to save history to file history.json", e);
} }
@ -977,18 +1044,15 @@ public class MainWindow implements Runnable, RipStatusHandler {
break; break;
case RIP_COMPLETE: case RIP_COMPLETE:
boolean alreadyInHistory = false;
RipStatusComplete rsc = (RipStatusComplete) msg.getObject(); RipStatusComplete rsc = (RipStatusComplete) msg.getObject();
String url = ripper.getURL().toExternalForm(); String url = ripper.getURL().toExternalForm();
for (int i = 0; i < historyListModel.size(); i++) { if (HISTORY.containsURL(url)) {
HistoryEntry entry = (HistoryEntry) historyListModel.get(i); // TODO update "modifiedDate" of entry in HISTORY
if (entry.url.equals(url)) { HistoryEntry entry = HISTORY.getEntryByURL(url);
alreadyInHistory = true; entry.count = rsc.count;
entry.modifiedDate = new Date(); entry.modifiedDate = new Date();
break;
} }
} else {
if (!alreadyInHistory) {
HistoryEntry entry = new HistoryEntry(); HistoryEntry entry = new HistoryEntry();
entry.url = url; entry.url = url;
entry.dir = rsc.getDir(); entry.dir = rsc.getDir();
@ -996,7 +1060,8 @@ public class MainWindow implements Runnable, RipStatusHandler {
try { try {
entry.title = ripper.getAlbumTitle(ripper.getURL()); entry.title = ripper.getAlbumTitle(ripper.getURL());
} catch (MalformedURLException e) { } } catch (MalformedURLException e) { }
historyListModel.addElement(entry); HISTORY.add(entry);
historyTableModel.fireTableDataChanged();
} }
if (configPlaySound.isSelected()) { if (configPlaySound.isSelected()) {
Utils.playSound("camera.wav"); Utils.playSound("camera.wav");