View Javadoc

1   // Copyright (C) 2004, Brian Enigma <enigma at netninja.com>
2   // This file is part of iGallery.
3   //
4   // iGallery is free software; you can redistribute it and/or modify
5   // it under the terms of the GNU General Public License as published by
6   // the Free Software Foundation; either version 2 of the License, or
7   // (at your option) any later version.
8   //
9   // iGallery is distributed in the hope that it will be useful,
10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  // GNU General Public License for more details.
13  //
14  // You should have received a copy of the GNU General Public License
15  // along with Foobar; if not, write to the Free Software
16  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  package org.ninjasoft.igallery.logic;
18  
19  import java.util.*;
20  import java.net.*;
21  import java.io.*;
22  
23  import org.ninjasoft.igallery.exceptions.*;
24  import org.ninjasoft.igallery.data.*;
25  
26  /***
27   * Class to wrap a gallery connection
28   * @author enigma
29   */
30  public class GalleryConnection {
31      private StatusListener listener = null;
32      private Cookiejar cookiejar = new Cookiejar();
33      private URL url;
34      public static boolean DEBUG = false;
35      
36      public GalleryConnection(String url) throws MalformedURLException {
37          this(url, null);
38      }
39      
40      public GalleryConnection(String url, StatusListener listener) throws MalformedURLException {
41          this.listener = listener;
42          this.url = new URL(url);
43          if (!this.url.getProtocol().equals("http"))
44              throw new MalformedURLException("This application only supports standard http:// connections");
45      }
46      
47      /***
48       * Login with the given username and password.  The login information is then cached
49       * for the remainder of this object's usage.  If successful, return true.  If fail,
50       * throw exception.  (This function actually never returns a false, only throws an
51       * exception.)
52       * @param username
53       * @param password
54       * @return
55       * @throws IOException
56       * @throws GalleryException
57       */
58      public boolean login(String username, String password) throws IOException, GalleryException {
59          Properties parameters = new Properties();
60          parameters.put("cmd", "login");
61          parameters.put("protocol_version", "2.0");
62          parameters.put("uname", username);
63          parameters.put("password", password);
64          Properties returnData = runCommand(parameters);
65          checkResponseCode(returnData);
66          
67          return true;
68      }
69      
70      /***
71       * Return a list of albums available to the user.  
72       * @return Hashtable with name=String of album name, value=Album object
73       * @throws IOException
74       * @throws GalleryException
75       */
76      public Hashtable getAlbumList() throws IOException, GalleryException {
77          Properties parameters = new Properties();
78          parameters.put("cmd", "fetch-albums-prune");
79          parameters.put("protocol_version", "2.2");
80          parameters.put("check_writable", "no");
81          Properties returnData = runCommand(parameters);
82          checkResponseCode(returnData);
83          return parseAlbumList(returnData);
84      }
85      
86      /***
87       * Given the server response for fetch-albums-prune, return a set of
88       * album beans.  This is returned in a Hashtable with the name
89       * as the key and the bean as the object (this is so that the parent
90       * album of nested albums can be determined because the parent
91       * field is returned as a refnumber)
92       * @param data
93       * @return
94       */
95      private Hashtable parseAlbumList(Properties data) {
96          Hashtable result = new Hashtable();
97          int albumCount = 0;
98          try{
99          	albumCount = Integer.parseInt(data.getProperty("album_count", "0"));
100         }catch(Exception e){}
101         // Create the list
102         for (int i=1; i<=albumCount; i++) {
103             GalleryAlbum album = new GalleryAlbum();
104             album.setRefNumber(i);
105             album.setName(data.getProperty("album.name." + i, ""));
106             album.setTitle(data.getProperty("album.title." + i, ""));
107             album.setSummary(data.getProperty("album.summary." + i, ""));
108             album.setParent(data.getProperty("album.parent." + i, ""));
109             result.put(album.getName(), album);
110         }
111         // Interleave the items in the list, such that we can build a sort of tree or breadcrumb trail
112         for (Iterator i = result.values().iterator(); i.hasNext(); ) {
113             GalleryAlbum thisAlbum = (GalleryAlbum) i.next();
114             String parent = "";
115             try{
116             	parent = thisAlbum.getParent();
117             }catch(Exception e){}
118             if (parent != null)
119                 thisAlbum.setParentAlbum((GalleryAlbum) result.get(parent));
120         }
121         return result;
122     }
123     
124     public void uploadPhoto(GalleryAlbum album, File image, String name, String caption) throws GalleryException, IOException {
125         Properties parameters = new Properties();
126         parameters.put("cmd", "add-item");
127         parameters.put("protocol_version", "2.0");
128         parameters.put("set_albumName", album.getName());
129         parameters.put("userfile_name", name);
130         parameters.put("userfile", fileContent(image));
131         parameters.put("force_filename", name);
132         if ((caption != null) && (caption.length() > 0))
133             parameters.put("caption", caption);
134         Properties returnData = runCommand(parameters);
135         checkResponseCode(returnData);
136     }
137     
138     /***
139      * Given a file, return the encoded file contents. 
140      * @param f
141      * @return
142      * @throws IOException
143      */
144     public String encodeFileContent(File f) throws IOException {
145         StringBuffer result = new StringBuffer();
146         FileInputStream in = new FileInputStream(f);
147         int c;
148         while ((c = in.read()) != -1) {
149             char ch = (char) c;
150             if ( ((ch >= '0') && (ch <= '9')) ||
151                  ((ch >= 'a') && (ch <= 'z')) ||
152                  ((ch >= 'A') && (ch <= 'Z'))
153                ) {
154             	result.append(ch);
155             } else {
156                 result.append("%");
157                 String code = Integer.toString(c, 16);
158                 if (code.length() == 1)
159                     result.append("0");
160                 result.append(code);
161             }
162         }
163         in.close();
164         return result.toString();
165     }
166     
167     /***
168      * Given a file, return the file contents. 
169      * @param f
170      * @return
171      * @throws IOException
172      */
173     public byte[] fileContent(File f) throws IOException {
174         ByteArrayOutputStream result = new ByteArrayOutputStream();
175         FileInputStream in = new FileInputStream(f);
176         byte[] buff = new byte[1024];
177         int len;
178         while ((len = in.read(buff)) > 0)
179             result.write(buff, 0, len);
180         in.close();
181         result.close();
182         return result.toByteArray();
183     }
184 
185     /***
186      * Given an album, return a list of pictures in that album.
187      * @param album
188      * @return Hashtable with name=String photo name, value=Image object
189      * @throws IOException
190      * @throws GalleryException
191      */
192     public Hashtable getPhotoList(GalleryAlbum album) throws IOException, GalleryException {
193         Properties parameters = new Properties();
194         parameters.put("cmd", "fetch-album-images");
195         parameters.put("protocol_version", "2.4");
196         parameters.put("set_albumName", album.getName());
197         parameters.put("albums_too", "no");
198         Properties returnData = runCommand(parameters);
199         checkResponseCode(returnData);
200         return parseImageList(returnData);
201     }
202     
203     /***
204      * Given the server response for fetch-album-images, return a set of
205      * image beans.  This is returned in a Hashtable with the name
206      * as the key and the bean as the object.
207      * @param data
208      * @return
209      */
210     private Hashtable parseImageList(Properties data) {
211         Hashtable result = new Hashtable();
212         int albumCount = 0;
213         try{
214             albumCount = Integer.parseInt(data.getProperty("image_count", "0"));
215         }catch(Exception e){}
216         // Create the list
217         for (int i=1; i<=albumCount; i++) {
218             GalleryPicture image= new GalleryPicture();
219             image.setName(data.getProperty("image.name." + i, ""));
220             image.setCaption(data.getProperty("image.caption." + i, ""));
221             result.put(image.getName(), image);
222         }
223         return result;
224     }
225 
226     /***
227      * Ensure that the response code is present and equal to zero.  Throw the proper
228      * exception if it is an error code.
229      * @param data
230      * @throws GalleryException
231      */
232     private void checkResponseCode(Properties data) throws GalleryException {
233         String codeStr = data.getProperty("status", "-1");
234         String message = data.getProperty("status_text", "");
235         int code = -1;
236         try{
237         	code = Integer.parseInt(codeStr);
238         }catch(Exception e){}
239         if (DEBUG)
240         	sendStatus("Got from server: " + code + " (" + message + ")");
241         if (code == -1)
242             throw new GalleryException(-1, "Could not find status in data");
243         if (code == 0)
244             return;
245         switch(code) {
246             case 101:
247             case 102:
248             case 103:
249             case 104:
250                 throw new InvalidVersionException(code, message);
251             case 201:
252             case 202:
253                 throw new InvalidLoginException(code, message);
254             case 301:
255                 throw new UnknownCommandException(code, message);
256             case 401:
257             case 404:
258             case 501:
259                 throw new InvalidPermissionException(code, message);
260             case 402:
261                 throw new NoFilenameException(code, message);
262             case 403:
263                 throw new UploadFailException(code, message);
264             case 502:
265                 throw new CreateFailedException(code, message);
266             default:
267                 throw new GalleryException(code, message);
268         }
269     }
270     
271     /***
272      * Given a bunch of name/value pairs, send them to the server and get a set of
273      * name value pairs back.
274      * @param parameters
275      * @return
276      * @throws IOException
277      * @throws GalleryException
278      */
279     private Properties runCommand(Properties parameters) throws IOException, GalleryException {
280         byte[] queryString = null;
281         HttpURLConnection connection = (HttpURLConnection) this.url.openConnection();
282         if (!parameters.getProperty("cmd", "").equals("add-item")) {
283             queryString = buildQueryString(parameters).getBytes();
284         } else {
285             String boundary = "----iGallery-multipart-boundary----";
286             connection.addRequestProperty("Content-type", "multipart/form-data;.boundary=" + boundary);
287             queryString = buildMultipart(parameters, boundary);
288         }
289         int len = queryString.length;
290         connection.setDoOutput(true);
291         connection.setDoInput(true);
292         cookiejar.setCookies(connection);
293         connection.addRequestProperty("Content-length", Integer.toString(len));
294         OutputStream os = connection.getOutputStream();
295         os.write(queryString, 0, len);
296         os.close();
297         connection.connect();
298         if (connection.getResponseCode() != 200) {
299             throw new WebServerException(-1, "Invalid response from webserver: expected 200, got " + 
300                     connection.getResponseCode() + " (" +
301                     connection.getResponseMessage() + ")");
302         }
303         InputStream in = connection.getInputStream();
304         Properties results = parseResults(in);
305         cookiejar.grabCookies(connection);
306         return results;
307     }
308     
309     /***
310      * Given a server response, parse it into a Properties object
311      * @param in
312      * @return
313      * @throws IOException
314      */
315     private Properties parseResults(InputStream in) throws IOException {
316         Properties results = new Properties();
317         // If debugging, dump the message to stdout
318         if (DEBUG) {
319             ByteArrayOutputStream out = new ByteArrayOutputStream();
320             byte buff[] = new byte[1024];
321             int len;
322             while ((len = in.read(buff)) > 0)
323                 out.write(buff, 0, len);
324             out.close();
325             if (DEBUG) {
326             	sendStatus("Got the following data from the server:");
327             	sendStatus(out.toString());
328             }
329             in = new ByteArrayInputStream(out.toByteArray());
330         }
331         // TODO: Skip bytes until start "#__GR2PROTO__
332         results.load(in);
333         return results;
334     }
335     
336     /***
337      * Given a Properties object of data to send to the server, turn it into
338      * POST data.
339      * @param properties
340      * @return
341      */
342     private String buildQueryString(Properties properties) throws UnsupportedEncodingException {
343         StringBuffer result = new StringBuffer();
344         for (Iterator i = properties.keySet().iterator(); i.hasNext(); ) {
345             String key = (String) i.next();
346             String value = properties.getProperty(key, "");
347             result.append(URLEncoder.encode(key, "UTF-8"));
348             result.append("=");
349             result.append(URLEncoder.encode(value, "UTF-8"));
350             result.append("&");
351         }
352         return result.toString();
353     }
354     
355     private byte[] buildMultipart(Properties properties, String boundary) throws IOException {
356         ByteArrayOutputStream result = new ByteArrayOutputStream();
357         for (Iterator i = properties.keySet().iterator(); i.hasNext(); ) {
358             String key = (String) i.next();
359             Object value = properties.get(key);
360             result.write("--".getBytes());
361             result.write(boundary.getBytes());
362             result.write("\r\n".getBytes());
363             result.write("Content-Disposition: form-data; name=\"".getBytes());
364             result.write(key.getBytes());
365             result.write("\"".getBytes());
366             if (value instanceof byte[]) {
367                 String filename = properties.getProperty("force_filename", "");
368                 result.write("; filename=\"".getBytes());
369                 result.write(filename.getBytes());
370                 result.write("\"".getBytes());
371                 /*
372                 if (filename.toLowerCase().endsWith(".jpg"))
373                 	result.write("\r\nContent-Type: image/jpeg".getBytes());
374                 else if (filename.toLowerCase().endsWith(".gif"))
375                     result.write("\r\nContent-Type: image/gif".getBytes());
376                 else if (filename.toLowerCase().endsWith(".png"))
377                     result.write("\r\nContent-Type: image/png".getBytes());
378                 */
379             }
380             result.write("\r\n\r\n".getBytes());
381             if (value instanceof String)
382             	result.write(((String) value).getBytes());
383             else if (value instanceof byte[])
384                 result.write((byte[]) value);
385             result.write("\r\n".getBytes());
386         }
387         result.write("--".getBytes());
388         result.write(boundary.getBytes());
389         result.write("--\r\n".getBytes());
390         return result.toByteArray();
391     }
392     
393     /***
394      * Simple unit test
395      * @param argv
396      * @throws Exception
397      */
398     public static void main(String[] argv) throws Exception {
399         GalleryConnection con = new GalleryConnection("http://localhost/gallerytest/gallery_remote2.php");
400         con.login("admin", "test");
401         Hashtable albums = con.getAlbumList();
402         for (Iterator i = albums.values().iterator(); i.hasNext(); ) {
403             GalleryAlbum thisAlbum = (GalleryAlbum) i.next();
404             System.out.println(thisAlbum);
405         }
406         System.out.println("Uploading first photo...");
407         con.uploadPhoto((GalleryAlbum) albums.get("nest2"), new File("/tmp/pics/IMG_1811.JPG"), "Pic1.jpg", "Caption 1");
408         System.out.println("Uploading second photo...");
409         con.uploadPhoto((GalleryAlbum) albums.get("nest2"), new File("/tmp/pics/IMG_1818.JPG"), "Pic2.jpg", "Caption 2");
410     }
411 
412     public void setStatusListener(StatusListener l)     {this.listener = l;}
413 
414     private void sendStatus(String message) {
415         if (listener != null)
416             listener.setStatus(message);
417     }
418     private void sendError(String message) {
419         if (listener != null)
420             listener.setError(message);
421     }
422 }