From e33b91aa4d43246ad62832e66e2acfad3dfb3608 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Fri, 04 Nov 2011 22:28:32 -0400
Subject: [PATCH] Support pagination in RSS feeds. Standardize pg as page parameter.

---
 docs/02_rpc.mkd                                   |    3 
 src/com/gitblit/utils/SyndicationUtils.java       |   16 +++
 src/com/gitblit/wicket/WicketUtils.java           |   14 +-
 src/com/gitblit/client/GitblitClient.java         |   16 +--
 src/com/gitblit/client/SearchDialog.java          |   49 ++++++++++-
 src/com/gitblit/client/FeedsPanel.java            |   70 +++++++++++++----
 src/com/gitblit/SyndicationServlet.java           |   14 ++
 tests/com/gitblit/tests/SyndicationUtilsTest.java |   22 ++++-
 8 files changed, 151 insertions(+), 53 deletions(-)

diff --git a/docs/02_rpc.mkd b/docs/02_rpc.mkd
index bb58d68..d1bb1f3 100644
--- a/docs/02_rpc.mkd
+++ b/docs/02_rpc.mkd
@@ -42,6 +42,7 @@
 <tr><td><em>repository</em></td><td><em>required</em></td><td>repository name is part of the url (see examples below)</td></tr>
 <tr><td>h=</td><td><em>optional</em><br/>default: HEAD</td><td>starting branch, ref, or commit id</td></tr>
 <tr><td>l=</td><td><em>optional</em><br/>default: web.syndicationEntries</td><td>maximum return count</td></tr>
+<tr><td>pg=</td><td><em>optional</em><br/>default: 0</td><td>page number for paging<br/>(offset into history = pagenumber*maximum return count)</td></tr>
 <tr><td colspan='3'><b>search query</b></td></tr>
 <tr><td>s=</td><td><em>required</em></td><td>search string</td></tr>
 <tr><td>st=</td><td><em>optional</em><br/>default: COMMIT</td><td>search type</td></tr>
@@ -51,7 +52,7 @@
 
     https://localhost:8443/feed/gitblit.git?l=50&h=refs/heads/master
     https://localhost:8443/feed/gitblit.git?l=50&h=refs/heads/master&s=documentation
-    https://localhost:8443/feed/gitblit.git?l=50&h=refs/heads/master&s=james&st=author
+    https://localhost:8443/feed/gitblit.git?l=50&h=refs/heads/master&s=james&st=author&pg=2
 
 ## JSON Remote Procedure Call (RPC) Interface
 
diff --git a/src/com/gitblit/SyndicationServlet.java b/src/com/gitblit/SyndicationServlet.java
index 128df43..39e37ca 100644
--- a/src/com/gitblit/SyndicationServlet.java
+++ b/src/com/gitblit/SyndicationServlet.java
@@ -129,6 +129,7 @@
 		String repositoryName = url;
 		String objectId = request.getParameter("h");
 		String l = request.getParameter("l");
+		String page = request.getParameter("pg");
 		String searchString = request.getParameter("s");
 		Constants.SearchType searchType = Constants.SearchType.COMMIT;
 		if (!StringUtils.isEmpty(request.getParameter("st"))) {
@@ -147,6 +148,13 @@
 			} catch (NumberFormatException x) {
 			}
 		}
+		int offset = 0;
+		if (!StringUtils.isEmpty(page)) {
+			try {
+				offset = length * Integer.parseInt(page);
+			} catch (NumberFormatException x) {
+			}
+		}
 
 		response.setContentType("application/rss+xml; charset=UTF-8");
 		Repository repository = GitBlit.self().getRepository(repositoryName);
@@ -154,11 +162,11 @@
 		List<RevCommit> commits;
 		if (StringUtils.isEmpty(searchString)) {
 			// standard log/history lookup
-			commits = JGitUtils.getRevLog(repository, objectId, 0, length);
+			commits = JGitUtils.getRevLog(repository, objectId, offset, length);
 		} else {
 			// repository search
-			commits = JGitUtils.searchRevlogs(repository, objectId, searchString, searchType, 0,
-					length);
+			commits = JGitUtils.searchRevlogs(repository, objectId, searchString, searchType,
+					offset, length);
 		}
 		Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(repository);
 		List<SyndicatedEntryModel> entries = new ArrayList<SyndicatedEntryModel>();
