View Javadoc

1   /*
2    * Created on Mar 1, 2005 by Justin Sher
3    *
4    *  ginp - Java Web Application for Viewing Photo Collections
5    *  Copyright (C) 2004  Justin Sher <justin@sher.net>
6    *
7    *  This library is free software; you can redistribute it and/or
8    *  modify it under the terms of the GNU Lesser General Public
9    *  License as published by the Free Software Foundation; either
10   *  version 2.1 of the License, or any later version.
11   *
12   *  This library is distributed in the hope that it will be useful,
13   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15   *  Lesser General Public License for more details.
16   *
17   *  You should have received a copy of the GNU Lesser General Public
18   *  License along with this library; if not, write to the Free Software
19   *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
20   */
21  package net.sf.ginp.browser;
22  
23  import java.awt.geom.AffineTransform;
24  import java.awt.image.AffineTransformOp;
25  import java.awt.image.BufferedImage;
26  import java.io.File;
27  import java.io.FileInputStream;
28  import java.io.FileOutputStream;
29  import java.io.FileReader;
30  import java.io.IOException;
31  import java.io.LineNumberReader;
32  import java.util.ArrayList;
33  import java.util.Arrays;
34  import java.util.Comparator;
35  import java.util.HashMap;
36  import java.util.Iterator;
37  import java.util.Vector;
38  
39  import org.apache.commons.logging.Log;
40  import org.apache.commons.logging.LogFactory;
41  
42  import net.sf.ginp.PicCollection;
43  import net.sf.ginp.util.StringTool;
44  import net.sf.ginp.config.Configuration;
45  
46  
47  import com.sun.image.codec.jpeg.JPEGCodec;
48  import com.sun.image.codec.jpeg.JPEGImageDecoder;
49  import com.sun.image.codec.jpeg.JPEGImageEncoder;
50  
51  /**
52   * @author Justin Sher
53   */
54  public class FolderManagerImpl implements FolderManager {
55  	//Cache is for one minute
56  	private static long MAX_CACHE=60000;
57  	private static MakeThumbs mth=null;
58  	private static Thread mthThread=null;
59  	HashMap folderCache=new HashMap();
60  	HashMap picCache=new HashMap();
61  	long folderCacheTimeout=-1;
62  	long picCacheTimeout=-1;
63  	private static Log log = LogFactory.getLog(FolderManagerImpl.class);
64      /**
65       *Gets the foldersInDirectory attribute of the PicCollection object
66       *@param root the root directory
67       *@param  relPath  Description of the Parameter
68       *@return          The foldersInDirectory value
69       */
70      private String[] getFoldersInDirectory(String root,String relPath) {
71         	long duration=System.currentTimeMillis()-picCacheTimeout;
72      	if (picCacheTimeout<0 || duration>MAX_CACHE) { 
73       		synchronized(folderCache) {
74      			//Clearing a cache while doing a get can
75      			//cause hashmap to hang.  Hence all the synchronized blocks
76      			folderCacheTimeout=System.currentTimeMillis();
77      			folderCache.clear();
78      		}
79      	}
80      	synchronized (folderCache) { 
81      		if (folderCache.get(root+relPath)!=null) { 
82      			return (String[]) folderCache.get(root+relPath);
83      		}
84      	}
85          Vector    dirs        = new Vector();
86  
87          File      dir         = new File(root + relPath);
88          if (dir.isDirectory()) {
89              String[]  files  = dir.list();
90              Arrays.sort(files);
91              for (int i = 0; i < files.length; i++) {
92                  File  file  = new File(root + relPath + "/" + files[i]);
93                  if (file.isDirectory()) {
94                      if (!(files[i].startsWith("."))) {
95                          dirs.add(files[i]);
96                      }
97                  }
98              }
99          }
100         String[]  retFolders  = new String[dirs.size()];
101         for (int i = 0; i < dirs.size(); i++) {
102             retFolders[i] = relPath + (String) dirs.get(i);
103         }
104         Arrays.sort(retFolders);
105         synchronized (folderCache) { 
106           	 folderCache.put(root+relPath,retFolders);
107           }
108         return retFolders;
109     }
110 
111     /**
112      *  Gets the picturesInDirectory attribute of the PicCollection object
113      *
114      *@param  relPath  Description of the Parameter
115      *@return          The picturesInDirectory value
116      */
117     private String[] getPicturesInDirectory(String root,String relPath) {
118     	long duration=System.currentTimeMillis()-picCacheTimeout;
119     	if (picCacheTimeout<0 || duration>MAX_CACHE) { 
120     		synchronized(picCache) {
121     			//Clearing a cache while doing a get can
122     			//cause hashmap to hang.  Hence all the synchronized blocks
123     			picCacheTimeout=System.currentTimeMillis();
124     			picCache.clear();
125     		}
126     	}
127     	synchronized (picCache) { 
128     		if (picCache.get(root+relPath)!=null) { 
129     			String cached[]= (String[]) picCache.get(root+relPath);
130     			return cached;
131     		}
132     	}
133         Vector    pics         = new Vector();
134         try {
135             File  dir  = new File(root + relPath);
136             if (dir.isDirectory()) {
137                 String[]  files  = dir.list();
138                 for (int i = 0; i < files.length; i++) {
139                     File  file  = new File(root + relPath + "/" + files[i]);
140                     if ((!file.isDirectory()) && ((files[i].toLowerCase()).endsWith(".jpg") || (files[i].toLowerCase()).endsWith(".jpeg"))) {
141                         pics.add(files[i]);
142                         if (log.isDebugEnabled()) {
143                             log.debug("Adding picture file: " + files[i]);
144                         }
145                     }
146                 }
147 
148                 // Add Featured Pics
149                 File      fl     = new File(root + relPath + "ginpfolder.xml");
150                 if (fl.exists()) {
151                     FileReader        fr               = new FileReader(fl);
152                     LineNumberReader  lr               = new LineNumberReader(fr);
153                     StringBuffer      sb               = new StringBuffer();
154                     String            temp;
155                     while ((temp = lr.readLine()) != null) {
156                         sb.append(temp + "\n");
157                     }
158                     String            featuredpicsXML  = StringTool.getXMLTagContent("featuredpics", sb.toString());
159                     String[]          picsXML          = StringTool.splitToArray(featuredpicsXML, "<pic>");
160 
161                     for (int i = 0; i < picsXML.length; i++) {
162                         if (picsXML[i].indexOf("</pic>") != -1) {
163                             temp = picsXML[i].substring(0, picsXML[i].indexOf("</pic>"));
164                             fl = new File(root + relPath + temp);
165                             if (fl.exists()) {
166                                 pics.add(temp);
167                             }
168                         }
169                     }
170                 }
171             }
172         } catch (IOException ex) {
173             log.error(ex);
174         }
175         String[]  retPictures  = new String[pics.size()];
176         for (int i = 0; i < pics.size(); i++) {
177             retPictures[i] = (String) pics.get(i);
178         }
179         String[] sorted = sortPictures(retPictures);
180         synchronized (picCache) {
181         	if (picCache.get(root+relPath)==null) { 
182         		picCache.put(root+relPath,sorted);
183         	}
184         	else { 
185         		return (String[]) picCache.get(root+relPath);
186         	}
187         }
188         
189         //Only One Thread Per Directory Should Ever Get Here (Unless it takes longer than 2 minutes to make)
190        	synchronized(MakeThumbs.class) { 
191        		if (mth==null || (mthThread!=null && !mthThread.isAlive())) { 
192        			mth  = new MakeThumbs();
193        			mthThread = new Thread(mth);
194        			mthThread.setDaemon(true);
195        			mthThread.setPriority(Thread.MIN_PRIORITY);
196        			mthThread.start();
197        		}
198        	}
199        	mth.addToQueue(root+relPath, sorted);
200         return sorted;
201     }
202 
203     /**
204      *  Sort the arry of picture names.
205      *
206      *@param  ary  Array of unSorted Picture Names
207      *@return      Sorted array based on current Sort criteria.
208      */
209     public String[] sortPictures(String[] ary) {
210 
211         //if (sortDESC) {
212         //revrce sort
213         //}
214         Comparator  comp  = new ComparePictures();
215         Arrays.sort(ary, comp);
216 
217         return ary;
218     }
219 
220 
221 
222 
223 
224 
225     /**
226      *  Description of the Class
227      *
228      *@author     doc
229      *@version    $Revision: 303 $
230      */
231     class ComparePictures implements Comparator {
232 
233 
234         /**
235          *  Constructor for the ComparePictures object
236          */
237         ComparePictures() { }
238 
239 
240         /**
241          *  Description of the Method
242          *
243          *@param  o1  Description of the Parameter
244          *@param  o2  Description of the Parameter
245          *@return     Description of the Return Value
246          */
247         public int compare(Object o1, Object o2) {
248 
249             if (o1.toString().indexOf("/") != -1) {
250                 o1 = o1.toString().substring(o1.toString().lastIndexOf("/") + 1);
251             }
252             if (o2.toString().indexOf("/") != -1) {
253                 o2 = o2.toString().substring(o2.toString().lastIndexOf("/") + 1);
254             }
255             return o1.toString().compareTo(o2.toString());
256         }
257 
258 
259         /**
260          *  Description of the Method
261          *
262          *@param  obj  Description of the Parameter
263          *@return      Description of the Return Value
264          */
265         public boolean equals(Object obj) {
266             return true;
267         }
268     }
269 
270 
271 	/* (non-Javadoc)
272 	 * @see net.sf.ginp.browser.FolderManager#getFoldersLength(net.sf.ginp.PicCollection)
273 	 */
274 	public int getFoldersLength(PicCollection collection) {
275 		return this.getFoldersInDirectory(collection.getRoot(),collection.getPath()).length;
276 	}
277 
278 	/* (non-Javadoc)
279 	 * @see net.sf.ginp.browser.FolderManager#getPicturesInDirectoryLength(java.lang.String)
280 	 */
281 	public int getPicturesInDirectoryLength(PicCollection collection,String selectedFolder) {
282 		return this.getPicturesInDirectory(collection,selectedFolder).length;
283 	}
284 
285 	/* (non-Javadoc)
286 	 * @see net.sf.ginp.browser.FolderManager#getFoldersInDirectory(java.lang.String)
287 	 */
288 	public String[] getFoldersInDirectory(PicCollection collection,String string) {
289 		return this.getFoldersInDirectory(collection.getRoot(),string);
290 	}
291 
292 	/* (non-Javadoc)
293 	 * @see net.sf.ginp.browser.FolderManager#getPicturesInDirectory(java.lang.String)
294 	 */
295 	public String[] getPicturesInDirectory(PicCollection collection,String path) {
296 		return this.getPicturesInDirectory(collection.getRoot(),path);
297 	}
298 
299 	/* (non-Javadoc)
300 	 * @see net.sf.ginp.browser.FolderManager#getPrevPictureName(java.lang.String, net.sf.ginp.PicCollection)
301 	 */
302 	public String getPrevPictureName(String picName, PicCollection collection) {
303 		String[] pictures2 = this.getPictures(collection);
304 		if (pictures2[0].equals(picName)) { return null; } 
305 		for (int x=1;x<pictures2.length;x++) { 
306 			String pic=pictures2[x];
307 			if (pic.equals(picName)) { 
308 				return pictures2[--x];
309 			}
310 		}
311 		return null;
312 	}
313 
314 	/* (non-Javadoc)
315 	 * @see net.sf.ginp.browser.FolderManager#getNextPictureName(java.lang.String, net.sf.ginp.PicCollection)
316 	 */
317 	public String getNextPictureName(String picName, PicCollection collection) {
318 		String[] pictures2 = this.getPictures(collection);
319 		if (pictures2[pictures2.length-1].equals(picName)) { return null; } 
320 		for (int x=0;x<pictures2.length-1;x++) { 
321 			String pic=pictures2[x];
322 			if (pic.equals(picName)) { 
323 				return pictures2[++x];
324 			}
325 		}
326 		return null;
327 	}
328 
329 	/* (non-Javadoc)
330 	 * @see net.sf.ginp.browser.FolderManager#getPicturesLength(java.lang.String)
331 	 */
332 	public int getPicturesLength(PicCollection collection,String path) {
333 		return this.getPicturesInDirectory(collection,path).length;
334 	}
335 
336 	/* (non-Javadoc)
337 	 * @see net.sf.ginp.browser.FolderManager#getPictures(net.sf.ginp.PicCollection)
338 	 */
339 	public String[] getPictures(PicCollection collection) {
340 		return this.getPicturesInDirectory(collection.getRoot(),collection.getPath());
341 	}
342 
343 	/* (non-Javadoc)
344 	 * @see net.sf.ginp.browser.FolderManager#getPicturesLength(net.sf.ginp.PicCollection)
345 	 */
346 	public int getPicturesLength(PicCollection collection) {
347 		return this.getPicturesInDirectory(collection.getRoot(),collection.getPath()).length;
348 	}
349 
350 	/* (non-Javadoc)
351 	 * @see net.sf.ginp.browser.FolderManager#getFolder(net.sf.ginp.PicCollection, int)
352 	 */
353 	public String getFolder(PicCollection collection, int count) {
354 		return this.getFoldersInDirectory(collection.getRoot(),collection.getPath())[count];
355 	}
356 
357 
358 }
359 
360 /**
361  *  A class that makes Thumbnail images. This is designed to be used as a
362  *  background thread.
363  *
364  *@author     $Author: dougculnane $
365  *@version    $Revision: 303 $
366  */
367 class MakeThumbs implements Runnable {
368 
369 
370              ArrayList		thumbQueue=new ArrayList();
371     private Log log = LogFactory.getLog(MakeThumbs.class);
372 
373     /**
374      * Add a request to make thumbs for a particular directory
375 	 * @param path the path of the directory
376      * @param pics the pictures in the directory
377 	 */
378 	public void addToQueue(String path, String[] pics) {
379 		synchronized(thumbQueue) {
380 			Iterator iter=thumbQueue.iterator();
381 			while (iter.hasNext()) {
382 				Object[] queue=(Object[])iter.next();
383 				if (queue[0].equals(path)) {
384 					//Already have one in the queue
385 					return;
386 				}
387 			}
388 			thumbQueue.add(new Object[] { path , pics } );
389 		}				
390 	}
391 
392 
393 	/**
394      *  Main processing method for the MakeThumbs object
395      */
396     public void run() {
397     	
398         String url=null;
399     	String[] pics=null;
400         int[] thumbSizes = {Configuration.getThumbSize(),
401                             Configuration.getFilmStripThumbSize()};
402                             
403     	while (true) {
404     		boolean makeQueue=false;
405     		synchronized (thumbQueue) {
406     			if (thumbQueue.size()>0) { 
407                     Object[] queue=(Object[]) thumbQueue.remove(0);
408     			    url=(String) queue[0];
409     			    pics=(String[]) queue[1];
410     			    makeQueue=true;
411     	    	}
412         	}
413     		if (!makeQueue){ 
414     	    		try {
415 						Thread.sleep(1000);
416                         continue;
417 					} catch (InterruptedException e1) {
418 						// TODO Auto-generated catch block
419 						log.error(e1);
420                         //.printStackTrace();
421 					}
422     		}
423     		
424             try {
425                 File  folder  = new File(url);
426                 if (folder.exists()) {
427 
428                     File  ginpFolder      = new File(url + "/.ginp");
429                     if (!(ginpFolder.exists())) {
430                         ginpFolder.mkdir();
431                     }
432                     // For each picture make a thumb;
433                     for (int i = 0; i < pics.length; i++) {
434                         makeThumbImage(url, pics[i], thumbSizes);
435                     }
436                 }
437             } catch (Exception e) {
438                 log.error(e);
439             }
440     	}
441     }
442     
443     void makeThumbImage(String url, String fileName, int[] sizes) {
444         
445         if (log.isDebugEnabled()) {
446             log.debug("makeThumbImage: url=" + url 
447                     + " fileName=" + fileName + " int[] sizes");
448         }
449         
450         for (int i = 0; i < sizes.length; i++) {
451         
452             try {
453                 
454                 String   thumbFile  = getThumbFileName(url, sizes[i], fileName);
455                 File     ginpPic    = new File(thumbFile);
456                 File     origPic    = new File(url + "/" + fileName);
457                 
458                 boolean  makeThumb  = false;
459                 if (ginpPic.exists()) {
460                     // ReThumb if the original changed after the thumb.        
461                     if (origPic.lastModified() > ginpPic.lastModified()) {
462                         makeThumb = true;
463                     }
464                 } else {
465                     makeThumb = true;
466                 }
467                 if (makeThumb) {
468                     if (i != 0) {
469                         File   bigThumb  = new File(getThumbFileName(url, sizes[i-1], fileName));
470                         if (bigThumb.exists()) {
471                             origPic  = bigThumb;
472                         }
473                     }
474                     makeThumbImage(origPic, thumbFile, sizes[i]);
475                 }
476             } catch (Exception e) {
477                 log.error(e);
478             }
479         }
480     }
481         
482     String getThumbFileName(String url, int size, String  fileName){
483         
484         String retFileName = url + "/.ginp/" + size + "-" + fileName;
485                 
486         // If this is a featured picture fix the path.
487         if (fileName.indexOf("/") != -1) {
488            retFileName = url
489                     + fileName.substring(0, fileName.lastIndexOf("/"))
490                     + "/.ginp/" + size + "-"
491                     + fileName.substring(fileName.lastIndexOf("/") + 1);
492         }
493         
494         return retFileName;
495     }
496     
497     
498     
499     void makeThumbImage(File origPicture, String thumbFileName, int maxThumbSize) {
500         
501         if (log.isDebugEnabled()) {
502             log.debug("makeThumbImage: origFileName=" + origPicture.getAbsolutePath() 
503                     + " thumbFileName=" + thumbFileName + " maxThumbSize=" +  maxThumbSize);
504         }
505         
506         // Only jpegs supported.
507         if ((origPicture.getName().toLowerCase()).endsWith(".jpg")
508                      || (origPicture.getName().toLowerCase()).endsWith(".jpeg")) {
509             try {
510                 
511                  // thumb it.
512                 JPEGImageDecoder dc         = JPEGCodec.createJPEGDecoder((new 
513                                                 FileInputStream(origPicture)));
514                 BufferedImage    origImage  = dc.decodeAsBufferedImage();
515                 int              origHeight = origImage.getHeight(null);
516                 int              origWidth  = origImage.getWidth(null);
517                 int              scaledW    = 0;
518                 int              scaledH    = 0;
519                 double           scale      = 1.0;
520             
521                 if (origHeight < origWidth) {
522                     scale = (double) maxThumbSize / (double) origWidth;
523                 } else {
524                     scale = (double) maxThumbSize / (double) origHeight;
525                 }
526                 scaledW = (int) (scale * origWidth);
527                 scaledH = (int) (scale * origHeight);
528             
529                 //AffineTransform    at          = new AffineTransform();
530                 AffineTransform    tx;
531                 AffineTransformOp  af;
532                 JPEGImageEncoder   encoder;
533                 BufferedImage      outImage;
534             
535                 outImage = new BufferedImage(scaledW, scaledH,
536                         BufferedImage.TYPE_INT_RGB);
537                 tx = new AffineTransform();
538                 tx.scale(scale, scale);
539                 af = new AffineTransformOp(tx, null);
540                 af.filter(origImage, outImage);
541             
542                 File ginpFolder = new File(thumbFileName.substring(0, thumbFileName.lastIndexOf("/.ginp")) + "/.ginp");
543                 if (!(ginpFolder.exists())) {
544                     ginpFolder.mkdir();
545                 }
546                 encoder = JPEGCodec.createJPEGEncoder(new FileOutputStream(thumbFileName));
547                 encoder.encode(outImage);
548             } catch (Exception e) {
549                log.error("Error Makeing Thumb Image " + thumbFileName, e);
550             }
551         }
552     }
553 }