diff --git a/README.md b/README.md index 10725ba..f822523 100644 --- a/README.md +++ b/README.md @@ -5,28 +5,28 @@ These set of Java classes allow you to easily talk to the CloudApp REST Apis. Usage Example ------------- - CloudApi api = new CloudApiImpl("Your email here", "Your password here"); + CloudApp api = new CloudAppImpl("Your email here", "Your password here"); // Add a new bookmark - JSONObject json = api.createBookmark('Simon Gaeremynck's portfolio', 'gaeremynck.com'); + CloudAppItem bookmark = api.createBookmark('Simon Gaeremynck's portfolio', 'gaeremynck.com'); // Add file - JSONObject json = api.uploadFile(new File("/path/to/file")); + CloudAppItem file = api.uploadFile(new File("/path/to/file")); // Get the first 10 items, regardless of category who aren't in the trash. - JSONArray array = api.getItems(1, 10, null, false); + List = api.getItems(1, 10, null, false); - // Get a specific item (http://cl.ly/bD5) - JSONObject json = api.getItem('bD5'); + // Get a specific item (http://cl.ly/2wr4) + CloudAppItem item = api.getItem('http://cl.ly/2wr4'); - // Delete item (by 'href' value) - api.deleteItem("http://my.cl.ly/items/1058986"); + // Delete an item + api.delete(item); Requirements ------------ -Java -Maven (only for building) +* Java +* Maven (only for building) Building @@ -36,8 +36,16 @@ A jar can be compiled trough maven by running the following command. However, this will skip the unit tests. If you want to have the unit tests, you will need to fill -in your email and password in the CloudApiTestCase.java file. -`mvn clean install` +in your email and password in src/test/resources/credentials.properties like so: +``` +cred_mail=me@email.com +cred_password=mypassword +``` + +Running a build can be done by running: +``` +mvn clean install +``` License diff --git a/pom.xml b/pom.xml index aa47a85..1fe616f 100644 --- a/pom.xml +++ b/pom.xml @@ -48,6 +48,12 @@ 1.6.1 compile + + org.apache.commons + commons-lang3 + 3.1 + compile + diff --git a/src/main/java/com/cloudapp/api/CloudApp.java b/src/main/java/com/cloudapp/api/CloudApp.java index a1995bb..fa5eb64 100644 --- a/src/main/java/com/cloudapp/api/CloudApp.java +++ b/src/main/java/com/cloudapp/api/CloudApp.java @@ -6,6 +6,7 @@ import com.cloudapp.api.model.CloudAppAccount; import com.cloudapp.api.model.CloudAppAccountStats; import com.cloudapp.api.model.CloudAppItem; +import com.cloudapp.api.model.CloudAppProgressListener; public interface CloudApp { @@ -58,7 +59,7 @@ public CloudAppAccount setPassword(String newPassword, String currentPassword) throws CloudAppException; /** - * Dispatch an email containing a link to reset the accountÕs password. + * Dispatch an email containing a link to reset the account's password. * * @see http://developer.getcloudapp.com/forgot-password * @param email @@ -94,7 +95,7 @@ public CloudAppAccount createAccount(String email, String password, boolean acce /** * Add or change the domain used for all links. Optionally, a URL may be provided to - * redirect visitors to the custom domainÕs root. Pro users only + * redirect visitors to the custom domain's root. Pro users only * * @see http://developer.getcloudapp.com/set-custom-domain * @param domain @@ -209,6 +210,18 @@ public List getItems(int page, int perPage, CloudAppItem.Type type */ public CloudAppItem upload(File file) throws CloudAppException; + /** + * + * @see http://developer.getcloudapp.com/upload-file + * @param file + * The file you wish to upload. + * @param listener + * To receive progress updates during upload + * @throws CloudAppException + * @return + */ + public CloudAppItem upload(File file, CloudAppProgressListener listener) throws CloudAppException; + /** * Deletes an item * diff --git a/src/main/java/com/cloudapp/api/model/CloudAppAccount.java b/src/main/java/com/cloudapp/api/model/CloudAppAccount.java index 9723b60..f719759 100644 --- a/src/main/java/com/cloudapp/api/model/CloudAppAccount.java +++ b/src/main/java/com/cloudapp/api/model/CloudAppAccount.java @@ -57,4 +57,9 @@ enum DefaultSecurity { */ public Date ActivatedAt() throws CloudAppException; + /** + * @return The date you suscription expires if any + */ + public Date SubscriptionExpiresAt() throws CloudAppException; + } diff --git a/src/main/java/com/cloudapp/api/model/CloudAppItem.java b/src/main/java/com/cloudapp/api/model/CloudAppItem.java index fe64d27..fa9ab14 100644 --- a/src/main/java/com/cloudapp/api/model/CloudAppItem.java +++ b/src/main/java/com/cloudapp/api/model/CloudAppItem.java @@ -86,6 +86,13 @@ enum Type { * @throws CloudAppExtion */ String getRedirectUrl() throws CloudAppException; + + /** + * @return A url that points to a thumbnail of this item if one is available, + * null otherwise. ie: "http://thumbs.cl.ly/2wr4" + * @throws CloudAppException + */ + String getThumbnailUrl() throws CloudAppException; /** * @return Identifies the app that uploaded this item. ie: @@ -104,7 +111,7 @@ enum Type { * @return When this item was last updated. (or null if it has not been) * @throws CloudAppExtion */ - Date getUploadedAt() throws CloudAppException; + Date getUpdatedAt() throws CloudAppException; /** * @return When this item was deleted. (or null if it has not been) diff --git a/src/main/java/com/cloudapp/api/model/CloudAppProgressListener.java b/src/main/java/com/cloudapp/api/model/CloudAppProgressListener.java new file mode 100644 index 0000000..91b82c2 --- /dev/null +++ b/src/main/java/com/cloudapp/api/model/CloudAppProgressListener.java @@ -0,0 +1,12 @@ +package com.cloudapp.api.model; + +/** + * Listener to receive notification as data's written to the output stream during upload. + */ +public interface CloudAppProgressListener { + void transferred(long written, long length); + + public static CloudAppProgressListener NO_OP = new CloudAppProgressListener() { + public void transferred(long written, long length) {} + }; +} diff --git a/src/main/java/com/cloudapp/impl/CloudAppImpl.java b/src/main/java/com/cloudapp/impl/CloudAppImpl.java index ef8c5d5..13044b2 100644 --- a/src/main/java/com/cloudapp/impl/CloudAppImpl.java +++ b/src/main/java/com/cloudapp/impl/CloudAppImpl.java @@ -3,6 +3,7 @@ import java.io.File; import java.util.List; +import com.cloudapp.api.model.CloudAppProgressListener; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.impl.DefaultConnectionReuseStrategy; @@ -186,6 +187,16 @@ public CloudAppItem upload(File file) throws CloudAppException { return items.upload(file); } + /** + * + * {@inheritDoc} + * + * @see com.cloudapp.api.CloudAppItems#upload(java.io.File, com.cloudapp.api.model.CloudAppProgressListener) + */ + public CloudAppItem upload(File file, CloudAppProgressListener listener) throws CloudAppException { + return items.upload(file, listener); + } + /** * * {@inheritDoc} diff --git a/src/main/java/com/cloudapp/impl/CloudAppInputStream.java b/src/main/java/com/cloudapp/impl/CloudAppInputStream.java index 6de8e12..99f4c2a 100644 --- a/src/main/java/com/cloudapp/impl/CloudAppInputStream.java +++ b/src/main/java/com/cloudapp/impl/CloudAppInputStream.java @@ -23,24 +23,29 @@ package com.cloudapp.impl; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.InputStream; +import java.io.*; +import com.cloudapp.api.model.CloudAppProgressListener; import org.apache.http.entity.mime.content.InputStreamBody; public class CloudAppInputStream extends InputStreamBody { - private long length; + private final long length; + private final CloudAppProgressListener listener; - public CloudAppInputStream(InputStream in, String filename, long length) { + public CloudAppInputStream(InputStream in, String filename, long length, CloudAppProgressListener listener) { super(in, filename); this.length = length; + this.listener = (listener == null) ? CloudAppProgressListener.NO_OP : listener; } - protected CloudAppInputStream(File file) throws FileNotFoundException { - this(new FileInputStream(file), file.getName(), file.length()); + protected CloudAppInputStream(File file, CloudAppProgressListener listener) throws FileNotFoundException { + this(new FileInputStream(file), file.getName(), file.length(), listener); + } + + @Override + public void writeTo(OutputStream out) throws IOException { + super.writeTo( new ListeningOutputStream(out) ); } @Override @@ -48,4 +53,34 @@ public long getContentLength() { return length; } + private class ListeningOutputStream extends FilterOutputStream { + + private long bytesWritten; + + public ListeningOutputStream(OutputStream out) { + super(out); + bytesWritten = 0L; + } + + @Override + public void write(int b) throws IOException { + out.write(b); + listener.transferred(++bytesWritten, length); + } + + @Override + public void write(byte[] b) throws IOException { + out.write(b); + bytesWritten += b.length; + listener.transferred(bytesWritten, length); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + out.write(b, off, len); + bytesWritten += (len - off); + listener.transferred(bytesWritten, length); + } + } + } diff --git a/src/main/java/com/cloudapp/impl/CloudAppItemsImpl.java b/src/main/java/com/cloudapp/impl/CloudAppItemsImpl.java index 298368e..3896d9f 100644 --- a/src/main/java/com/cloudapp/impl/CloudAppItemsImpl.java +++ b/src/main/java/com/cloudapp/impl/CloudAppItemsImpl.java @@ -6,6 +6,7 @@ import java.util.Iterator; import java.util.List; +import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpResponse; import org.apache.http.ParseException; import org.apache.http.client.ClientProtocolException; @@ -26,6 +27,7 @@ import org.slf4j.LoggerFactory; import com.cloudapp.api.CloudAppException; +import com.cloudapp.api.model.CloudAppProgressListener; import com.cloudapp.api.model.CloudAppItem; import com.cloudapp.impl.model.CloudAppItemImpl; @@ -108,21 +110,25 @@ public List getItems(int page, int perPage, CloudAppItem.Type type perPage = 5; if (page == 0) page = 1; - - HttpGet req = new HttpGet(ITEMS_URL); - req.addHeader("Accept", "application/json"); - HttpParams params = new BasicHttpParams(); - params.setIntParameter("page", page); - params.setIntParameter("per_page", perPage); - params.setBooleanParameter("deleted", showDeleted); - if (type != null) { - params.setParameter("type", type.toString().toLowerCase()); + + List params = new ArrayList(); + params.add("page="+page); + params.add("per_page="+perPage); + params.add("deleted="+ (showDeleted ? "true" : "false")); + + if (type != null) + { + params.add("type=" + type.toString().toLowerCase()); } - if (source != null) { - params.setParameter("source", source); + if (source != null) + { + params.add("source=" + source); } - req.setParams(params); + String queryString = StringUtils.join(params.iterator(), "&"); + HttpGet req = new HttpGet(ITEMS_URL + "?" + queryString); + req.addHeader("Accept", "application/json"); + HttpResponse response = client.execute(req); int status = response.getStatusLine().getStatusCode(); String responseBody = EntityUtils.toString(response.getEntity()); @@ -157,6 +163,10 @@ public List getItems(int page, int perPage, CloudAppItem.Type type * @see com.cloudapp.api.CloudAppItems#upload(java.io.File) */ public CloudAppItem upload(File file) throws CloudAppException { + return upload( file, CloudAppProgressListener.NO_OP ); + } + + public CloudAppItem upload(File file, CloudAppProgressListener listener) throws CloudAppException { try { // Do a GET request so we have the S3 endpoint HttpGet req = new HttpGet(NEW_ITEM_URL); @@ -177,7 +187,7 @@ public CloudAppItem upload(File file) throws CloudAppException { null); } - return uploadToAmazon(json, file); + return uploadToAmazon(json, file, listener); } catch (ClientProtocolException e) { LOGGER.error("Something went wrong trying to contact the CloudApp API.", e); @@ -204,7 +214,7 @@ public CloudAppItem upload(File file) throws CloudAppException { * @throws ParseException * @throws IOException */ - private CloudAppItem uploadToAmazon(JSONObject json, File file) throws JSONException, + private CloudAppItem uploadToAmazon(JSONObject json, File file, CloudAppProgressListener listener) throws JSONException, CloudAppException, ParseException, IOException { JSONObject params = json.getJSONObject("params"); MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE); @@ -218,7 +228,7 @@ private CloudAppItem uploadToAmazon(JSONObject json, File file) throws JSONExcep // Add the actual file. // We have to use the 'file' parameter for the S3 storage. - InputStreamBody stream = new CloudAppInputStream(file); + InputStreamBody stream = new CloudAppInputStream(file, listener); entity.addPart("file", stream); HttpPost uploadRequest = new HttpPost(json.getString("url")); diff --git a/src/main/java/com/cloudapp/impl/model/CloudAppAccountImpl.java b/src/main/java/com/cloudapp/impl/model/CloudAppAccountImpl.java index b0dfb80..e979e23 100644 --- a/src/main/java/com/cloudapp/impl/model/CloudAppAccountImpl.java +++ b/src/main/java/com/cloudapp/impl/model/CloudAppAccountImpl.java @@ -74,4 +74,14 @@ public Date ActivatedAt() throws CloudAppException { } } + @Override + public Date SubscriptionExpiresAt() throws CloudAppException { + try { + String d = getString("subscription_expires_at"); + return formatBis.parse(d); + } catch (ParseException e) { + throw new CloudAppException(500, "Could not parse the date.", e); + } + } + } diff --git a/src/main/java/com/cloudapp/impl/model/CloudAppItemImpl.java b/src/main/java/com/cloudapp/impl/model/CloudAppItemImpl.java index ad97329..9d6739e 100644 --- a/src/main/java/com/cloudapp/impl/model/CloudAppItemImpl.java +++ b/src/main/java/com/cloudapp/impl/model/CloudAppItemImpl.java @@ -31,8 +31,8 @@ public boolean isSubscribed() throws CloudAppException { } public boolean isTrashed() throws CloudAppException { - Date d = getDeletedAt(); - return d != null; + String d = getString("deleted_at"); + return !(d == null || d == "null"); } public String getUrl() throws CloudAppException { @@ -67,6 +67,15 @@ public String getRemoteUrl() throws CloudAppException { public String getRedirectUrl() throws CloudAppException { return getString("redirect_url"); } + + public String getThumbnailUrl() throws CloudAppException { + if (json.has("thumbnail_url")) { + return getString("thumbnail_url"); + } + else { + return null; + } + } public String getSource() throws CloudAppException { return getString("source"); @@ -81,9 +90,9 @@ public Date getCreatedAt() throws CloudAppException { } } - public Date getUploadedAt() throws CloudAppException { + public Date getUpdatedAt() throws CloudAppException { try { - String d = getString("uploaded_at"); + String d = getString("updated_at"); return format.parse(d); } catch (ParseException e) { throw new CloudAppException(500, "Could not parse the date.", e); diff --git a/src/main/java/com/cloudapp/impl/model/CloudAppModel.java b/src/main/java/com/cloudapp/impl/model/CloudAppModel.java index 37b2646..22c66e7 100644 --- a/src/main/java/com/cloudapp/impl/model/CloudAppModel.java +++ b/src/main/java/com/cloudapp/impl/model/CloudAppModel.java @@ -11,7 +11,9 @@ public abstract class CloudAppModel { protected JSONObject json; protected static final DateFormat format = new SimpleDateFormat( - "yyyy-MM-dd'T'HH:mm:ssz"); + "yyyy-MM-dd'T'HH:mm:ss'Z'"); + protected static final DateFormat formatBis = new SimpleDateFormat( + "yyyy-MM-dd"); protected String getString(String key) throws CloudAppException { try { diff --git a/src/test/java/com/cloudapp/impl/CloudAppItemsImplTest.java b/src/test/java/com/cloudapp/impl/CloudAppItemsImplTest.java index a369fc9..91f046a 100644 --- a/src/test/java/com/cloudapp/impl/CloudAppItemsImplTest.java +++ b/src/test/java/com/cloudapp/impl/CloudAppItemsImplTest.java @@ -5,7 +5,9 @@ import java.io.File; import java.net.URL; import java.util.List; +import java.util.concurrent.atomic.AtomicLong; +import com.cloudapp.api.model.CloudAppProgressListener; import org.json.JSONException; import org.junit.Assert; import org.junit.Before; @@ -16,12 +18,13 @@ public class CloudAppItemsImplTest extends BaseTestCase { - private File file; + private static final String TEST_FILE_NAME = "test_file.txt"; + private File file; @Before public void setUp() { super.setUp(); - URL fileurl = getClass().getResource("/test_file.txt"); + URL fileurl = getClass().getResource( "/" + TEST_FILE_NAME ); file = new File(fileurl.getPath()); } @@ -49,6 +52,26 @@ public void testDelete() throws JSONException, CloudAppException { @Test public void testUpload() throws CloudAppException, JSONException { CloudAppItem o = api.upload(file); - assertEquals("test_file.txt", o.getName()); + Assert.assertNotNull(o); + assertEquals(TEST_FILE_NAME, o.getName()); + Assert.assertNotNull(o.getCreatedAt()); } + + @Test + public void simpleGetItemsTest() throws CloudAppException { + List l = api.getItems(1, 5, null, false, null); + Assert.assertNotNull(l); + } + + @Test + public void testUploadListener() throws CloudAppException, JSONException { + final AtomicLong calledCount = new AtomicLong(); + CloudAppItem o = api.upload(file, new CloudAppProgressListener() { + public void transferred(long written, long length) { + calledCount.incrementAndGet(); + } + }); + assertEquals( TEST_FILE_NAME, o.getName() ); + assertEquals( 1, calledCount.get() ); + } }