diff --git a/src/com/gitblit/client/FeedsPanel.java b/src/com/gitblit/client/FeedsPanel.java
index a8094f8..97764db 100644
--- a/src/com/gitblit/client/FeedsPanel.java
+++ b/src/com/gitblit/client/FeedsPanel.java
@@ -18,6 +18,7 @@
 import java.awt.BorderLayout;
 import java.awt.FlowLayout;
 import java.awt.Insets;
+import java.awt.Rectangle;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.MouseAdapter;
@@ -76,6 +77,12 @@
 
 	private JComboBox authorSelector;
 
+	private int page;
+
+	private JButton prev;
+
+	private JButton next;
+
 	public FeedsPanel(GitblitClient gitblit) {
 		super();
 		this.gitblit = gitblit;
@@ -83,10 +90,29 @@
 	}
 
 	private void initialize() {
+
+		prev = new JButton("<");
+		prev.setToolTipText(Translation.get("gb.pagePrevious"));
+		prev.setEnabled(false);
+		prev.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent e) {
+				refreshFeeds(--page);
+			}
+		});
+
+		next = new JButton(">");
+		next.setToolTipText(Translation.get("gb.pageNext"));
+		next.setEnabled(false);
+		next.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent e) {
+				refreshFeeds(++page);
+			}
+		});
+
 		JButton refreshFeeds = new JButton(Translation.get("gb.refresh"));
 		refreshFeeds.addActionListener(new ActionListener() {
 			public void actionPerformed(ActionEvent e) {
-				refreshFeeds();
+				refreshFeeds(0);
 			}
 		});
 
@@ -205,6 +231,8 @@
 		northControls.add(repositorySelector);
 		northControls.add(new JLabel(Translation.get("gb.author")));
 		northControls.add(authorSelector);
+//		northControls.add(prev);
+//		northControls.add(next);
 
 		JPanel northPanel = new JPanel(new BorderLayout(0, Utils.MARGIN));
 		northPanel.add(header, BorderLayout.NORTH);
@@ -221,11 +249,12 @@
 		return Utils.INSETS;
 	}
 
