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 extends UserLibrary> 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;
}