/* * Copyright (c) 2014 tabletoptool.com team. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/gpl.html * * Contributors: * rptools.com team - initial implementation * tabletoptool.com team - further development */ package com.t3.client.ui; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URL; import java.text.NumberFormat; import java.util.ArrayList; import java.util.List; import javax.swing.AbstractListModel; import javax.swing.DefaultListModel; import javax.swing.JButton; import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JTabbedPane; import javax.swing.JTextField; import javax.swing.ListModel; import javax.swing.ListSelectionModel; import javax.swing.SwingWorker; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import com.jidesoft.swing.FolderChooser; import com.t3.client.AppPreferences; import com.t3.client.AppSetup; import com.t3.client.RemoteFileDownloader; import com.t3.client.TabletopTool; import com.t3.client.WebDownloader; import com.t3.client.swing.AbeillePanel; import com.t3.client.swing.GenericDialog; import com.t3.language.I18N; import com.t3.persistence.FileUtil; public class AddResourceDialog extends AbeillePanel<AddResourceDialog.Model> { private static final Logger log = Logger.getLogger(AddResourceDialog.class); private static final String LIBRARY_URL = "http://library.tabletoptool.com/legacy"; private static final String LIBRARY_LIST_URL = LIBRARY_URL + "/listArtPacks"; public enum Tab { LOCAL, WEB, TABLETOPTOOL_SITE } private GenericDialog dialog; private Model model; private boolean downloadLibraryListInitiated; private boolean install = false; public AddResourceDialog() { super("com/t3/client/ui/forms/addResourcesDialog.xml"); setPreferredSize(new Dimension(550, 300)); panelInit(); } public boolean getInstall() { return install; } public void showDialog() { dialog = new GenericDialog("Add Resource to Library", TabletopTool.getFrame(), this); model = new Model(); bind(model); getRootPane().setDefaultButton(getInstallButton()); dialog.showDialog(); } @Override public Model getModel() { return model; } public JButton getInstallButton() { return (JButton) getComponent("installButton"); } public JTextField getBrowseTextField() { return (JTextField) getComponent("@localDirectory"); } public JList<LibraryRow> getLibraryList() { return (JList<LibraryRow>) getComponent("@tabletoptoolList"); } public void initLibraryList() { JList list = getLibraryList(); list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); list.setModel(new MessageListModel(I18N.getText("dialog.addresource.downloading"))); } public void initTabPane() { final JTabbedPane tabPane = (JTabbedPane) getComponent("tabPane"); tabPane.getModel().addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { // Hmmm, this is fragile (breaks if the order changes) rethink this later switch (tabPane.getSelectedIndex()) { case 0: model.tab = Tab.LOCAL; break; case 1: model.tab = Tab.WEB; break; case 2: model.tab = Tab.TABLETOPTOOL_SITE; downloadLibraryList(); break; } } }); } public void initLocalDirectoryButton() { final JButton button = (JButton) getComponent("localDirectoryButton"); button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { FolderChooser folderChooser = new FolderChooser(); folderChooser.setCurrentDirectory(TabletopTool.getFrame().getLoadFileChooser().getCurrentDirectory()); folderChooser.setRecentListVisible(false); folderChooser.setFileHidingEnabled(true); folderChooser.setDialogTitle(I18N.getText("msg.title.loadAssetTree")); int result = folderChooser.showOpenDialog(button.getTopLevelAncestor()); if (result == FolderChooser.APPROVE_OPTION) { File root = folderChooser.getSelectedFolder(); getBrowseTextField().setText(root.getAbsolutePath()); } } }); } public void initInstallButton() { JButton button = (JButton) getComponent("installButton"); button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { install = true; if (commit()) { close(); } } }); } public void initCancelButton() { JButton button = (JButton) getComponent("cancelButton"); button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { close(); } }); } private void downloadLibraryList() { if (downloadLibraryListInitiated) { return; } // This pattern is safe because it is only called on the EDT downloadLibraryListInitiated = true; new SwingWorker<Object, Object>() { ListModel model; @Override protected Object doInBackground() throws Exception { String result = null; try { WebDownloader downloader = new WebDownloader(new URL(LIBRARY_LIST_URL)); result = downloader.read(); } finally { if (result == null) { model = new MessageListModel(I18N.getText("dialog.addresource.errorDownloading")); return null; } } DefaultListModel<LibraryRow> listModel = new DefaultListModel<LibraryRow>(); // Create a list to compare against for dups List<String> libraryNameList = new ArrayList<String>(); for (File file : AppPreferences.getAssetRoots()) { libraryNameList.add(file.getName()); } // Generate the list try { BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(result.getBytes()))); String line = null; while ((line = reader.readLine()) != null) { LibraryRow row = new LibraryRow(line); // Don't include if we've already got it if (libraryNameList.contains(row.name)) { continue; } listModel.addElement(row); } model = listModel; } catch (Throwable t) { log.error("unable to parse library list", t); model = new MessageListModel(I18N.getText("dialog.addresource.errorDownloading")); } return null; } @Override protected void done() { getLibraryList().setModel(model); } }.execute(); } @Override public boolean commit() { if (!super.commit()) { return false; } // Add the resource final List<LibraryRow> rowList = new ArrayList<LibraryRow>(); switch (model.getTab()) { case LOCAL: if (StringUtils.isEmpty(model.getLocalDirectory())) { TabletopTool.showMessage("dialog.addresource.warn.filenotfound", "Error", JOptionPane.ERROR_MESSAGE, model.getLocalDirectory()); return false; } File root = new File(model.getLocalDirectory()); if (!root.exists()) { TabletopTool.showMessage("dialog.addresource.warn.filenotfound", "Error", JOptionPane.ERROR_MESSAGE, model.getLocalDirectory()); return false; } if (!root.isDirectory()) { TabletopTool.showMessage("dialog.addresource.warn.directoryrequired", "Error", JOptionPane.ERROR_MESSAGE, model.getLocalDirectory()); return false; } AppSetup.installLibrary(FileUtil.getNameWithoutExtension(root), root); return true; case WEB: if (StringUtils.isEmpty(model.getUrlName())) { TabletopTool.showMessage("dialog.addresource.warn.musthavename", "Error", JOptionPane.ERROR_MESSAGE, model.getLocalDirectory()); return false; } // validate the url format so that we don't hit it later try { new URL(model.getUrl()); } catch (MalformedURLException e) { TabletopTool.showMessage("dialog.addresource.warn.invalidurl", "Error", JOptionPane.ERROR_MESSAGE, model.getUrl()); return false; } rowList.add(new LibraryRow(model.getUrlName(), model.getUrl(), -1)); break; case TABLETOPTOOL_SITE: List<LibraryRow> selectedRows = getLibraryList().getSelectedValuesList(); if (selectedRows == null || selectedRows.isEmpty()) { TabletopTool.showMessage("dialog.addresource.warn.mustselectone", "Error", JOptionPane.ERROR_MESSAGE); return false; } for (LibraryRow row : selectedRows) { //validate the url format row.path = LIBRARY_URL + "/" + row.path; try { new URL(row.path); } catch (MalformedURLException e) { TabletopTool.showMessage("dialog.addresource.warn.invalidurl", "Error", JOptionPane.ERROR_MESSAGE, row.path); return false; } rowList.add(row); } break; } new SwingWorker<Object, Object>() { @Override protected Object doInBackground() throws Exception { for (LibraryRow row : rowList) { try { RemoteFileDownloader downloader = new RemoteFileDownloader(new URL(row.path)); File tmpFile = downloader.read(); AppSetup.installLibrary(row.name, tmpFile.toURL()); tmpFile.delete(); } catch (IOException e) { log.error("Error downloading library: " + e, e); TabletopTool.showInformation("dialog.addresource.warn.couldnotload"); } } return null; } }.execute(); return true; } private void close() { unbind(); dialog.closeDialog(); } private static class LibraryRow { private final String name; private String path; private final int size; public LibraryRow(String name, String path, int size) { this.name = name.trim(); this.path = path.trim(); this.size = size; } public LibraryRow(String row) { String[] data = row.split("\\|"); name = data[0].trim(); path = data[1].trim(); size = Integer.parseInt(data[2]); } @Override public String toString() { return "<html><b>" + name + "</b> <i>(" + getSizeString() + ")</i>"; } private String getSizeString() { NumberFormat format = NumberFormat.getNumberInstance(); if (size < 1000) { return format.format(size) + " bytes"; } if (size < 1000000) { return format.format(size / 1000) + " k"; } return format.format(size / 1000000) + " mb"; } } public static class Model { private String localDirectory; private String urlName; private String url; private Tab tab = Tab.LOCAL; public String getLocalDirectory() { return localDirectory; } public void setLocalDirectory(String localDirectory) { this.localDirectory = localDirectory; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public Tab getTab() { return tab; } public void setTab(Tab tab) { this.tab = tab; } public String getUrlName() { return urlName; } public void setUrlName(String urlName) { this.urlName = urlName; } } private class MessageListModel extends AbstractListModel<String> { private final String message; public MessageListModel(String message) { this.message = message; } @Override public String getElementAt(int index) { return message; } @Override public int getSize() { return 1; } } }