-	protected void refreshFeeds() {
+	protected void refreshFeeds(final int page) {
+		this.page = page;
 		GitblitWorker worker = new GitblitWorker(FeedsPanel.this, null) {
 			@Override
 			protected Boolean doRequest() throws IOException {
-				gitblit.refreshSubscribedFeeds();
+				gitblit.refreshSubscribedFeeds(page);
 				return true;
 			}
 
@@ -244,24 +273,33 @@
 		tableModel.entries.addAll(gitblit.getSyndicatedEntries());
 		tableModel.fireTableDataChanged();
 		header.setText(Translation.get("gb.activity") + " ("
-				+ gitblit.getSyndicatedEntries().size() + ")");
+				+ gitblit.getSyndicatedEntries().size() + (page > 0 ? (", pg " + (page + 1)) : "")
+				+ ")");
 		if (pack) {
 			Utils.packColumns(table, Utils.MARGIN);
 		}
-		// determine unique repositories
-		Set<String> uniqueRepositories = new HashSet<String>();
-		for (SyndicatedEntryModel entry : tableModel.entries) {
-			uniqueRepositories.add(entry.repository);
+		table.scrollRectToVisible(new Rectangle(table.getCellRect(0, 0, true)));
+
+		if (page == 0) {
+			// determine unique repositories
+			Set<String> uniqueRepositories = new HashSet<String>();
+			for (SyndicatedEntryModel entry : tableModel.entries) {
+				uniqueRepositories.add(entry.repository);
+			}
+
+			// repositories
+			List<String> sortedRespositories = new ArrayList<String>(uniqueRepositories);
+			StringUtils.sortRepositorynames(sortedRespositories);
+			repositoryChoices.removeAllElements();
+			repositoryChoices.addElement(ALL);
+			for (String repo : sortedRespositories) {
+				repositoryChoices.addElement(repo);
+			}
 		}
 
-		// repositories
-		List<String> sortedRespositories = new ArrayList<String>(uniqueRepositories);
-		StringUtils.sortRepositorynames(sortedRespositories);
-		repositoryChoices.removeAllElements();
-		repositoryChoices.addElement(ALL);
-		for (String repo : sortedRespositories) {
-			repositoryChoices.addElement(repo);
-		}
+		// update pagination buttons
+		next.setEnabled(tableModel.entries.size() > 0);
+		prev.setEnabled(page > 0);
 	}
 
 	private void updateAuthors() {
diff --git a/src/com/gitblit/client/GitblitClient.java b/src/com/gitblit/client/GitblitClient.java
index e8460f5..588b6d8 100644
--- a/src/com/gitblit/client/GitblitClient.java
+++ b/src/com/gitblit/client/GitblitClient.java
@@ -101,13 +101,7 @@
 		refreshSettings();
 		refreshAvailableFeeds();
 		refreshRepositories();
-
-		try {
-			// RSS feeds may be disabled by server
-			refreshSubscribedFeeds();
-		} catch (IOException e) {
-			e.printStackTrace();
-		}
+		refreshSubscribedFeeds(0);
 
 		try {
 			// credentials may not have administrator access
@@ -253,14 +247,14 @@
 		return availableFeeds;
 	}
 
-	public List<SyndicatedEntryModel> refreshSubscribedFeeds() throws IOException {
+	public List<SyndicatedEntryModel> refreshSubscribedFeeds(int page) throws IOException {
 		Set<SyndicatedEntryModel> allEntries = new HashSet<SyndicatedEntryModel>();
 		if (reg.feeds.size() > 0) {
 			for (FeedModel feed : reg.feeds) {
 				feed.lastRefreshDate = feed.currentRefreshDate;
 				feed.currentRefreshDate = new Date();
 				List<SyndicatedEntryModel> entries = SyndicationUtils.readFeed(url,
-						feed.repository, feed.branch, -1, account, password);
+						feed.repository, feed.branch, -1, page, account, password);
 				allEntries.addAll(entries);
 			}
 		}
@@ -308,9 +302,9 @@
 	}
 
 	public List<SyndicatedEntryModel> search(String repository, String branch, String fragment,
-			Constants.SearchType type, int numberOfEntries) throws IOException {
+			Constants.SearchType type, int numberOfEntries, int page) throws IOException {
 		return SyndicationUtils.readSearchFeed(url, repository, branch, fragment, type,
-				numberOfEntries, account, password);
+				numberOfEntries, page, account, password);
 	}
 
 	public List<FederationModel> refreshFederationRegistrations() throws IOException {
diff --git a/src/com/gitblit/client/SearchDialog.java b/src/com/gitblit/client/SearchDialog.java
index 2f45611..5dbea78 100644
--- a/src/com/gitblit/client/SearchDialog.java
+++ b/src/com/gitblit/client/SearchDialog.java
@@ -18,6 +18,7 @@
 import java.awt.BorderLayout;
 import java.awt.FlowLayout;
 import java.awt.Insets;
+import java.awt.Rectangle;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.MouseAdapter;
@@ -77,6 +78,12 @@
 
 	private JComboBox maxHitsSelector;
 
+	private int page;
+
+	private JButton prev;
+
+	private JButton next;
+
 	public SearchDialog(GitblitClient gitblit) {
 		super();
 		this.gitblit = gitblit;
@@ -88,10 +95,28 @@
 
 	private void initialize() {
 
+		prev = new JButton("<");
+		prev.setToolTipText(Translation.get("gb.pagePrevious"));
+		prev.setEnabled(false);
+		prev.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent e) {
+				search(--page);
+			}
+		});
+
+		next = new JButton(">");
+		next.setToolTipText(Translation.get("gb.pageNext"));
+		next.setEnabled(false);
+		next.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent e) {
+				search(++page);
+			}
+		});
+
 		final JButton search = new JButton(Translation.get("gb.search"));
 		search.addActionListener(new ActionListener() {
 			public void actionPerformed(ActionEvent e) {
-				search();
+				search(0);
 			}
 		});
 
@@ -194,12 +219,12 @@
 		searchTypeSelector.setSelectedItem(Constants.SearchType.COMMIT);
 
 		maxHitsSelector = new JComboBox(new Integer[] { 25, 50, 75, 100 });
-		maxHitsSelector.setSelectedIndex(-1);
+		maxHitsSelector.setSelectedIndex(0);
 
 		searchFragment = new JTextField(25);
 		searchFragment.addActionListener(new ActionListener() {
 			public void actionPerformed(ActionEvent event) {
-				search();
+				search(0);
 			}
 		});
 
@@ -214,6 +239,8 @@
 		northControls.add(maxHitsSelector);
 		northControls.add(searchFragment);
 		northControls.add(search);
+		northControls.add(prev);
+		northControls.add(next);
 
 		JPanel northPanel = new JPanel(new BorderLayout(0, Utils.MARGIN));
 		northPanel.add(header, BorderLayout.NORTH);
@@ -263,7 +290,8 @@
 		}
 	}
 
