diff --git a/app/src/cc/arduino/libraries/contributions/ui/ContributedLibraryTableCell.java b/app/src/cc/arduino/libraries/contributions/ui/ContributedLibraryTableCell.java new file mode 100644 index 000000000..717d1e637 --- /dev/null +++ b/app/src/cc/arduino/libraries/contributions/ui/ContributedLibraryTableCell.java @@ -0,0 +1,293 @@ +/* + * This file is part of Arduino. + * + * Copyright 2014 Arduino LLC (http://www.arduino.cc/) + * + * Arduino is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As a special exception, you may use this file as part of a free software + * library without restriction. Specifically, if other files instantiate + * templates or use macros or inline functions from this file, or you compile + * this file and link it with other files to produce an executable, this + * file does not by itself cause the resulting executable to be covered by + * the GNU General Public License. This exception does not however + * invalidate any other reasons why the executable file might be covered by + * the GNU General Public License. + */ +package cc.arduino.libraries.contributions.ui; + +import static processing.app.I18n._; +import static processing.app.I18n.format; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Insets; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.AbstractCellEditor; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JPanel; +import javax.swing.JTable; +import javax.swing.JTextPane; +import javax.swing.border.EmptyBorder; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; +import javax.swing.table.TableCellEditor; +import javax.swing.table.TableCellRenderer; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.html.HTMLDocument; +import javax.swing.text.html.StyleSheet; + +import processing.app.Base; +import cc.arduino.libraries.contributions.ContributedLibrary; +import cc.arduino.libraries.contributions.ui.LibrariesIndexTableModel.ContributedLibraryReleases; + +@SuppressWarnings("serial") +public class ContributedLibraryTableCell extends AbstractCellEditor implements + TableCellEditor, TableCellRenderer { + + private JPanel panel; + private JTextPane description; + private JButton installButton; + private JButton removeButton; + private Component removeButtonPlaceholder; + private Component installButtonPlaceholder; + + public ContributedLibraryTableCell() { + description = new JTextPane(); + description.setInheritsPopupMenu(true); + Insets margin = description.getMargin(); + margin.bottom = 0; + description.setMargin(margin); + description.setContentType("text/html"); + Document doc = description.getDocument(); + if (doc instanceof HTMLDocument) { + HTMLDocument html = (HTMLDocument) doc; + StyleSheet stylesheet = html.getStyleSheet(); + stylesheet.addRule("body { margin: 0; padding: 0;" + + "font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;" + + "font-size: 100%;" + "font-size: 0.95em; }"); + } + description.setOpaque(false); + description.setBorder(new EmptyBorder(4, 7, 7, 7)); + description.setHighlighter(null); + description.setEditable(false); + description.addHyperlinkListener(new HyperlinkListener() { + @Override + public void hyperlinkUpdate(HyperlinkEvent e) { + if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + Base.openURL(e.getDescription()); + } + } + }); + + { + installButton = new JButton(_("Install")); + installButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + onInstall(editorValue.getSelected()); + } + }); + int width = installButton.getPreferredSize().width; + installButtonPlaceholder = Box.createRigidArea(new Dimension(width, 1)); + } + + { + removeButton = new JButton(_("Remove")); + removeButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + onRemove(editorValue.getInstalled()); + } + }); + int width = removeButton.getPreferredSize().width; + removeButtonPlaceholder = Box.createRigidArea(new Dimension(width, 1)); + } + + panel = new JPanel(); + panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); + + panel.add(description); + panel.add(Box.createHorizontalStrut(5)); + panel.add(installButton); + panel.add(installButtonPlaceholder); + panel.add(Box.createHorizontalStrut(5)); + panel.add(removeButton); + panel.add(removeButtonPlaceholder); + panel.add(Box.createHorizontalStrut(5)); + } + + protected void onRemove(ContributedLibrary contributedPlatform) { + // Empty + } + + protected void onInstall(ContributedLibrary contributedPlatform) { + // Empty + } + + public Component getTableCellRendererComponent(JTable table, Object value, + boolean isSelected, + boolean hasFocus, int row, + int column) { + parentTable = table; + return getUpdatedCellComponent(value, isSelected, row); + } + + private ContributedLibraryReleases editorValue; + private JTable parentTable; + + @Override + public Object getCellEditorValue() { + return editorValue; + } + + @Override + public Component getTableCellEditorComponent(JTable table, Object value, + boolean isSelected, int row, + int column) { + parentTable = table; + editorValue = (ContributedLibraryReleases) value; + return getUpdatedCellComponent(value, true, row); + } + + private Component getUpdatedCellComponent(Object value, boolean isSelected, + int row) { + ContributedLibraryReleases releases = (ContributedLibraryReleases) value; + ContributedLibrary selectedLib = releases.getSelected(); + ContributedLibrary installedLib = releases.getInstalled(); + + boolean removable, installable, upgradable; + if (installedLib == null) { + installable = true; + removable = false; + upgradable = false; + } else { + installable = false; + removable = !installedLib.isReadOnly(); + upgradable = (selectedLib != installedLib); + } + if (installable) + installButton.setText(_("Install")); + if (upgradable) + installButton.setText(_("Upgrade")); + installButton.setVisible(installable || upgradable); + installButtonPlaceholder.setVisible(!(installable || upgradable)); + removeButton.setVisible(removable); + removeButtonPlaceholder.setVisible(!removable); + + String name = selectedLib.getName(); + String author = selectedLib.getAuthor(); + // String maintainer = selectedLib.getMaintainer(); + String website = selectedLib.getWebsite(); + String sentence = selectedLib.getSentence(); + String paragraph = selectedLib.getParagraph(); + String availableVer = selectedLib.getVersion(); + String url = selectedLib.getUrl(); + + String midcolor = isSelected ? "#000000" : "#888888"; + + String desc = ""; + // Library name + desc += format("{0}", name); + + desc += format("", midcolor); + + if (author != null && !author.isEmpty()) { + desc += format(" by {1}", website, author); + } + desc += "
"; + + if (sentence != null) { + desc += format("{0}
", sentence); + if (paragraph != null && !paragraph.isEmpty()) + desc += format("{0}
", paragraph); + desc += "
"; + } + + desc += "
"; // close midcolor + + // If the selected lib is available from repository... + if (url != null) { + desc += format(_("Available version: {0}"), availableVer); + removeButton.setText(_("Remove")); + } else { + removeButton.setText(_("Delete")); + } + desc += "
"; + + if (installedLib != null) { + String installedVer = installedLib.getVersion(); + if (installedVer == null) + installedVer = "Legacy"; + desc += format(_("Installed version: {0}"), installedVer); + if (installedLib.isReadOnly()) + desc += " " + _("(Bundled)"); + } + desc += "
"; + + desc += ""; + description.setText(desc); + description.setBackground(Color.WHITE); + + try { + // for modelToView to work, the text area has to be sized. It doesn't + // matter if it's visible or not. + + // See: + // http://stackoverflow.com/questions/3081210/how-to-set-jtextarea-to-have-height-that-matches-the-size-of-a-text-it-contains + int width = parentTable.getBounds().width; + width -= installButtonPlaceholder.getPreferredSize().width; + width -= removeButtonPlaceholder.getPreferredSize().width; + Dimension minimalSize = new Dimension(width, 10); + description.setPreferredSize(minimalSize); + description.setSize(minimalSize); + + Rectangle r = description.modelToView(description.getDocument() + .getLength()); + r.height += description.modelToView(0).y; // add margins + Dimension d = new Dimension(minimalSize.width, r.y + r.height); + description.setPreferredSize(d); + } catch (BadLocationException e) { + e.printStackTrace(); + } + + if (isSelected) { + panel.setBackground(parentTable.getSelectionBackground()); + panel.setForeground(parentTable.getSelectionForeground()); + } else { + panel.setBackground(parentTable.getBackground()); + panel.setForeground(parentTable.getForeground()); + } + + return panel; + } + + void setEnabled(boolean enabled) { + installButton.setEnabled(enabled); + removeButton.setEnabled(enabled); + } + + public void invalidate() { + panel.invalidate(); + } + +} diff --git a/app/src/cc/arduino/libraries/contributions/ui/LibrariesIndexTableModel.java b/app/src/cc/arduino/libraries/contributions/ui/LibrariesIndexTableModel.java new file mode 100644 index 000000000..7ba736899 --- /dev/null +++ b/app/src/cc/arduino/libraries/contributions/ui/LibrariesIndexTableModel.java @@ -0,0 +1,278 @@ +/* + * This file is part of Arduino. + * + * Copyright 2014 Arduino LLC (http://www.arduino.cc/) + * + * Arduino is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As a special exception, you may use this file as part of a free software + * library without restriction. Specifically, if other files instantiate + * templates or use macros or inline functions from this file, or you compile + * this file and link it with other files to produce an executable, this + * file does not by itself cause the resulting executable to be covered by + * the GNU General Public License. This exception does not however + * invalidate any other reasons why the executable file might be covered by + * the GNU General Public License. + */ +package cc.arduino.libraries.contributions.ui; + +import static cc.arduino.packages.contributions.VersionComparator.VERSION_COMPARATOR; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.swing.table.AbstractTableModel; + +import cc.arduino.libraries.contributions.ContributedLibrary; +import cc.arduino.libraries.contributions.LibrariesIndexer; +import cc.arduino.packages.contributions.ContributedPackage; +import cc.arduino.packages.contributions.ContributedPlatform; + +@SuppressWarnings("serial") +public class LibrariesIndexTableModel extends AbstractTableModel { + + public final static int DESCRIPTION_COL = 0; + + public static class ContributedLibraryReleases implements + Comparable { + public ContributedPackage packager; + public String name; + public List releases = new ArrayList(); + public List versions = new ArrayList(); + public ContributedLibrary selected = null; + + public ContributedLibraryReleases(ContributedLibrary library) { + name = library.getName(); + add(library); + } + + public boolean shouldContain(ContributedLibrary lib) { + if (!lib.getName().equals(name)) + return false; + return true; + } + + public void add(ContributedLibrary library) { + releases.add(library); + versions.add(library.getVersion()); + selected = getLatest(); + } + + public ContributedLibrary getInstalled() { + for (ContributedLibrary lib : releases) + if (lib.isInstalled()) + return lib; + return null; + } + + public ContributedLibrary getLatest() { + ContributedLibrary latest = null; + for (ContributedLibrary lib : releases) { + if (latest == null) + latest = lib; + // TODO a better version compare + + if (VERSION_COMPARATOR.compare(lib.getVersion(), latest.getVersion()) > 0) + latest = lib; + } + return latest; + } + + public ContributedLibrary getSelected() { + return selected; + } + + public void selectVersion(String version) { + for (ContributedLibrary lib : releases) { + if (lib.getVersion().equals(version)) { + selected = lib; + return; + } + } + } + + public void select(ContributedLibrary value) { + for (ContributedLibrary plat : releases) { + if (plat == value) { + selected = plat; + return; + } + } + } + + @Override + public int compareTo(ContributedLibraryReleases o) { + return name.compareToIgnoreCase(o.name); + } + } + + private List contributions = new ArrayList(); + + private String[] columnNames = { "Description" }; + + private Class[] columnTypes = { ContributedPlatform.class }; + + private LibrariesIndexer indexer; + + public void setIndexer(LibrariesIndexer _index) { + indexer = _index; + } + + String selectedCategory = null; + String selectedFilters[] = null; + + public void updateIndexFilter(String category, String filters[]) { + selectedCategory = category; + selectedFilters = filters; + update(); + } + + /** + * Check if string contains all the substrings in set. The + * compare is case insensitive. + * + * @param string + * @param set + * @return true if all the strings in set are contained in + * string. + */ + private boolean stringContainsAll(String string, String set[]) { + if (set == null) + return true; + for (String s : set) { + if (!string.toLowerCase().contains(s.toLowerCase())) + return false; + } + return true; + } + + private void addContribution(ContributedLibrary lib) { + for (ContributedLibraryReleases contribution : contributions) { + if (!contribution.shouldContain(lib)) + continue; + contribution.add(lib); + return; + } + + contributions.add(new ContributedLibraryReleases(lib)); + } + + @Override + public int getColumnCount() { + return columnNames.length; + } + + @Override + public int getRowCount() { + return contributions.size(); + } + + @Override + public String getColumnName(int column) { + return columnNames[column]; + } + + @Override + public Class getColumnClass(int colum) { + return columnTypes[colum]; + } + + @Override + public void setValueAt(Object value, int row, int col) { + if (col == DESCRIPTION_COL) { + fireTableCellUpdated(row, col); + } + } + + @Override + public Object getValueAt(int row, int col) { + ContributedLibraryReleases contribution = contributions.get(row); + if (col == DESCRIPTION_COL) { + return contribution;// .getSelected(); + } + return null; + } + + @Override + public boolean isCellEditable(int row, int col) { + return col == DESCRIPTION_COL; + } + + public List getReleasesVersions(int row) { + return contributions.get(row).versions; + } + + public ContributedLibraryReleases getReleases(int row) { + return contributions.get(row); + } + + public ContributedLibrary getSelectedRelease(int row) { + return contributions.get(row).getSelected(); + } + + public void update() { + updateContributions(); + fireTableDataChanged(); + } + + private void applyFilterToLibrary(ContributedLibrary lib) { + if (selectedCategory != null && !selectedCategory.isEmpty()) { + if (lib.getCategory() == null || + !lib.getCategory().equals(selectedCategory)) + return; + } + if (!stringContainsAll(lib.getName(), selectedFilters)) + return; + addContribution(lib); + } + + public void updateLibrary(ContributedLibrary lib) { + // Find the row interested in the change + int row = -1; + for (ContributedLibraryReleases releases : contributions) { + if (releases.shouldContain(lib)) + row = contributions.indexOf(releases); + } + + updateContributions(); + + // If the library is found in the list send update event + // or insert event on the specific row... + for (ContributedLibraryReleases releases : contributions) { + if (releases.shouldContain(lib)) { + if (row == -1) { + row = contributions.indexOf(releases); + fireTableRowsInserted(row, row); + } else { + fireTableRowsUpdated(row, row); + } + return; + } + } + // ...otherwise send a row deleted event + fireTableRowsDeleted(row, row); + } + + private void updateContributions() { + contributions.clear(); + for (ContributedLibrary l : indexer.getIndex().getLibraries()) + applyFilterToLibrary(l); + for (ContributedLibrary l : indexer.getInstalledLibraries()) + applyFilterToLibrary(l); + Collections.sort(contributions); + } + +} diff --git a/app/src/cc/arduino/libraries/contributions/ui/LibraryInstaller.java b/app/src/cc/arduino/libraries/contributions/ui/LibraryInstaller.java new file mode 100644 index 000000000..827982106 --- /dev/null +++ b/app/src/cc/arduino/libraries/contributions/ui/LibraryInstaller.java @@ -0,0 +1,145 @@ +/* + * This file is part of Arduino. + * + * Copyright 2014 Arduino LLC (http://www.arduino.cc/) + * + * Arduino is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As a special exception, you may use this file as part of a free software + * library without restriction. Specifically, if other files instantiate + * templates or use macros or inline functions from this file, or you compile + * this file and link it with other files to produce an executable, this + * file does not by itself cause the resulting executable to be covered by + * the GNU General Public License. This exception does not however + * invalidate any other reasons why the executable file might be covered by + * the GNU General Public License. + */ +package cc.arduino.libraries.contributions.ui; + +import static processing.app.I18n._; + +import java.io.File; +import java.io.IOException; +import java.net.URL; + +import processing.app.helpers.FileUtils; +import cc.arduino.libraries.contributions.ContributedLibrary; +import cc.arduino.libraries.contributions.LibrariesIndexer; +import cc.arduino.packages.contributions.DownloadableContributionsDownloader; +import cc.arduino.utils.ArchiveExtractor; +import cc.arduino.utils.MultiStepProgress; +import cc.arduino.utils.Progress; + +public class LibraryInstaller { + + private LibrariesIndexer indexer; + private File stagingFolder; + private DownloadableContributionsDownloader downloader; + + public LibraryInstaller(LibrariesIndexer _indexer) { + indexer = _indexer; + stagingFolder = _indexer.getStagingFolder(); + downloader = new DownloadableContributionsDownloader(stagingFolder) { + @Override + protected void onProgress(Progress progress) { + LibraryInstaller.this.onProgress(progress); + }; + }; + } + + public void updateIndex() throws Exception { + final MultiStepProgress progress = new MultiStepProgress(2); + + // Step 1: Download index + URL url = new URL("http://arduino.cc/library_index.json"); + File outputFile = indexer.getIndexFile(); + File tmpFile = new File(outputFile.getAbsolutePath() + ".tmp"); + try { + downloader.download(url, tmpFile, progress, + _("Downloading libraries index...")); + } catch (InterruptedException e) { + // Download interrupted... just exit + return; + } + progress.stepDone(); + + // TODO: Check downloaded index + + // Replace old index with the updated one + if (outputFile.exists()) + outputFile.delete(); + if (!tmpFile.renameTo(outputFile)) + throw new Exception( + _("An error occurred while updating libraries index!")); + + // Step 2: Rescan index + rescanLibraryIndex(progress); + } + + public void install(ContributedLibrary lib) throws Exception { + if (lib.isInstalled()) + throw new Exception(_("Library is already installed!")); + + final MultiStepProgress progress = new MultiStepProgress(3); + + // Step 1: Download library + try { + downloader.download(lib, progress, _("Downloading library.")); + } catch (InterruptedException e) { + // Download interrupted... just exit + return; + } + + // TODO: Extract to temporary folders and move to the final destination only + // once everything is successfully unpacked. If the operation fails remove + // all the temporary folders and abort installation. + + // Step 2: Unpack library on the correct location + progress.setStatus(_("Installing library...")); + onProgress(progress); + File destFolder = new File(indexer.getSketchbookLibrariesFolder(), lib.getName()); + destFolder.mkdirs(); + ArchiveExtractor.extract(lib.getDownloadedFile(), destFolder, 1); + progress.stepDone(); + + // Step 3: Rescan index + rescanLibraryIndex(progress); + } + + public void remove(ContributedLibrary lib) throws IOException { + final MultiStepProgress progress = new MultiStepProgress(2); + + // Step 1: Remove library + progress.setStatus(_("Removing library...")); + onProgress(progress); + FileUtils.recursiveDelete(lib.getInstalledFolder()); + progress.stepDone(); + + // Step 2: Rescan index + rescanLibraryIndex(progress); + } + + private void rescanLibraryIndex(MultiStepProgress progress) + throws IOException { + progress.setStatus(_("Updating list of installed libraries")); + onProgress(progress); + indexer.rescanLibraries(); + progress.stepDone(); + } + + protected void onProgress(Progress progress) { + // Empty + } +} diff --git a/app/src/cc/arduino/libraries/contributions/ui/LibraryManagerUI.java b/app/src/cc/arduino/libraries/contributions/ui/LibraryManagerUI.java new file mode 100644 index 000000000..75b0551c4 --- /dev/null +++ b/app/src/cc/arduino/libraries/contributions/ui/LibraryManagerUI.java @@ -0,0 +1,381 @@ +/* + * This file is part of Arduino. + * + * Copyright 2014 Arduino LLC (http://www.arduino.cc/) + * + * Arduino is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As a special exception, you may use this file as part of a free software + * library without restriction. Specifically, if other files instantiate + * templates or use macros or inline functions from this file, or you compile + * this file and link it with other files to produce an executable, this + * file does not by itself cause the resulting executable to be covered by + * the GNU General Public License. This exception does not however + * invalidate any other reasons why the executable file might be covered by + * the GNU General Public License. + */ +package cc.arduino.libraries.contributions.ui; + +import static cc.arduino.packages.contributions.ui.ContributionIndexTableModel.DESCRIPTION_COL; +import static processing.app.I18n._; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dialog; +import java.awt.Dimension; +import java.awt.Frame; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Collection; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.ScrollPaneConstants; +import javax.swing.SwingUtilities; +import javax.swing.border.EmptyBorder; +import javax.swing.event.TableModelEvent; +import javax.swing.event.TableModelListener; +import javax.swing.table.TableCellEditor; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; +import javax.swing.table.TableColumnModel; + +import processing.app.Theme; +import cc.arduino.libraries.contributions.ContributedLibrary; +import cc.arduino.libraries.contributions.LibrariesIndexer; +import cc.arduino.ui.FilterJTextField; +import cc.arduino.ui.ProgressJProgressBar; +import cc.arduino.utils.Progress; + +@SuppressWarnings("serial") +public class LibraryManagerUI extends JDialog { + + private FilterJTextField filterField; + + private JLabel categoryLabel; + private JComboBox categoryChooser; + private Component categoryStrut1; + private Component categoryStrut2; + private Component categoryStrut3; + + private LibrariesIndexTableModel contribModel = new LibrariesIndexTableModel(); + private JTable contribTable; + private ProgressJProgressBar progressBar; + + private Box progressBox; + private Box updateBox; + + private ContributedLibraryTableCell cellEditor; + + // Currently selected category and filters + private String category; + private String[] filters; + + public LibraryManagerUI(Frame parent) { + super(parent, "Library Manager", Dialog.ModalityType.APPLICATION_MODAL); + + setResizable(true); + + Container pane = getContentPane(); + pane.setLayout(new BorderLayout()); + + { + categoryStrut1 = Box.createHorizontalStrut(5); + categoryStrut2 = Box.createHorizontalStrut(5); + categoryStrut3 = Box.createHorizontalStrut(5); + + categoryLabel = new JLabel(_("Category:")); + + categoryChooser = new JComboBox(); + categoryChooser.setMaximumRowCount(20); + categoryChooser.setEnabled(false); + + filterField = new FilterJTextField(_("Filter your search...")) { + @Override + protected void onFilter(String[] _filters) { + filters = _filters; + cellEditor.stopCellEditing(); + contribModel.updateIndexFilter(category, filters); + } + }; + + JPanel panel = new JPanel(); + panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); + panel.add(categoryStrut1); + panel.add(categoryLabel); + panel.add(categoryStrut2); + panel.add(categoryChooser); + panel.add(categoryStrut3); + panel.add(filterField); + panel.setBorder(new EmptyBorder(7, 7, 7, 7)); + pane.add(panel, BorderLayout.NORTH); + } + + contribTable = new JTable(contribModel); + contribTable.setTableHeader(null); + contribTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + contribTable.setColumnSelectionAllowed(false); + contribTable.setDragEnabled(false); + contribTable.setIntercellSpacing(new Dimension(0, 1)); + contribTable.setShowVerticalLines(false); + contribTable.setSelectionBackground(Theme.getColor("status.notice.bgcolor")); + { + TableColumnModel tcm = contribTable.getColumnModel(); + TableColumn col = tcm.getColumn(DESCRIPTION_COL); + col.setCellRenderer(new ContributedLibraryTableCell()); + cellEditor = new ContributedLibraryTableCell() { + @Override + protected void onInstall(ContributedLibrary selectedPlatform) { + onInstallPressed(selectedPlatform); + } + + @Override + protected void onRemove(ContributedLibrary installedPlatform) { + onRemovePressed(installedPlatform); + } + }; + col.setCellEditor(cellEditor); + col.setResizable(true); + } + + { + JScrollPane s = new JScrollPane(); + s.setViewportView(contribTable); + s.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); + s.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + pane.add(s, BorderLayout.CENTER); + } + + pane.add(Box.createHorizontalStrut(10), BorderLayout.WEST); + pane.add(Box.createHorizontalStrut(10), BorderLayout.EAST); + + { + progressBar = new ProgressJProgressBar(); + progressBar.setStringPainted(true); + progressBar.setString(" "); + progressBar.setVisible(true); + + JButton cancelButton = new JButton(_("Cancel")); + cancelButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + onCancelPressed(); + } + }); + + JButton updateButton = new JButton(_("Update list")); + updateButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + onUpdatePressed(); + } + }); + + { + progressBox = Box.createHorizontalBox(); + progressBox.add(progressBar); + progressBox.add(Box.createHorizontalStrut(5)); + progressBox.add(cancelButton); + + updateBox = Box.createHorizontalBox(); + updateBox.add(Box.createHorizontalGlue()); + updateBox.add(updateButton); + + JPanel progressPanel = new JPanel(); + progressPanel.setBorder(new EmptyBorder(7, 7, 7, 7)); + progressPanel.setLayout(new BoxLayout(progressPanel, BoxLayout.Y_AXIS)); + progressPanel.add(progressBox); + progressPanel.add(updateBox); + pane.add(progressPanel, BorderLayout.SOUTH); + + setProgressVisible(false); + } + } + + setMinimumSize(new Dimension(600, 450)); + } + + private TableModelListener tableModelListener = new TableModelListener() { + @Override + public void tableChanged(final TableModelEvent arg0) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + updateCellsHeight(arg0); + } + }); + } + }; + + public void setIndexer(LibrariesIndexer indexer) { + contribModel.removeTableModelListener(tableModelListener); + categoryChooser.removeActionListener(categoryChooserActionListener); + + contribModel.setIndexer(indexer); + + category = null; + categoryChooser.removeAllItems(); + + contribModel.addTableModelListener(tableModelListener); + categoryChooser.addActionListener(categoryChooserActionListener); + + // Load categories + Collection categories = indexer.getIndex().getCategories(); + categoryChooser.addItem(""); + for (String s : categories) + categoryChooser.addItem(s); + + // Enable categories combo only if there are two or more choices + int count = categoryChooser.getItemCount(); + categoryChooser.setEnabled(count > 1); + + filterField.setEnabled(contribModel.getRowCount() > 0); + + // Create LibrariesInstaller tied with the provided index + installer = new LibraryInstaller(indexer) { + @Override + public void onProgress(Progress progress) { + setProgress(progress); + } + }; + } + + ActionListener categoryChooserActionListener = new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + String selected = (String) categoryChooser.getSelectedItem(); + if (category == null || !category.equals(selected)) { + category = selected; + cellEditor.stopCellEditing(); + contribModel.updateIndexFilter(category, filters); + } + } + }; + + public void setProgressVisible(boolean visible) { + progressBox.setVisible(visible); + + filterField.setEnabled(!visible); + categoryChooser.setEnabled(!visible); + contribTable.setEnabled(!visible); + updateBox.setVisible(!visible); + updateBox.setEnabled(!visible); + cellEditor.setEnabled(!visible); + + if (visible && contribTable.isEditing()) { + TableCellEditor editor = contribTable.getCellEditor(); + if (editor != null) + editor.stopCellEditing(); + } + } + + private void updateCellsHeight(TableModelEvent e) { + int first = e.getFirstRow(); + int last = Math.min(e.getLastRow(), contribTable.getRowCount() - 1); + for (int row = first; row <= last; row++) { + TableCellRenderer editor = new ContributedLibraryTableCell(); + Component comp = contribTable.prepareRenderer(editor, row, 0); + int height = comp.getPreferredSize().height; + contribTable.setRowHeight(row, height); + } + } + + public void setProgress(Progress progress) { + progressBar.setValue(progress); + } + + /* + * Installer methods follows + */ + + private LibraryInstaller installer; + private Thread installerThread = null; + + public void onCancelPressed() { + if (installerThread != null) + installerThread.interrupt(); + } + + public void onUpdatePressed() { + installerThread = new Thread(new Runnable() { + @Override + public void run() { + try { + setProgressVisible(true); + installer.updateIndex(); + onIndexesUpdated(); + } catch (Exception e) { + // TODO Show ERROR + e.printStackTrace(); + } finally { + setProgressVisible(false); + } + } + }); + installerThread.start(); + } + + public void onInstallPressed(final ContributedLibrary lib) { + installerThread = new Thread(new Runnable() { + @Override + public void run() { + try { + setProgressVisible(true); + installer.install(lib); + contribModel.updateLibrary(lib); + } catch (Exception e) { + // TODO Show ERROR + e.printStackTrace(); + } finally { + setProgressVisible(false); + } + } + }); + installerThread.start(); + } + + public void onRemovePressed(final ContributedLibrary lib) { + installerThread = new Thread(new Runnable() { + @Override + public void run() { + try { + setProgressVisible(true); + installer.remove(lib); + contribModel.updateLibrary(lib); + } catch (Exception e) { + // TODO Show ERROR + e.printStackTrace(); + } finally { + setProgressVisible(false); + } + } + }); + installerThread.start(); + } + + protected void onIndexesUpdated() throws Exception { + // Empty + } + +} diff --git a/app/src/cc/arduino/packages/contributions/ui/ContributionManagerUI.java b/app/src/cc/arduino/packages/contributions/ui/ContributionManagerUI.java index f815317e6..6320e72f0 100644 --- a/app/src/cc/arduino/packages/contributions/ui/ContributionManagerUI.java +++ b/app/src/cc/arduino/packages/contributions/ui/ContributionManagerUI.java @@ -48,7 +48,6 @@ import javax.swing.JComboBox; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JPanel; -import javax.swing.JProgressBar; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.ListSelectionModel; @@ -65,6 +64,9 @@ import javax.swing.table.TableColumnModel; import cc.arduino.packages.contributions.ContributedPlatform; import cc.arduino.packages.contributions.ContributionInstaller; import cc.arduino.packages.contributions.ContributionsIndexer; +import cc.arduino.ui.FilterJTextField; +import cc.arduino.ui.ProgressJProgressBar; +import cc.arduino.utils.Progress; @SuppressWarnings("serial") public class ContributionManagerUI extends JDialog { @@ -79,7 +81,7 @@ public class ContributionManagerUI extends JDialog { private ContributionIndexTableModel contribModel = new ContributionIndexTableModel(); private JTable contribTable; - private JProgressBar progressBar; + private ProgressJProgressBar progressBar; private Box progressBox; private Box updateBox; @@ -169,7 +171,7 @@ public class ContributionManagerUI extends JDialog { pane.add(Box.createHorizontalStrut(10), BorderLayout.EAST); { - progressBar = new JProgressBar(); + progressBar = new ProgressJProgressBar(); progressBar.setStringPainted(true); progressBar.setString(" "); progressBar.setVisible(true); @@ -249,13 +251,12 @@ public class ContributionManagerUI extends JDialog { categoryChooser.addItem(s); // Create ConstributionInstaller tied with the provided index - installer = new ContributionInstaller(indexer); - installer.setListener(new ContributionInstaller.Listener() { + installer = new ContributionInstaller(indexer) { @Override - public void onProgress(double progress, String message) { - setProgress((int) progress, message); + public void onProgress(Progress progress) { + setProgress(progress); } - }); + }; } ActionListener categoryChooserActionListener = new ActionListener() { @@ -298,10 +299,8 @@ public class ContributionManagerUI extends JDialog { } } - public void setProgress(int progress, String text) { + public void setProgress(Progress progress) { progressBar.setValue(progress); - if (text != null) - progressBar.setString(text); } /* diff --git a/app/src/cc/arduino/packages/contributions/ui/FilterJTextField.java b/app/src/cc/arduino/ui/FilterJTextField.java similarity index 98% rename from app/src/cc/arduino/packages/contributions/ui/FilterJTextField.java rename to app/src/cc/arduino/ui/FilterJTextField.java index f66afaa62..c9b36d0a8 100644 --- a/app/src/cc/arduino/packages/contributions/ui/FilterJTextField.java +++ b/app/src/cc/arduino/ui/FilterJTextField.java @@ -26,7 +26,7 @@ * invalidate any other reasons why the executable file might be covered by * the GNU General Public License. */ -package cc.arduino.packages.contributions.ui; +package cc.arduino.ui; import java.awt.Color; import java.awt.Font; diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index c7dfa074c..03b8629a7 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -22,15 +22,7 @@ package processing.app; -import java.awt.*; -import java.awt.event.*; -import java.io.*; -import java.util.*; -import java.util.List; - -import javax.swing.*; - -import cc.arduino.libraries.contributions.LibrariesIndexer; +import cc.arduino.libraries.contributions.ui.LibraryManagerUI; import cc.arduino.packages.DiscoveryManager; import cc.arduino.packages.contributions.ui.ContributionManagerUI; import cc.arduino.view.SplashScreenHelper; @@ -43,7 +35,6 @@ import processing.app.helpers.filefilters.OnlyFilesWithExtension; import processing.app.javax.swing.filechooser.FileNameExtensionFilter; import processing.app.legacy.PApplet; import processing.app.macosx.ThinkDifferent; -import processing.app.legacy.PConstants; import processing.app.packages.LibraryList; import processing.app.packages.UserLibrary; import processing.app.tools.MenuScroller; @@ -1020,9 +1011,7 @@ public class Base { } public LibraryList getIDELibs() { - if (getLibraries() == null) - return new LibraryList(); - LibraryList res = new LibraryList(getLibraries()); + LibraryList res = new LibraryList(BaseNoGui.librariesIndexer.getInstalledLibraries()); res.removeAll(getUserLibs()); return res; } @@ -1036,16 +1025,13 @@ public class Base { return; importMenu.removeAll(); - JMenuItem addLibraryMenuItem = new JMenuItem(_("Add Library...")); - addLibraryMenuItem.addActionListener(new ActionListener() { + JMenuItem menu = new JMenuItem(_("Manage libraries...")); + menu.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - Base.this.handleAddLibrary(); - Base.this.onBoardOrPortChange(); - Base.this.rebuildImportMenu(Editor.importMenu); - Base.this.rebuildExamplesMenu(Editor.examplesMenu); + openManageLibrariesDialog(); } }); - importMenu.add(addLibraryMenuItem); + importMenu.add(menu); importMenu.addSeparator(); // Split between user supplied libraries and IDE libraries @@ -1116,6 +1102,27 @@ public class Base { editor.onBoardOrPortChange(); } + private void openManageLibrariesDialog() { + @SuppressWarnings("serial") + LibraryManagerUI managerUI = new LibraryManagerUI(activeEditor) { + @Override + protected void onIndexesUpdated() throws Exception { + BaseNoGui.initPackages(); + rebuildBoardsMenu(); + onBoardOrPortChange(); + setIndexer(BaseNoGui.librariesIndexer); + } + }; + managerUI.setIndexer(BaseNoGui.librariesIndexer); + managerUI.setVisible(true); + // Manager dialog is modal, waits here until closed + + //handleAddLibrary(); + onBoardOrPortChange(); + rebuildImportMenu(Editor.importMenu); + rebuildExamplesMenu(Editor.examplesMenu); + } + private void openInstallBoardDialog() { // Create dialog for contribution manager @SuppressWarnings("serial") @@ -1693,8 +1700,9 @@ public class Base { } + // XXX: Remove this method and make librariesIndexer non-static static public LibraryList getLibraries() { - return BaseNoGui.getLibraries(); + return BaseNoGui.librariesIndexer.getInstalledLibraries(); } diff --git a/arduino-core/src/cc/arduino/libraries/contributions/LibrariesIndex.java b/arduino-core/src/cc/arduino/libraries/contributions/LibrariesIndex.java index 07cccd339..10fd5bbe9 100644 --- a/arduino-core/src/cc/arduino/libraries/contributions/LibrariesIndex.java +++ b/arduino-core/src/cc/arduino/libraries/contributions/LibrariesIndex.java @@ -28,6 +28,8 @@ */ package cc.arduino.libraries.contributions; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; public abstract class LibrariesIndex { @@ -49,4 +51,19 @@ public abstract class LibrariesIndex { res += l.toString(); return res; } + + private List categories = new ArrayList(); + + public List getCategories() { + return categories; + } + + public void fillCategories() { + categories.clear(); + for (ContributedLibrary lib : getLibraries()) { + if (!categories.contains(lib.getCategory())) + categories.add(lib.getCategory()); + } + Collections.sort(categories); + } } diff --git a/arduino-core/src/cc/arduino/libraries/contributions/LibrariesIndexer.java b/arduino-core/src/cc/arduino/libraries/contributions/LibrariesIndexer.java index a8697c6d8..5b34ae3f0 100644 --- a/arduino-core/src/cc/arduino/libraries/contributions/LibrariesIndexer.java +++ b/arduino-core/src/cc/arduino/libraries/contributions/LibrariesIndexer.java @@ -38,6 +38,7 @@ import java.util.List; import processing.app.BaseNoGui; import processing.app.I18n; +import processing.app.helpers.FileUtils; import processing.app.helpers.filefilters.OnlyDirs; import processing.app.packages.LegacyUserLibrary; import processing.app.packages.LibraryList; @@ -51,16 +52,23 @@ import com.fasterxml.jackson.module.mrbean.MrBeanModule; public class LibrariesIndexer { private LibrariesIndex index; + private LibraryList installedLibraries = new LibraryList(); + private List librariesFolders; private File indexFile; + private File stagingFolder; + private File sketchbookLibrariesFolder; - public LibrariesIndexer(File _settingsFolder) { - indexFile = new File(_settingsFolder, "library_index.json"); + public LibrariesIndexer(File preferencesFolder) { + indexFile = new File(preferencesFolder, "library_index.json"); + stagingFolder = new File(preferencesFolder, "staging" + File.separator + + "libraries"); } public void parseIndex() throws JsonParseException, IOException { parseIndex(indexFile); System.out.println(index); + index.fillCategories(); // TODO: resolve libraries inner references } @@ -75,75 +83,108 @@ public class LibrariesIndexer { index = mapper.readValue(indexIn, LibrariesIndex.class); } - public LibraryList scanLibraries(List folders) throws IOException { - LibraryList res = new LibraryList(); - for (File folder : folders) - res.addOrReplaceAll(scanLibraries(folder)); - return res; + public void setLibrariesFolders(List _librariesFolders) + throws IOException { + librariesFolders = _librariesFolders; + rescanLibraries(); } - private LibraryList scanLibraries(File folder) throws IOException { - LibraryList res = new LibraryList(); + public void rescanLibraries() throws IOException { + // Clear all installed flags + installedLibraries.clear(); + for (ContributedLibrary lib : index.getLibraries()) + lib.setInstalled(false); - File list[] = folder.listFiles(new OnlyDirs()); + // Rescan libraries + for (File folder : librariesFolders) + scanInstalledLibraries(folder); + } + + private void scanInstalledLibraries(File folder) { + File list[] = folder.listFiles(OnlyDirs.ONLY_DIRS); // if a bad folder or something like that, this might come back null if (list == null) - return res; + return; for (File subfolder : list) { if (!BaseNoGui.isSanitaryName(subfolder.getName())) { - String mess = I18n - .format(_("The library \"{0}\" cannot be used.\n" - + "Library names must contain only basic letters and numbers.\n" - + "(ASCII only and no spaces, and it cannot start with a number)"), - subfolder.getName()); + String mess = I18n.format(_("The library \"{0}\" cannot be used.\n" + + "Library names must contain only basic letters and numbers.\n" + + "(ASCII only and no spaces, and it cannot start with a number)"), + subfolder.getName()); BaseNoGui.showMessage(_("Ignoring bad library name"), mess); continue; } try { - ContributedLibrary lib = scanLibrary(subfolder); - - // (also replace previously found libs with the same name) - if (lib != null) - res.addOrReplace(lib); + scanLibrary(subfolder); } catch (IOException e) { System.out.println(I18n.format(_("Invalid library found in {0}: {1}"), subfolder, e.getMessage())); } } - return res; } - private ContributedLibrary scanLibrary(File subfolder) throws IOException { - // A library is considered non-Legacy if it contains - // a file called "library.properties" - File check = new File(subfolder, "library.properties"); - if (!check.exists() || !check.isFile()) - return LegacyUserLibrary.create(subfolder); + private void scanLibrary(File folder) throws IOException { + boolean readOnly = !FileUtils + .isSubDirectory(sketchbookLibrariesFolder, folder); - ContributedLibrary lib = UserLibrary.create(subfolder); + // A library is considered "legacy" if it doesn't contains + // a file called "library.properties" + File check = new File(folder, "library.properties"); + if (!check.exists() || !check.isFile()) { + + // Create a legacy library and exit + LegacyUserLibrary lib = LegacyUserLibrary.create(folder); + lib.setReadOnly(readOnly); + installedLibraries.addOrReplace(lib); + return; + } + + // Create a regular library + UserLibrary lib = UserLibrary.create(folder); + lib.setReadOnly(readOnly); + installedLibraries.addOrReplace(lib); // Check if we can find the same library in the index - // String libName = subfolder.getName(); // XXX: lib.getName()? - // ContributedLibrary foundLib = index.find(libName, lib.getVersion()); - // if (foundLib != null) { - // foundLib.setInstalled(true); - // foundLib.setInstalledFolder(subfolder); - // return foundLib; - // } - - return lib; - } - - public static void main(String[] args) throws JsonParseException, IOException { - LibrariesIndexer indexer = new LibrariesIndexer(new File( - "/home/megabug/.arduino15")); - indexer.parseIndex(); - LibraryList libs = indexer.scanLibraries(new File( - "/home/megabug/sketchbook/libraries")); - for (ContributedLibrary lib : libs) { - System.out.println(lib); + // and mark it as installed + String libName = folder.getName(); // XXX: lib.getName()? + ContributedLibrary foundLib = index.find(libName, lib.getVersion()); + if (foundLib != null) { + foundLib.setInstalled(true); + foundLib.setInstalledFolder(folder); + foundLib.setReadOnly(readOnly); } } + + public LibrariesIndex getIndex() { + return index; + } + + public LibraryList getInstalledLibraries() { + return installedLibraries; + } + + public File getStagingFolder() { + return stagingFolder; + } + + /** + * Set the sketchbook library folder.
+ * New libraries will be installed here.
+ * Libraries not found on this folder will be marked as read-only. + * + * @param folder + */ + public void setSketchbookLibrariesFolder(File folder) { + this.sketchbookLibrariesFolder = folder; + } + + public File getSketchbookLibrariesFolder() { + return sketchbookLibrariesFolder; + } + + public File getIndexFile() { + return indexFile; + } } diff --git a/arduino-core/src/cc/arduino/packages/contributions/ContributionInstaller.java b/arduino-core/src/cc/arduino/packages/contributions/ContributionInstaller.java index 23d60dfff..16e3a341f 100644 --- a/arduino-core/src/cc/arduino/packages/contributions/ContributionInstaller.java +++ b/arduino-core/src/cc/arduino/packages/contributions/ContributionInstaller.java @@ -42,42 +42,15 @@ import java.util.Observer; import processing.app.helpers.FileUtils; import cc.arduino.utils.ArchiveExtractor; import cc.arduino.utils.FileHash; +import cc.arduino.utils.MultiStepProgress; +import cc.arduino.utils.Progress; import cc.arduino.utils.network.FileDownloader; public class ContributionInstaller { - /** - * Listener for installation progress. - */ - public static interface Listener { - /** - * Receive the latest progress update. - * - * @param progress - * Actual progress in the range 0...100 - * @param message - * A verbose description message of the actual operation - */ - void onProgress(double progress, String message); - } - - private Listener listener = null; - private File stagingFolder; private ContributionsIndexer indexer; - private double progress; - private double progressStepsDelta; - - public void setListener(Listener listener) { - this.listener = listener; - } - - private void updateProgress(double progress, String message) { - if (listener != null) - listener.onProgress(progress, message); - } - public ContributionInstaller(ContributionsIndexer contributionsIndexer) { stagingFolder = contributionsIndexer.getStagingFolder(); indexer = contributionsIndexer; @@ -102,19 +75,18 @@ public class ContributionInstaller { } // Calculate progress increases - progress = 0.0; - progressStepsDelta = 100.0 / (tools.size() + 1) / 2.0; + MultiStepProgress progress = new MultiStepProgress((tools.size() + 1) * 2); // Download all try { // Download platform - download(platform, _("Downloading boards definitions.")); + download(platform, progress, _("Downloading boards definitions.")); // Download tools int i = 1; for (ContributedTool tool : tools) { String msg = format(_("Downloading tools ({0}/{1})."), i, tools.size()); - download(tool.getDownloadableContribution(), msg); + download(tool.getDownloadableContribution(), progress, msg); i++; } } catch (InterruptedException e) { @@ -133,8 +105,9 @@ public class ContributionInstaller { File toolsFolder = new File(packageFolder, "tools"); int i = 1; for (ContributedTool tool : platform.getResolvedTools()) { - String msg = format(_("Installing tools ({0}/{1})..."), i, tools.size()); - updateProgress(progress, msg); + progress.setStatus(format(_("Installing tools ({0}/{1})..."), i, + tools.size())); + onProgress(progress); i++; DownloadableContribution toolContrib = tool.getDownloadableContribution(); File destFolder = new File(toolsFolder, tool.getName() + File.separator + @@ -144,11 +117,12 @@ public class ContributionInstaller { ArchiveExtractor.extract(toolContrib.getDownloadedFile(), destFolder, 1); toolContrib.setInstalled(true); toolContrib.setInstalledFolder(destFolder); - progress += progressStepsDelta; + progress.stepDone(); } // Unpack platform on the correct location - updateProgress(progress, _("Installing boards...")); + progress.setStatus(_("Installing boards...")); + onProgress(progress); File platformFolder = new File(packageFolder, "hardware" + File.separator + platform.getArchitecture()); File destFolder = new File(platformFolder, platform.getVersion()); @@ -156,13 +130,15 @@ public class ContributionInstaller { ArchiveExtractor.extract(platform.getDownloadedFile(), destFolder, 1); platform.setInstalled(true); platform.setInstalledFolder(destFolder); - progress += progressStepsDelta; + progress.stepDone(); - updateProgress(100.0, _("Installation completed!")); + progress.setStatus(_("Installation completed!")); + onProgress(progress); } public File download(DownloadableContribution contribution, - final String statusText) throws Exception { + final MultiStepProgress progress, final String statusText) + throws Exception { URL url = new URL(contribution.getUrl()); String path = url.getPath(); String fileName = path.substring(path.lastIndexOf('/') + 1); @@ -186,18 +162,20 @@ public class ContributionInstaller { long total = me.getInitialSize() + me.getDownloadSize() / 1000; msg = format(_("Downloaded {0}kb of {1}kb."), downloaded, total); } - updateProgress((int) progress + progressStepsDelta * - me.getProgress() / 100.0, statusText + " " + msg); + progress.setStatus(statusText + " " + msg); + progress.setProgress(me.getProgress()); + onProgress(progress); } }); downloader.download(); if (!downloader.isCompleted()) throw new Exception("Error dowloading " + url, downloader.getError()); } - progress += progressStepsDelta; + progress.stepDone(); // Test checksum - updateProgress(progress, _("Verifying archive integrity...")); + progress.setStatus(_("Verifying archive integrity...")); + onProgress(progress); String checksum = contribution.getChecksum(); String algo = checksum.split(":")[0]; if (!FileHash.hash(outputFile, algo).equals(checksum)) @@ -236,8 +214,8 @@ public class ContributionInstaller { } public void updateIndex() throws Exception { + final MultiStepProgress progress = new MultiStepProgress(2); final String statusText = _("Downloading platforms index..."); - updateProgress(0, statusText); URL url = new URL("http://arduino.cc/package_index.json"); File tmpFile = File.createTempFile("package_index", ".json"); @@ -252,13 +230,15 @@ public class ContributionInstaller { long total = me.getInitialSize() + me.getDownloadSize() / 1000; msg = format(_("Downloaded {0}kb of {1}kb."), downloaded, total); } - updateProgress((int) progress + progressStepsDelta * me.getProgress() / - 100.0, statusText + " " + msg); + progress.setStatus(statusText + " " + msg); + progress.setProgress(me.getProgress()); + onProgress(progress); } }); downloader.download(); if (!downloader.isCompleted()) throw new Exception("Error dowloading " + url, downloader.getError()); + progress.stepDone(); // TODO: Check downloaded index @@ -269,4 +249,8 @@ public class ContributionInstaller { if (!tmpFile.renameTo(outputFile)) throw new Exception("An error occurred while updating platforms index!"); } + + protected void onProgress(Progress progress) { + // Empty + } } diff --git a/arduino-core/src/cc/arduino/packages/contributions/VersionComparator.java b/arduino-core/src/cc/arduino/packages/contributions/VersionComparator.java index d048a44eb..b23c66649 100644 --- a/arduino-core/src/cc/arduino/packages/contributions/VersionComparator.java +++ b/arduino-core/src/cc/arduino/packages/contributions/VersionComparator.java @@ -32,10 +32,21 @@ import java.util.Comparator; public class VersionComparator implements Comparator { + // An handy pre-instatiated object + public static final VersionComparator VERSION_COMPARATOR = new VersionComparator(); + @Override - public int compare(String o1, String o2) { - // TODO: do a proper version compare - return o1.compareTo(o2); + public int compare(String a, String b) { + // null is always less than any other value + if (a == null && b == null) + return 0; + if (a == null) + return -1; + if (b == null) + return 1; + + // TODO: do a proper version compare. Look also http://semver.org/ + return a.compareTo(b); } } diff --git a/arduino-core/src/processing/app/BaseNoGui.java b/arduino-core/src/processing/app/BaseNoGui.java index b08ae891f..f0c5fa113 100644 --- a/arduino-core/src/processing/app/BaseNoGui.java +++ b/arduino-core/src/processing/app/BaseNoGui.java @@ -64,6 +64,7 @@ public class BaseNoGui { // maps library name to their library folder static private LibraryList libraries; + // XXX: Remove this field static private List librariesFolders; static UserNotifier notifier = new BasicUserNotifier(); @@ -410,9 +411,8 @@ public class BaseNoGui { } static public LibraryList getUserLibs() { - if (libraries == null) - return new LibraryList(); - return libraries.filterLibrariesInSubfolder(getSketchbookFolder()); + LibraryList libs = BaseNoGui.librariesIndexer.getInstalledLibraries(); + return libs.filterLibrariesInSubfolder(getSketchbookFolder()); } /** @@ -727,12 +727,14 @@ public class BaseNoGui { if (referencedPlatform != null) { File referencedPlatformFolder = referencedPlatform.getFolder(); // Add libraries folder for the referenced platform - librariesFolders.add(new File(referencedPlatformFolder, "libraries")); + File folder = new File(referencedPlatformFolder, "libraries"); + librariesFolders.add(folder); } } File platformFolder = targetPlatform.getFolder(); // Add libraries folder for the selected platform - librariesFolders.add(new File(platformFolder, "libraries")); + File folder = new File(platformFolder, "libraries"); + librariesFolders.add(folder); } // Add libraries folder for the sketchbook @@ -742,7 +744,9 @@ public class BaseNoGui { // Libraries located in the latest folders on the list can override // other libraries with the same name. try { - scanAndUpdateLibraries(librariesFolders); + BaseNoGui.librariesIndexer.setSketchbookLibrariesFolder(getSketchbookLibrariesFolder()); + BaseNoGui.librariesIndexer.setLibrariesFolders(librariesFolders); + BaseNoGui.librariesIndexer.rescanLibraries(); } catch (IOException e) { showWarning(_("Error"), _("Error loading libraries"), e); } @@ -759,7 +763,7 @@ public class BaseNoGui { static public void populateImportToLibraryTable() { // Populate importToLibraryTable importToLibraryTable = new HashMap(); - for (UserLibrary lib : getLibraries()) { + for (UserLibrary lib : librariesIndexer.getInstalledLibraries()) { try { String headers[] = headerListFromIncludePath(lib.getSrcFolder()); for (String header : headers) { @@ -968,14 +972,6 @@ public class BaseNoGui { } } - static public void scanAndUpdateLibraries(List folders) throws IOException { - libraries = scanLibraries(folders); - } - - static public LibraryList scanLibraries(List folders) throws IOException { - return librariesIndexer.scanLibraries(folders); - } - static public void selectBoard(TargetBoard targetBoard) { TargetPlatform targetPlatform = targetBoard.getContainerPlatform(); TargetPackage targetPackage = targetPlatform.getContainerPackage(); diff --git a/arduino-core/src/processing/app/packages/LegacyUserLibrary.java b/arduino-core/src/processing/app/packages/LegacyUserLibrary.java index c5cf6284b..34f09ef9c 100644 --- a/arduino-core/src/processing/app/packages/LegacyUserLibrary.java +++ b/arduino-core/src/processing/app/packages/LegacyUserLibrary.java @@ -120,9 +120,7 @@ public class LegacyUserLibrary extends UserLibrary { @Override public String toString() { - String res = "LegacyLibrary:"; - res += " (name=" + name + ")"; - return res; + return "LegacyLibrary:" + name + "\n"; } @Override diff --git a/arduino-core/src/processing/app/packages/LibraryList.java b/arduino-core/src/processing/app/packages/LibraryList.java index 7bb5ec9f7..391c58b1a 100644 --- a/arduino-core/src/processing/app/packages/LibraryList.java +++ b/arduino-core/src/processing/app/packages/LibraryList.java @@ -30,7 +30,6 @@ package processing.app.packages; import java.io.File; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import processing.app.helpers.FileUtils; @@ -54,15 +53,14 @@ public class LibraryList extends ArrayList { } public void addOrReplace(UserLibrary lib) { - UserLibrary l = getByName(lib.getName()); - if (l != null) - remove(l); + remove(lib); add(lib); } - - public void addOrReplaceAll(Collection c) { - for (UserLibrary l : c) - addOrReplace(l); + + public void remove(UserLibrary lib) { + UserLibrary l = getByName(lib.getName()); + if (l != null) + super.remove(l); } public void sort() { diff --git a/arduino-core/src/processing/app/packages/UserLibrary.java b/arduino-core/src/processing/app/packages/UserLibrary.java index 1caaaf276..f92cd0087 100644 --- a/arduino-core/src/processing/app/packages/UserLibrary.java +++ b/arduino-core/src/processing/app/packages/UserLibrary.java @@ -257,15 +257,14 @@ public class UserLibrary extends ContributedLibrary { @Override public String toString() { - String res = "Library:"; - res += " (name=" + name + ")"; - res += " (version=" + version + ")"; - res += " (author=" + author + ")"; - res += " (maintainer=" + maintainer + ")"; - res += " (sentence=" + sentence + ")"; - res += " (paragraph=" + paragraph + ")"; - res += " (url=" + website + ")"; - res += " (architectures=" + architectures + ")"; + String res = "Library: " + name + "\n"; + res += " (version=" + version + ")\n"; + res += " (author=" + author + ")\n"; + res += " (maintainer=" + maintainer + ")\n"; + res += " (sentence=" + sentence + ")\n"; + res += " (paragraph=" + paragraph + ")\n"; + res += " (url=" + website + ")\n"; + res += " (architectures=" + architectures + ")\n"; return res; }