From 4cac0d3a0952078ce8ebd3fdedbefeb7803ac080 Mon Sep 17 00:00:00 2001 From: James Moger <james.moger@gitblit.com> Date: Fri, 28 Oct 2011 21:24:02 -0400 Subject: [PATCH] Added basic rss subscriptions to Manager --- src/com/gitblit/client/SyndicatedEntryTableModel.java | 121 ++++++++++ src/com/gitblit/client/GitblitClient.java | 135 ++++++++++ src/com/gitblit/client/GitblitPanel.java | 233 +++++++++++++++++- src/com/gitblit/client/DateCellRenderer.java | 21 + src/com/gitblit/client/Utils.java | 8 src/com/gitblit/build/Build.java | 2 src/com/gitblit/client/GitblitRegistration.java | 2 tests/com/gitblit/tests/SyndicationUtilsTest.java | 11 src/com/gitblit/client/RegistrationsDialog.java | 2 src/com/gitblit/utils/SyndicationUtils.java | 40 ++ src/com/gitblit/wicket/GitBlitWebApp.properties | 5 resources/bullet_feed.png | 0 src/com/gitblit/models/SyndicatedEntryModel.java | 59 ++++ docs/04_releases.mkd | 1 src/com/gitblit/models/RepositoryModel.java | 1 src/com/gitblit/client/NameRenderer.java | 46 +++ src/com/gitblit/client/StatusPanel.java | 2 build.xml | 2 src/com/gitblit/client/GitblitManager.java | 12 docs/00_index.mkd | 1 20 files changed, 640 insertions(+), 64 deletions(-) diff --git a/build.xml b/build.xml index cdb0ac3..5c3494b 100644 --- a/build.xml +++ b/build.xml @@ -448,6 +448,8 @@ <resource file="${basedir}/resources/book_16x16.png" /> <resource file="${basedir}/resources/bug_16x16.png" /> <resource file="${basedir}/resources/health_16x16.png" /> + <resource file="${basedir}/resources/feed_16x16.png" /> + <resource file="${basedir}/resources/bullet_feed.png" /> <resource file="${basedir}/resources/blank.png" /> <resource file="${basedir}/src/com/gitblit/wicket/GitBlitWebApp.properties" /> diff --git a/docs/00_index.mkd b/docs/00_index.mkd index 0985075..1080f09 100644 --- a/docs/00_index.mkd +++ b/docs/00_index.mkd @@ -52,6 +52,7 @@ - fixed: Gitblit can now browse the Linux kernel repository (issue 25) - fixed: Gitblit now runs on Servlet 3.0 webservers (e.g. Tomcat 7, Jetty 8) (issue 23) - fixed: Set the RSS content type of syndication feeds for Firefox 4 (issue 22) +- fixed: RSS feeds are now properly encoded to UTF-8 - fixed: Null pointer exception if did not set federation strategy (issue 20) - fixed: Gitblit GO allows SSL renegotiation if running on Java 1.6.0_22 or later - updated: MarkdownPapers 1.2.4 diff --git a/docs/04_releases.mkd b/docs/04_releases.mkd index 50612e6..778fd0e 100644 --- a/docs/04_releases.mkd +++ b/docs/04_releases.mkd @@ -26,6 +26,7 @@ - fixed: Gitblit can now browse the Linux kernel repository (issue 25) - fixed: Gitblit now runs on Servlet 3.0 webservers (e.g. Tomcat 7, Jetty 8) (issue 23) - fixed: Set the RSS content type of syndication feeds for Firefox 4 (issue 22) +- fixed: RSS feeds are now properly encoded to UTF-8 - fixed: Null pointer exception if did not set federation strategy (issue 20) - fixed: Gitblit GO allows SSL renegotiation if running on Java 1.6.0_22 or later - updated: MarkdownPapers 1.2.4 diff --git a/resources/bullet_feed.png b/resources/bullet_feed.png new file mode 100644 index 0000000..c6e5f24 --- /dev/null +++ b/resources/bullet_feed.png Binary files differ diff --git a/src/com/gitblit/build/Build.java b/src/com/gitblit/build/Build.java index 034efc1..4389eac 100644 --- a/src/com/gitblit/build/Build.java +++ b/src/com/gitblit/build/Build.java @@ -138,6 +138,8 @@ public static void manager(DownloadListener listener) { downloadListener = listener; downloadFromApache(MavenObject.GSON, BuildType.RUNTIME); + downloadFromApache(MavenObject.ROME, BuildType.RUNTIME); + downloadFromApache(MavenObject.JDOM, BuildType.RUNTIME); downloadFromApache(MavenObject.JSCH, BuildType.RUNTIME); downloadFromEclipse(MavenObject.JGIT, BuildType.RUNTIME); diff --git a/src/com/gitblit/client/DateCellRenderer.java b/src/com/gitblit/client/DateCellRenderer.java index f30b05f..3d0ab15 100644 --- a/src/com/gitblit/client/DateCellRenderer.java +++ b/src/com/gitblit/client/DateCellRenderer.java @@ -49,18 +49,23 @@ super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); if (value instanceof Date) { Date date = (Date) value; - String timeAgo; - String strDate; + String title; + String dateString; if (date.getTime() == 0) { - timeAgo = "--"; - strDate = "never"; + title = "--"; + dateString = "never"; } else { - timeAgo = TimeUtils.timeAgo(date); - strDate = new SimpleDateFormat(pattern).format((Date) value); + title = TimeUtils.timeAgo(date); + dateString = new SimpleDateFormat(pattern).format((Date) value); } - this.setText(timeAgo); - this.setToolTipText(strDate); + if ((System.currentTimeMillis() - date.getTime()) > 10 * 24 * 60 * 60 * 1000L) { + String tmp = dateString; + dateString = title; + title = tmp; + } + this.setText(title); + this.setToolTipText(dateString); } return this; } diff --git a/src/com/gitblit/client/GitblitClient.java b/src/com/gitblit/client/GitblitClient.java index 9f4dd3e..ccde45a 100644 --- a/src/com/gitblit/client/GitblitClient.java +++ b/src/com/gitblit/client/GitblitClient.java @@ -19,8 +19,10 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import com.gitblit.GitBlitException.ForbiddenException; import com.gitblit.GitBlitException.NotAllowedException; @@ -31,8 +33,11 @@ import com.gitblit.models.RepositoryModel; import com.gitblit.models.ServerSettings; import com.gitblit.models.ServerStatus; +import com.gitblit.models.SyndicatedEntryModel; import com.gitblit.models.UserModel; import com.gitblit.utils.RpcUtils; +import com.gitblit.utils.StringUtils; +import com.gitblit.utils.SyndicationUtils; /** * GitblitClient is a object that retrieves data from a Gitblit server, caches @@ -44,6 +49,8 @@ public class GitblitClient implements Serializable { private static final long serialVersionUID = 1L; + + protected final GitblitRegistration reg; public final String url; @@ -63,22 +70,35 @@ private final List<FederationModel> federationRegistrations; + private final List<SyndicatedEntryModel> syndicatedEntries; + private ServerStatus status; - public GitblitClient(String url, String account, char[] password) { - this.url = url; - this.account = account; - this.password = password; + public GitblitClient(GitblitRegistration reg) { + this.reg = reg; + this.url = reg.url; + this.account = reg.account; + this.password = reg.password; this.allUsers = new ArrayList<UserModel>(); this.allRepositories = new ArrayList<RepositoryModel>(); this.federationRegistrations = new ArrayList<FederationModel>(); + this.syndicatedEntries = new ArrayList<SyndicatedEntryModel>(); } public void login() throws IOException { refreshRepositories(); try { + // RSS feeds may be disabled by server + refreshSubscribedFeeds(); + } catch (IOException e) { + e.printStackTrace(); + } + + try { + // credentials may not have administrator access + // or server may have disabled rpc management refreshUsers(); refreshSettings(); allowManagement = true; @@ -91,6 +111,8 @@ } try { + // credentials may not have administrator access + // or server may have disabled rpc administration refreshStatus(); allowAdministration = true; } catch (UnauthorizedException e) { @@ -133,6 +155,7 @@ allRepositories.clear(); allRepositories.addAll(repositories.values()); Collections.sort(allRepositories); + updateSubscribedStates(); return allRepositories; } @@ -153,6 +176,110 @@ return status; } + public List<SyndicatedEntryModel> refreshSubscribedFeeds() throws IOException { + Set<SyndicatedEntryModel> allFeeds = new HashSet<SyndicatedEntryModel>(); + if (reg.feeds != null && reg.feeds.size() > 0) { + for (String feed : reg.feeds) { + String[] values = feed.split(":"); + String repository = values[0]; + String branch = null; + if (values.length > 1) { + branch = values[1]; + } + List<SyndicatedEntryModel> list = SyndicationUtils.readFeed(url, repository, + branch, -1, account, password); + allFeeds.addAll(list); + } + } + syndicatedEntries.clear(); + syndicatedEntries.addAll(allFeeds); + Collections.sort(syndicatedEntries); + return syndicatedEntries; + } + + private void updateSubscribedStates() { + if (reg.feeds != null) { + Set<String> subscribedRepositories = new HashSet<String>(); + for (String feed : reg.feeds) { + if (feed.indexOf(':') > -1) { + // strip branch + subscribedRepositories.add(feed.substring(0, feed.indexOf(':')).toLowerCase()); + } else { + // default branch + subscribedRepositories.add(feed.toLowerCase()); + } + } + // set subscribed flag + for (RepositoryModel repository : allRepositories) { + repository.subscribed = subscribedRepositories.contains(repository.name + .toLowerCase()); + } + } + } + + public List<SyndicatedEntryModel> getSyndicatedEntries() { + return syndicatedEntries; + } + + public boolean isSubscribed(RepositoryModel repository, String branch) { + if (reg.feeds != null && reg.feeds.size() > 0) { + for (String feed : reg.feeds) { + String[] values = feed.split(":"); + String repositoryName = values[0]; + if (repository.name.equalsIgnoreCase(repositoryName)) { + return true; + } + // TODO check branch subscriptions + String branchName = null; + if (values.length > 1) { + branchName = values[1]; + } + } + } + return false; + } + + public boolean subscribe(RepositoryModel repository, String branch) { + String feed = repository.name; + if (!StringUtils.isEmpty(branch)) { + feed += ":" + branch; + } + if (reg.feeds == null) { + reg.feeds = new ArrayList<String>(); + } + reg.feeds.add(feed); + updateSubscribedStates(); + return true; + } + + public boolean unsubscribe(RepositoryModel repository, String branch) { + String feed = repository.name; + if (!StringUtils.isEmpty(branch)) { + feed += ":" + branch; + } + reg.feeds.remove(feed); + if (syndicatedEntries.size() > 0) { + List<SyndicatedEntryModel> toRemove = new ArrayList<SyndicatedEntryModel>(); + for (SyndicatedEntryModel model : syndicatedEntries) { + if (model.repository.equalsIgnoreCase(repository.name)) { + boolean emptyUnsubscribeBranch = StringUtils.isEmpty(branch); + boolean emptyFromBranch = StringUtils.isEmpty(model.branch); + if (emptyUnsubscribeBranch && emptyFromBranch) { + // default branch, remove + toRemove.add(model); + } else if (!emptyUnsubscribeBranch && !emptyFromBranch) { + if (model.branch.equals(branch)) { + // specific branch, remove + toRemove.add(model); + } + } + } + } + } + updateSubscribedStates(); + return true; + } + public List<FederationModel> refreshFederationRegistrations() throws IOException { List<FederationModel> list = RpcUtils.getFederationRegistrations(url, account, password); federationRegistrations.clear(); diff --git a/src/com/gitblit/client/GitblitManager.java b/src/com/gitblit/client/GitblitManager.java index f16616a..a337040 100644 --- a/src/com/gitblit/client/GitblitManager.java +++ b/src/com/gitblit/client/GitblitManager.java @@ -31,6 +31,7 @@ import java.text.MessageFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Date; @@ -204,11 +205,11 @@ return; } } - + // login setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); final GitblitRegistration registration = reg; - final GitblitPanel panel = new GitblitPanel(registration); + final GitblitPanel panel = new GitblitPanel(registration, this); SwingWorker<Boolean, Void> worker = new SwingWorker<Boolean, Void>() { @Override @@ -303,6 +304,10 @@ password = new String(Base64.decode(pw)).toCharArray(); } GitblitRegistration reg = new GitblitRegistration(server, url, account, password); + String[] feeds = config.getStringList("servers", server, "feeds"); + if (feeds != null) { + reg.feeds = new ArrayList<String>(Arrays.asList(feeds)); + } reg.lastLogin = lastLogin; registrations.put(reg.name, reg); } @@ -333,6 +338,9 @@ if (reg.lastLogin != null) { config.setString("servers", reg.name, "lastLogin", dateFormat.format(reg.lastLogin)); } + if (reg.feeds != null) { + config.setStringList("servers", reg.name, "feeds", reg.feeds); + } config.save(); return true; } catch (Throwable t) { diff --git a/src/com/gitblit/client/GitblitPanel.java b/src/com/gitblit/client/GitblitPanel.java index 03ddfb3..198b24b 100644 --- a/src/com/gitblit/client/GitblitPanel.java +++ b/src/com/gitblit/client/GitblitPanel.java @@ -58,6 +58,7 @@ import com.gitblit.client.ClosableTabComponent.CloseTabListener; import com.gitblit.models.RepositoryModel; import com.gitblit.models.SettingModel; +import com.gitblit.models.SyndicatedEntryModel; import com.gitblit.models.UserModel; import com.gitblit.utils.StringUtils; @@ -75,6 +76,8 @@ private final int margin = 5; private final Insets insets = new Insets(margin, margin, margin, margin); + + private final RegistrationsDialog.RegistrationListener listener; private GitblitClient gitblit; @@ -96,14 +99,6 @@ private JButton delRepository; - private NameRenderer nameRenderer; - - private IndicatorsRenderer typeRenderer; - - private DefaultTableCellRenderer ownerRenderer; - - private DefaultTableCellRenderer sizeRenderer; - private TableRowSorter<RepositoriesTableModel> defaultRepositoriesSorter; private TableRowSorter<UsersTableModel> defaultUsersSorter; @@ -120,15 +115,19 @@ private StatusPanel statusPanel; - public GitblitPanel(GitblitRegistration reg) { - this(reg.url, reg.account, reg.password); - } + private SyndicatedEntryTableModel syndicationModel; - public GitblitPanel(String url, String account, char[] password) { - this.gitblit = new GitblitClient(url, account, password); + private HeaderPanel feedsHeader; + + private JTable syndicationEntriesTable; + + public GitblitPanel(GitblitRegistration reg, RegistrationsDialog.RegistrationListener listener) { + this.gitblit = new GitblitClient(reg); + this.listener = listener; tabs = new JTabbedPane(JTabbedPane.BOTTOM); tabs.addTab(Translation.get("gb.repositories"), createRepositoriesPanel()); + tabs.addTab(Translation.get("gb.recentCommits"), createFeedsPanel()); tabs.addTab(Translation.get("gb.users"), createUsersPanel()); tabs.addTab(Translation.get("gb.settings"), createSettingsPanel()); tabs.addTab(Translation.get("gb.status"), createStatusPanel()); @@ -183,20 +182,28 @@ } }); - nameRenderer = new NameRenderer(); - typeRenderer = new IndicatorsRenderer(); + final JButton subscribeRepository = new JButton(Translation.get("gb.subscribe") + "..."); + subscribeRepository.setEnabled(false); + subscribeRepository.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + subscribeRepository(getSelectedRepositories().get(0)); + } + }); - sizeRenderer = new DefaultTableCellRenderer(); + NameRenderer nameRenderer = new NameRenderer(true); + IndicatorsRenderer typeRenderer = new IndicatorsRenderer(); + + DefaultTableCellRenderer sizeRenderer = new DefaultTableCellRenderer(); sizeRenderer.setHorizontalAlignment(SwingConstants.RIGHT); sizeRenderer.setForeground(new Color(0, 0x80, 0)); - ownerRenderer = new DefaultTableCellRenderer(); + DefaultTableCellRenderer ownerRenderer = new DefaultTableCellRenderer(); ownerRenderer.setForeground(Color.gray); ownerRenderer.setHorizontalAlignment(SwingConstants.CENTER); repositoriesModel = new RepositoriesTableModel(); defaultRepositoriesSorter = new TableRowSorter<RepositoriesTableModel>(repositoriesModel); - repositoriesTable = Utils.newTable(repositoriesModel); + repositoriesTable = Utils.newTable(repositoriesModel, Utils.DATE_FORMAT); repositoriesTable.setRowHeight(nameRenderer.getFont().getSize() + 8); repositoriesTable.setRowSorter(defaultRepositoriesSorter); repositoriesTable.getRowSorter().toggleSortOrder( @@ -217,6 +224,7 @@ boolean selected = repositoriesTable.getSelectedRow() > -1; browseRepository.setEnabled(singleSelection); delRepository.setEnabled(selected); + subscribeRepository.setEnabled(singleSelection); if (selected) { int viewRow = repositoriesTable.getSelectedRow(); int modelRow = repositoriesTable.convertRowIndexToModel(viewRow); @@ -264,6 +272,7 @@ repositoryControls.add(createRepository); repositoryControls.add(editRepository); repositoryControls.add(delRepository); + repositoryControls.add(subscribeRepository); JPanel repositoriesPanel = new JPanel(new BorderLayout(margin, margin)) { @@ -290,6 +299,94 @@ repositoriesTable.getColumn(name).setMinWidth(maxWidth); repositoriesTable.getColumn(name).setMaxWidth(maxWidth); } + } + + private JPanel createFeedsPanel() { + JButton refreshFeeds = new JButton(Translation.get("gb.refresh")); + refreshFeeds.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + refreshFeeds(); + } + }); + + final JButton viewCommit = new JButton(Translation.get("gb.view")); + viewCommit.setEnabled(false); + viewCommit.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + viewCommit(); + } + }); + + final JButton viewCommitDiff = new JButton(Translation.get("gb.commitdiff")); + viewCommitDiff.setEnabled(false); + viewCommitDiff.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + viewCommitDiff(); + } + }); + + final JButton viewTree = new JButton(Translation.get("gb.tree")); + viewTree.setEnabled(false); + viewTree.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + viewTree(); + } + }); + + JPanel controls = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 0)); + controls.add(refreshFeeds); + controls.add(viewCommit); + controls.add(viewCommitDiff); + controls.add(viewTree); + + NameRenderer nameRenderer = new NameRenderer(); + syndicationModel = new SyndicatedEntryTableModel(); + feedsHeader = new HeaderPanel(Translation.get("gb.recentCommits"), "feed_16x16.png"); + syndicationEntriesTable = Utils.newTable(syndicationModel, Utils.DATE_FORMAT); + String name = syndicationEntriesTable + .getColumnName(SyndicatedEntryTableModel.Columns.Author.ordinal()); + syndicationEntriesTable.setRowHeight(nameRenderer.getFont().getSize() + 8); + syndicationEntriesTable.getColumn(name).setCellRenderer(nameRenderer); + + syndicationEntriesTable.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + if (e.isControlDown()) { + viewCommitDiff(); + } else { + viewCommit(); + } + } + } + }); + + syndicationEntriesTable.getSelectionModel().addListSelectionListener( + new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + if (e.getValueIsAdjusting()) { + return; + } + boolean singleSelection = syndicationEntriesTable.getSelectedRowCount() == 1; + viewCommit.setEnabled(singleSelection); + viewCommitDiff.setEnabled(singleSelection); + viewTree.setEnabled(singleSelection); + } + }); + + JPanel panel = new JPanel(new BorderLayout(5, 5)) { + + private static final long serialVersionUID = 1L; + + @Override + public Insets getInsets() { + return insets; + } + }; + panel.add(feedsHeader, BorderLayout.NORTH); + panel.add(new JScrollPane(syndicationEntriesTable), BorderLayout.CENTER); + panel.add(controls, BorderLayout.SOUTH); + return panel; } private JPanel createUsersPanel() { @@ -323,9 +420,10 @@ } }); + NameRenderer nameRenderer = new NameRenderer(); usersModel = new UsersTableModel(); defaultUsersSorter = new TableRowSorter<UsersTableModel>(usersModel); - usersTable = Utils.newTable(usersModel); + usersTable = Utils.newTable(usersModel, Utils.DATE_FORMAT); String name = usersTable.getColumnName(UsersTableModel.Columns.Name.ordinal()); usersTable.setRowHeight(nameRenderer.getFont().getSize() + 8); usersTable.getColumn(name).setCellRenderer(nameRenderer); @@ -414,10 +512,11 @@ } }); + NameRenderer nameRenderer = new NameRenderer(); final SettingPanel settingPanel = new SettingPanel(); settingsModel = new SettingsTableModel(); defaultSettingsSorter = new TableRowSorter<SettingsTableModel>(settingsModel); - settingsTable = Utils.newTable(settingsModel); + settingsTable = Utils.newTable(settingsModel, Utils.DATE_FORMAT); settingsTable.setDefaultRenderer(SettingModel.class, new SettingCellRenderer()); String name = settingsTable.getColumnName(UsersTableModel.Columns.Name.ordinal()); settingsTable.setRowHeight(nameRenderer.getFont().getSize() + 8); @@ -509,6 +608,9 @@ updateRepositoriesTable(); Utils.packColumns(repositoriesTable, 5); + updateFeedsTable(); + Utils.packColumns(syndicationEntriesTable, 5); + if (gitblit.allowManagement()) { updateUsersTable(); } else { @@ -548,6 +650,14 @@ repositoriesModel.fireTableDataChanged(); repositoriesHeader.setText(Translation.get("gb.repositories") + " (" + gitblit.getRepositories().size() + ")"); + } + + private void updateFeedsTable() { + syndicationModel.entries.clear(); + syndicationModel.entries.addAll(gitblit.getSyndicatedEntries()); + syndicationModel.fireTableDataChanged(); + feedsHeader.setText(Translation.get("gb.recentCommits") + " (" + + gitblit.getSyndicatedEntries().size() + ")"); } private void updateUsersTable() { @@ -811,6 +921,89 @@ } } + protected void subscribeRepository(final RepositoryModel repository) { + if (repository == null) { + return; + } + // TODO this is lame. need better ui. + if (gitblit.isSubscribed(repository, null)) { + // unsubscribe + String msg = MessageFormat.format("Do you want to unsubscribe from {0}?", + repository.name); + String[] options = { "no", "yes" }; + int result = JOptionPane.showOptionDialog(GitblitPanel.this, msg, "Unsubscribe?", + JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, + options[0]); + if (result == 1) { + if (gitblit.unsubscribe(repository, null)) { + updateFeedsTable(); + updateRepositoriesTable(); + listener.saveRegistration(repository.name, gitblit.reg); + } + } + } else { + // subscribe + String msg = MessageFormat.format("Do you want to subscribe to {0}?", repository.name); + String[] options = { "no", "yes" }; + int result = JOptionPane.showOptionDialog(GitblitPanel.this, msg, "Subscribe?", + JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, + options[0]); + if (result == 1) { + if (gitblit.subscribe(repository, null)) { + updateRepositoriesTable(); + listener.saveRegistration(repository.name, gitblit.reg); + } + } + } + } + + protected void refreshFeeds() { + // TODO change request type here + GitblitWorker worker = new GitblitWorker(GitblitPanel.this, RpcRequest.LIST_USERS) { + @Override + protected Boolean doRequest() throws IOException { + gitblit.refreshSubscribedFeeds(); + return true; + } + + @Override + protected void onSuccess() { + updateFeedsTable(); + } + }; + worker.execute(); + } + + protected SyndicatedEntryModel getSelectedSyndicatedEntry() { + int viewRow = syndicationEntriesTable.getSelectedRow(); + int modelRow = syndicationEntriesTable.convertRowIndexToModel(viewRow); + SyndicatedEntryModel entry = syndicationModel.get(modelRow); + return entry; + } + + protected void viewCommit() { + SyndicatedEntryModel entry = getSelectedSyndicatedEntry(); + browse(entry.link); + } + + protected void viewCommitDiff() { + SyndicatedEntryModel entry = getSelectedSyndicatedEntry(); + browse(entry.link.replace("/commit/", "/commitdiff/")); + } + + protected void viewTree() { + SyndicatedEntryModel entry = getSelectedSyndicatedEntry(); + browse(entry.link.replace("/commit/", "/tree/")); + } + + protected void browse(String url) { + try { + Desktop.getDesktop().browse(new URI(url)); + } catch (Exception x) { + x.printStackTrace(); + } + } + protected void refreshUsers() { GitblitWorker worker = new GitblitWorker(GitblitPanel.this, RpcRequest.LIST_USERS) { @Override diff --git a/src/com/gitblit/client/GitblitRegistration.java b/src/com/gitblit/client/GitblitRegistration.java index 0463c12..cbd4324 100644 --- a/src/com/gitblit/client/GitblitRegistration.java +++ b/src/com/gitblit/client/GitblitRegistration.java @@ -17,6 +17,7 @@ import java.io.Serializable; import java.util.Date; +import java.util.List; import com.gitblit.utils.StringUtils; @@ -36,6 +37,7 @@ char[] password; boolean savePassword; Date lastLogin; + List<String> feeds; public GitblitRegistration(String name, String url, String account, char[] password) { this.url = url; diff --git a/src/com/gitblit/client/NameRenderer.java b/src/com/gitblit/client/NameRenderer.java index 5b1a173..ce04255 100644 --- a/src/com/gitblit/client/NameRenderer.java +++ b/src/com/gitblit/client/NameRenderer.java @@ -18,8 +18,11 @@ import java.awt.Color; import java.awt.Component; +import javax.swing.ImageIcon; import javax.swing.JTable; import javax.swing.table.DefaultTableCellRenderer; + +import com.gitblit.models.RepositoryModel; /** * Repository name cell renderer. This renderer shows the group name in a gray @@ -34,11 +37,24 @@ final String groupSpan; + private final boolean displayIcon; + + private final ImageIcon blankIcon; + + private final ImageIcon subscribedIcon; + public NameRenderer() { - this(Color.gray, new Color(0x00, 0x69, 0xD6)); + this(false); } - public NameRenderer(Color group, Color repo) { + public NameRenderer(boolean showIcon) { + this(Color.gray, new Color(0x00, 0x69, 0xD6), showIcon); + } + + private NameRenderer(Color group, Color repo, boolean showIcon) { + blankIcon = new ImageIcon(getClass().getResource("/blank.png")); + subscribedIcon = new ImageIcon(getClass().getResource("/bullet_feed.png")); + displayIcon = showIcon; groupSpan = "<span style='color:" + getHexColor(group) + "'>"; setForeground(repo); } @@ -55,14 +71,26 @@ public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - String name = value.toString(); - int lastSlash = name.lastIndexOf('/'); - if (!isSelected && lastSlash > -1) { - String group = name.substring(0, lastSlash + 1); - String repo = name.substring(lastSlash + 1); - setText("<html><body>" + groupSpan + group + "</span>" + repo); + if (value instanceof RepositoryModel) { + RepositoryModel model = (RepositoryModel) value; + String name = value.toString(); + int lastSlash = name.lastIndexOf('/'); + if (!isSelected && lastSlash > -1) { + String group = name.substring(0, lastSlash + 1); + String repo = name.substring(lastSlash + 1); + setText("<html><body>" + groupSpan + group + "</span>" + repo); + } else { + this.setText(name); + } + if (displayIcon) { + if (model.subscribed) { + setIcon(subscribedIcon); + } else { + setIcon(blankIcon); + } + } } else { - this.setText(name); + this.setText(value.toString()); } return this; } diff --git a/src/com/gitblit/client/RegistrationsDialog.java b/src/com/gitblit/client/RegistrationsDialog.java index 0b87bb4..d40e333 100644 --- a/src/com/gitblit/client/RegistrationsDialog.java +++ b/src/com/gitblit/client/RegistrationsDialog.java @@ -92,7 +92,7 @@ private void initialize() { NameRenderer nameRenderer = new NameRenderer(); model = new RegistrationsTableModel(registrations); - registrationsTable = Utils.newTable(model); + registrationsTable = Utils.newTable(model, Utils.DATE_FORMAT); registrationsTable.setRowHeight(nameRenderer.getFont().getSize() + 8); String id = registrationsTable diff --git a/src/com/gitblit/client/StatusPanel.java b/src/com/gitblit/client/StatusPanel.java index b85d87a..797ae9b 100644 --- a/src/com/gitblit/client/StatusPanel.java +++ b/src/com/gitblit/client/StatusPanel.java @@ -92,7 +92,7 @@ fieldsPanel.add(createFieldPanel("gb.heapMaximum", heapMaximum)); model = new PropertiesTableModel(); - JTable propertiesTable = Utils.newTable(model); + JTable propertiesTable = Utils.newTable(model, Utils.DATE_FORMAT); String name = propertiesTable.getColumnName(PropertiesTableModel.Columns.Name.ordinal()); NameRenderer nameRenderer = new NameRenderer(); propertiesTable.setRowHeight(nameRenderer.getFont().getSize() + 8); diff --git a/src/com/gitblit/client/SyndicatedEntryTableModel.java b/src/com/gitblit/client/SyndicatedEntryTableModel.java new file mode 100644 index 0000000..4f25c9b --- /dev/null +++ b/src/com/gitblit/client/SyndicatedEntryTableModel.java @@ -0,0 +1,121 @@ +/* + * Copyright 2011 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.client; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import javax.swing.table.AbstractTableModel; + +import com.gitblit.models.SyndicatedEntryModel; + +/** + * Table model of List<SyndicatedEntryModel> + * + * @author James Moger + * + */ +public class SyndicatedEntryTableModel extends AbstractTableModel { + + private static final long serialVersionUID = 1L; + + List<SyndicatedEntryModel> entries; + + enum Columns { + Date, Repository, Author, Message; + + @Override + public String toString() { + return name().replace('_', ' '); + } + } + + public SyndicatedEntryTableModel() { + this(new ArrayList<SyndicatedEntryModel>()); + } + + public SyndicatedEntryTableModel(List<SyndicatedEntryModel> entries) { + setEntries(entries); + } + + public void setEntries(List<SyndicatedEntryModel> entries) { + this.entries = entries; + Collections.sort(entries); + } + + @Override + public int getRowCount() { + return entries.size(); + } + + @Override + public int getColumnCount() { + return Columns.values().length; + } + + @Override + public String getColumnName(int column) { + Columns col = Columns.values()[column]; + switch (col) { + case Date: + return Translation.get("gb.date"); + case Repository: + return Translation.get("gb.repository"); + case Author: + return Translation.get("gb.author"); + case Message: + return Translation.get("gb.message"); + } + return ""; + } + + /** + * Returns <code>Object.class</code> regardless of <code>columnIndex</code>. + * + * @param columnIndex + * the column being queried + * @return the Object.class + */ + public Class<?> getColumnClass(int columnIndex) { + if (Columns.Date.ordinal() == columnIndex) { + return Date.class; + } + return String.class; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + SyndicatedEntryModel entry = entries.get(rowIndex); + Columns col = Columns.values()[columnIndex]; + switch (col) { + case Date: + return entry.published; + case Repository: + return entry.repository; + case Author: + return entry.author; + case Message: + return entry.title; + } + return null; + } + + public SyndicatedEntryModel get(int modelRow) { + return entries.get(modelRow); + } +} diff --git a/src/com/gitblit/client/Utils.java b/src/com/gitblit/client/Utils.java index 786eb9f..1f44c32 100644 --- a/src/com/gitblit/client/Utils.java +++ b/src/com/gitblit/client/Utils.java @@ -36,15 +36,19 @@ import com.gitblit.Constants.RpcRequest; public class Utils { + + public final static String TIMESTAMP_FORMAT = "yyyy-MM-dd HH:mm"; + + public final static String DATE_FORMAT = "yyyy-MM-dd"; - public static JTable newTable(TableModel model) { + public static JTable newTable(TableModel model, String datePattern) { JTable table = new JTable(model); table.setCellSelectionEnabled(false); table.setRowSelectionAllowed(true); table.getTableHeader().setReorderingAllowed(false); table.setGridColor(new Color(0xd9d9d9)); table.setBackground(Color.white); - table.setDefaultRenderer(Date.class, new DateCellRenderer(null, Color.orange.darker())); + table.setDefaultRenderer(Date.class, new DateCellRenderer(datePattern, Color.orange.darker())); return table; } diff --git a/src/com/gitblit/models/RepositoryModel.java b/src/com/gitblit/models/RepositoryModel.java index 9a774fb..af42061 100644 --- a/src/com/gitblit/models/RepositoryModel.java +++ b/src/com/gitblit/models/RepositoryModel.java @@ -55,6 +55,7 @@ public String frequency; public String origin; public String size; + public boolean subscribed; public RepositoryModel() { this("", "", "", new Date(0)); diff --git a/src/com/gitblit/models/SyndicatedEntryModel.java b/src/com/gitblit/models/SyndicatedEntryModel.java new file mode 100644 index 0000000..a1c1221 --- /dev/null +++ b/src/com/gitblit/models/SyndicatedEntryModel.java @@ -0,0 +1,59 @@ +/* + * Copyright 2011 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.models; + +import java.io.Serializable; +import java.util.Date; + +/** + * SyndicationEntryModel represents an entry in a syndication (RSS) feed. + * + * @author James Moger + */ +public class SyndicatedEntryModel implements Serializable, Comparable<SyndicatedEntryModel> { + + public String repository; + public String branch; + public String title; + public String author; + public Date published; + public String link; + public String content; + public String contentType; + + private static final long serialVersionUID = 1L; + + public SyndicatedEntryModel() { + } + + @Override + public int compareTo(SyndicatedEntryModel o) { + return o.published.compareTo(published); + } + + @Override + public int hashCode() { + return link.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (o instanceof SyndicatedEntryModel) { + return hashCode() == o.hashCode(); + } + return false; + } +} diff --git a/src/com/gitblit/utils/SyndicationUtils.java b/src/com/gitblit/utils/SyndicationUtils.java index 3664a18..2237774 100644 --- a/src/com/gitblit/utils/SyndicationUtils.java +++ b/src/com/gitblit/utils/SyndicationUtils.java @@ -27,6 +27,8 @@ import org.eclipse.jgit.revwalk.RevCommit; import com.gitblit.Constants; +import com.gitblit.GitBlitException; +import com.gitblit.models.SyndicatedEntryModel; import com.sun.syndication.feed.synd.SyndContent; import com.sun.syndication.feed.synd.SyndContentImpl; import com.sun.syndication.feed.synd.SyndEntry; @@ -64,6 +66,7 @@ SyndFeed feed = new SyndFeedImpl(); feed.setFeedType("rss_2.0"); + feed.setEncoding("UTF-8"); feed.setTitle(title); feed.setLink(MessageFormat.format("{0}/summary/{1}", hostUrl, StringUtils.encodeURL(repository))); @@ -84,15 +87,14 @@ entry.setPublishedDate(commit.getCommitterIdent().getWhen()); SyndContent content = new SyndContentImpl(); - content.setType("text/html"); - String html = StringUtils.escapeForHtml(commit.getFullMessage(), false); - content.setValue(StringUtils.breakLinesForHtml(html)); + content.setType("text/plain"); + content.setValue(commit.getFullMessage()); entry.setDescription(content); entries.add(entry); } feed.setEntries(entries); - OutputStreamWriter writer = new OutputStreamWriter(os); + OutputStreamWriter writer = new OutputStreamWriter(os, "UTF-8"); SyndFeedOutput output = new SyndFeedOutput(); output.output(feed, writer); writer.close(); @@ -112,12 +114,11 @@ * is used. * @param username * @param password - * @return the JSON message as a string + * @return a list of SyndicationModel entries * @throws {@link IOException} */ - public static SyndFeed readFeed(String url, String repository, String branch, - int numberOfEntries, String username, char[] password) throws IOException, - FeedException { + public static List<SyndicatedEntryModel> readFeed(String url, String repository, String branch, + int numberOfEntries, String username, char[] password) throws IOException { String feedUrl; if (StringUtils.isEmpty(branch)) { // no branch specified @@ -142,8 +143,27 @@ URLConnection conn = ConnectionUtils.openReadConnection(feedUrl, username, password); InputStream is = conn.getInputStream(); SyndFeedInput input = new SyndFeedInput(); - SyndFeed feed = input.build(new XmlReader(is)); + SyndFeed feed = null; + try { + feed = input.build(new XmlReader(is)); + } catch (FeedException f) { + throw new GitBlitException(f); + } is.close(); - return feed; + List<SyndicatedEntryModel> entries = new ArrayList<SyndicatedEntryModel>(); + for (Object o : feed.getEntries()) { + SyndEntryImpl entry = (SyndEntryImpl) o; + SyndicatedEntryModel model = new SyndicatedEntryModel(); + model.repository = repository; + model.branch = branch; + model.title = entry.getTitle(); + model.author = entry.getAuthor(); + model.published = entry.getPublishedDate(); + model.link = entry.getLink(); + model.content = entry.getDescription().getValue(); + model.contentType = entry.getDescription().getType(); + entries.add(model); + } + return entries; } } diff --git a/src/com/gitblit/wicket/GitBlitWebApp.properties b/src/com/gitblit/wicket/GitBlitWebApp.properties index 267fb56..e16b44d 100644 --- a/src/com/gitblit/wicket/GitBlitWebApp.properties +++ b/src/com/gitblit/wicket/GitBlitWebApp.properties @@ -175,4 +175,7 @@ gb.heapUsed = used heap gb.free = free gb.version = version -gb.releaseDate = release date \ No newline at end of file +gb.releaseDate = release date +gb.date = date +gb.recentCommits = recent commits +gb.subscribe = subscribe \ No newline at end of file diff --git a/tests/com/gitblit/tests/SyndicationUtilsTest.java b/tests/com/gitblit/tests/SyndicationUtilsTest.java index 33d7dbd..6084b99 100644 --- a/tests/com/gitblit/tests/SyndicationUtilsTest.java +++ b/tests/com/gitblit/tests/SyndicationUtilsTest.java @@ -23,10 +23,9 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; -import com.gitblit.client.GitblitFeed; +import com.gitblit.models.SyndicatedEntryModel; import com.gitblit.utils.JGitUtils; import com.gitblit.utils.SyndicationUtils; -import com.sun.syndication.feed.synd.SyndFeed; public class SyndicationUtilsTest extends TestCase { @@ -44,10 +43,10 @@ } public void testFeedRead() throws Exception { - GitblitFeed reader = new GitblitFeed("https://localhost:8443", "ticgit.git", "master"); - SyndFeed feed = reader.update(5, "admin", "admin".toCharArray()); + List<SyndicatedEntryModel> feed = SyndicationUtils.readFeed("https://localhost:8443", + "ticgit.git", "master", 5, "admin", "admin".toCharArray()); assertTrue(feed != null); - assertTrue(feed.getEntries().size() > 0); - assertEquals(5, feed.getEntries().size()); + assertTrue(feed.size() > 0); + assertEquals(5, feed.size()); } } \ No newline at end of file -- Gitblit v1.9.1