-	protected void search() {
+	protected void search(final int page) {
+		this.page = page;
 		final String repository = repositorySelector.getSelectedItem().toString();
 		final String branch = branchSelector.getSelectedIndex() > -1 ? branchSelector
 				.getSelectedItem().toString() : null;
@@ -272,13 +300,15 @@
 		final String fragment = searchFragment.getText();
 		final int maxEntryCount = maxHitsSelector.getSelectedIndex() > -1 ? ((Integer) maxHitsSelector
 				.getSelectedItem()) : -1;
+
 		if (StringUtils.isEmpty(fragment)) {
 			return;
 		}
 		SwingWorker<List<SyndicatedEntryModel>, Void> worker = new SwingWorker<List<SyndicatedEntryModel>, Void>() {
 			@Override
 			protected List<SyndicatedEntryModel> doInBackground() throws IOException {
-				return gitblit.search(repository, branch, fragment, searchType, maxEntryCount);
+				return gitblit
+						.search(repository, branch, fragment, searchType, maxEntryCount, page);
 			}
 
 			@Override
@@ -298,11 +328,18 @@
 		tableModel.entries.clear();
 		tableModel.entries.addAll(entries);
 		tableModel.fireTableDataChanged();
-		setTitle(Translation.get("gb.search") + ": " + fragment + " (" + entries.size() + ")");
+		setTitle(Translation.get("gb.search") + ": " + fragment + " (" + entries.size()
+				+ (page > 0 ? (", pg " + (page + 1)) : "") + ")");
 		header.setText(getTitle());
 		if (pack) {
 			Utils.packColumns(table, Utils.MARGIN);
 		}
+		table.scrollRectToVisible(new Rectangle(table.getCellRect(0, 0, true)));
+
+		// update pagination buttons
+		int maxHits = (Integer) maxHitsSelector.getSelectedItem();
+		next.setEnabled(entries.size() == maxHits);
+		prev.setEnabled(page > 0);
 	}
 
 	protected SyndicatedEntryModel getSelectedSyndicatedEntry() {
diff --git a/src/com/gitblit/utils/SyndicationUtils.java b/src/com/gitblit/utils/SyndicationUtils.java
index 4ba5622..6919cd2 100644
--- a/src/com/gitblit/utils/SyndicationUtils.java
+++ b/src/com/gitblit/utils/SyndicationUtils.java
@@ -100,7 +100,7 @@
 			content.setType(entryModel.contentType);
 			content.setValue(entryModel.content);
 			entry.setDescription(content);
-			
+
 			entries.add(entry);
 		}
 		feed.setEntries(entries);
@@ -123,17 +123,22 @@
 	 * @param numberOfEntries
 	 *            the number of entries to retrieve. if <= 0 the server default
 	 *            is used.
+	 * @param page
+	 *            0-indexed. used to paginate the results.
 	 * @param username
 	 * @param password
 	 * @return a list of SyndicationModel entries
 	 * @throws {@link IOException}
 	 */
 	public static List<SyndicatedEntryModel> readFeed(String url, String repository, String branch,
-			int numberOfEntries, String username, char[] password) throws IOException {
+			int numberOfEntries, int page, String username, char[] password) throws IOException {
 		// build feed url
 		List<String> parameters = new ArrayList<String>();
 		if (numberOfEntries > 0) {
 			parameters.add("l=" + numberOfEntries);
+		}
+		if (page > 0) {
+			parameters.add("pg=" + page);
 		}
 		if (!StringUtils.isEmpty(branch)) {
 			parameters.add("h=" + branch);
@@ -155,6 +160,8 @@
 	 * @param numberOfEntries
 	 *            the number of entries to retrieve. if <= 0 the server default
 	 *            is used.
+	 * @param page
+	 *            0-indexed. used to paginate the results.
 	 * @param username
 	 * @param password
 	 * @return a list of SyndicationModel entries
@@ -162,13 +169,16 @@
 	 */
 	public static List<SyndicatedEntryModel> readSearchFeed(String url, String repository,
 			String branch, String fragment, Constants.SearchType searchType, int numberOfEntries,
-			String username, char[] password) throws IOException {
+			int page, String username, char[] password) throws IOException {
 		// determine parameters
 		List<String> parameters = new ArrayList<String>();
 		parameters.add("s=" + StringUtils.encodeURL(fragment));
 		if (numberOfEntries > 0) {
 			parameters.add("l=" + numberOfEntries);
 		}
+		if (page > 0) {
+			parameters.add("pg=" + page);
+		}
 		if (!StringUtils.isEmpty(branch)) {
 			parameters.add("h=" + branch);
 		}
diff --git a/src/com/gitblit/wicket/WicketUtils.java b/src/com/gitblit/wicket/WicketUtils.java
index f47663c..37b4447 100644
--- a/src/com/gitblit/wicket/WicketUtils.java
+++ b/src/com/gitblit/wicket/WicketUtils.java
@@ -284,9 +284,9 @@
 			return newObjectParameter(repositoryName, objectId);
 		}
 		if (StringUtils.isEmpty(objectId)) {
-			return new PageParameters("r=" + repositoryName + ",page=" + pageNumber);
+			return new PageParameters("r=" + repositoryName + ",pg=" + pageNumber);
 		}
-		return new PageParameters("r=" + repositoryName + ",h=" + objectId + ",page=" + pageNumber);
+		return new PageParameters("r=" + repositoryName + ",h=" + objectId + ",pg=" + pageNumber);
 	}
 
 	public static PageParameters newHistoryPageParameter(String repositoryName, String objectId,
@@ -295,10 +295,10 @@
 			return newObjectParameter(repositoryName, objectId);
 		}
 		if (StringUtils.isEmpty(objectId)) {
-			return new PageParameters("r=" + repositoryName + ",f=" + path + ",page=" + pageNumber);
+			return new PageParameters("r=" + repositoryName + ",f=" + path + ",pg=" + pageNumber);
 		}
 		return new PageParameters("r=" + repositoryName + ",h=" + objectId + ",f=" + path
-				+ ",page=" + pageNumber);
+				+ ",pg=" + pageNumber);
 	}
 
 	public static PageParameters newBlobDiffParameter(String repositoryName, String baseCommitId,
@@ -323,10 +323,10 @@
 			String search, Constants.SearchType type, int pageNumber) {
 		if (StringUtils.isEmpty(commitId)) {
 			return new PageParameters("r=" + repositoryName + ",s=" + search + ",st=" + type.name()
-					+ ",page=" + pageNumber);
+					+ ",pg=" + pageNumber);
 		}
 		return new PageParameters("r=" + repositoryName + ",h=" + commitId + ",s=" + search
-				+ ",st=" + type.name() + ",page=" + pageNumber);
+				+ ",st=" + type.name() + ",pg=" + pageNumber);
 	}
 
 	public static String getRepositoryName(PageParameters params) {
@@ -355,7 +355,7 @@
 
 	public static int getPage(PageParameters params) {
 		// index from 1
-		return params.getInt("page", 1);
+		return params.getInt("pg", 1);
 	}
 
 	public static String getUsername(PageParameters params) {
diff --git a/tests/com/gitblit/tests/SyndicationUtilsTest.java b/tests/com/gitblit/tests/SyndicationUtilsTest.java
index 98bdd4b..e0a32bf 100644
--- a/tests/com/gitblit/tests/SyndicationUtilsTest.java
+++ b/tests/com/gitblit/tests/SyndicationUtilsTest.java
@@ -18,7 +18,9 @@
 import java.io.ByteArrayOutputStream;
 import java.util.ArrayList;
 import java.util.Date;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 import junit.framework.TestCase;
 
@@ -51,16 +53,24 @@
 	}
 
 	public void testFeedRead() throws Exception {
-		List<SyndicatedEntryModel> feed = SyndicationUtils.readFeed("https://localhost:8443",
-				"ticgit.git", "master", 5, "admin", "admin".toCharArray());
-		assertTrue(feed != null);
-		assertTrue(feed.size() > 0);
-		assertEquals(5, feed.size());
+		Set<String> links = new HashSet<String>();
+		for (int i = 0; i < 2; i++) {
+			List<SyndicatedEntryModel> feed = SyndicationUtils.readFeed("https://localhost:8443",
+					"ticgit.git", "master", 5, i, "admin", "admin".toCharArray());
+			assertTrue(feed != null);
+			assertTrue(feed.size() > 0);
+			assertEquals(5, feed.size());
+			for (SyndicatedEntryModel entry : feed) {
+				links.add(entry.link);
+			}
+		}
+		// confirm we have 10 unique commits
+		assertEquals("Feed pagination failed", 10, links.size());
 	}
 
 	public void testSearchFeedRead() throws Exception {
 		List<SyndicatedEntryModel> feed = SyndicationUtils.readSearchFeed("https://localhost:8443",
-				"ticgit.git", null, "documentation", null, 5, "admin", "admin".toCharArray());
+				"ticgit.git", null, "documentation", null, 5, 0, "admin", "admin".toCharArray());
 		assertTrue(feed != null);
 		assertTrue(feed.size() > 0);
 		assertEquals(2, feed.size());

--
Gitblit v1.9.1