Subversion Repositories general

Rev

Rev 1248 | Rev 1250 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

package ak.photoalbum.logic;

import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.StringTokenizer;
import java.util.Map;
import java.util.Hashtable;
import java.io.File;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.FileNotFoundException;
import java.net.URLEncoder;

import org.xml.sax.SAXException;
import org.apache.commons.digester.Digester;
import org.apache.log4j.Logger;

import ak.photoalbum.images.Thumbnailer;
import ak.photoalbum.images.ThumbnailPosition;
import ak.photoalbum.util.FileUtils;
import ak.photoalbum.util.FileNameComparator;
import ak.photoalbum.util.ImagesFilter;
import ak.photoalbum.config.ConfigRoot;
import ak.photoalbum.config.ConfigBranch;
import ak.photoalbum.config.ConfigDirThumbnail;

public class Logic
{
  protected static final String URL_ENCODING    = "UTF-8";
  protected static final String META_FILE_NAME  = "meta.xml"; // FIXME make configurable (?)

  protected Logger       logger;
  protected ConfigRoot   config;
  protected Map          branches           = new Hashtable(); // <String, Branch>
  protected Digester     configDigester     = createConfigDigester();
  protected Digester     metaDigester       = createMetaDigester();

  protected Logic()
  {
    this.logger = Logger.getLogger(this.getClass());
  }

  protected Digester createConfigDigester()
  {
    /*
4     <uri>children</uri>
5     <images-root>/home/data/photos</images-root>
6     <cache-dir>/home/data/photos/.cache</cache-dir>
7     <thumbnail-format>jpg</thumbnail-format>
8     <columns>6</columns>
9     <rows>5</rows>
10     <images-mask>*.jpeg</images-mask>
11     <images-mask>*.jpg</images-mask>
12     <dir-thumbnail>
13       <template>images/dir_template.gif</template>
14       <thumbnail>
15         <left>5</left>
16         <top>9</top>
17         <width>40</width>
18         <height>40</height>
19         <align>center</align>
20         <valign>center</valign>
*/
    Digester digester = new Digester();
    digester.setValidating(false);

    digester.addObjectCreate("photos", ConfigRoot.class);
    
    digester.addObjectCreate("photos/branch", ConfigBranch.class);
    digester.addBeanPropertySetter("photos/branch/uri",              "uri");
    digester.addBeanPropertySetter("photos/branch/images-root",      "imagesRoot");
    digester.addBeanPropertySetter("photos/branch/cache-dir",        "cacheDir");
    digester.addBeanPropertySetter("photos/branch/thumbnail-format", "thumbnailFormat");
    digester.addBeanPropertySetter("photos/branch/columns",          "columns");
    digester.addBeanPropertySetter("photos/branch/rows",             "rows");

// FIXME images-mask

    digester.addSetNext("photos/branch", "addBranch");
 /*   digester.addBeanPropertySetter("meta/item/subtitle", "subtitle");
    digester.addSetProperties("meta/item/subtitle", "mime", "subtitleMime");
    digester.addBeanPropertySetter("meta/item/comment", "comment");
    digester.addSetProperties("meta/item/comment", "mime", "commentMime");
    digester.addSetNext("photos/branch", "addItem");
*/
    return digester;
  }

  protected Digester createMetaDigester()
  {
    Digester digester = new Digester();
    digester.setValidating(false);

    digester.addObjectCreate("meta", MetaInfo.class);
    
    digester.addObjectCreate("meta/item", MetaInfoItem.class);
    digester.addSetProperties("meta/item", "id", "id");
    digester.addBeanPropertySetter("meta/item/title", "title");
    digester.addBeanPropertySetter("meta/item/subtitle", "subtitle");
    digester.addSetProperties("meta/item/subtitle", "mime", "subtitleMime");
    digester.addBeanPropertySetter("meta/item/comment", "comment");
    digester.addSetProperties("meta/item/comment", "mime", "commentMime");
    digester.addSetNext("meta/item", "addItem");

    return digester;
  }

  public void init(ResourceFactory resourceFactory, String configPath)
    throws IOException, SAXException, LogicException
  {
    logger.info("starting");
    config = (ConfigRoot)configDigester.parse(resourceFactory.getAsStream(configPath));

    for(Iterator i = config.getBranches(); i.hasNext(); ) {
      Branch branch = new Branch((ConfigBranch)i.next());
      brnaches.put(branch.getUri(), branch);
    }

    logger.info("started");
  }

  public Branch getBranch(String uri)
  {
    return (Branch)branches.get(uri);
  }

  public void buildCache(String uri)
    throws IOException
  {
    getBranch(uri).getThumbnailer().buildCache();
  }

  public void getEntry(String uri, String path, IndexEntry page,
      IndexEntry index, IndexEntry prev, IndexEntry current, IndexEntry next)
    throws IOException, SAXException, LogicException
  {
    Branch branch = getBranch(uri);
    File   file   = new File(branch.getImagesRoot(), path);

    securePath(branch.getImagesRoot(), file);

    if(!file.exists())
      throw new FileNotFoundException(
        "[" + file.getCanonicalPath() + "] not found");

    File   dir      = file.getParentFile();
    File[] children = dir.listFiles(branch.getImagesFilter());
    int    pos;

    Arrays.sort(children, branch.getFileNameComparator());
    pos = Arrays.binarySearch(children, file, branch.getFileNameComparator());

    if(pos < 0)
      throw new FileNotFoundException("[" + file.getCanonicalPath()
        + "] not found in [" + dir.getCanonicalPath() + "]");

    branch.getMetaInfos().clear();     // FIXME make this more intelligent
    setEntryInfo(page,    file, false);
    setEntryInfo(current, file, true);
    setEntryInfo(index,   dir,  true);
    if(pos > 0)                 setEntryInfo(prev, children[pos-1], true);
    if(pos < children.length-1) setEntryInfo(next, children[pos+1], true);
  }

  protected void setEntryInfo(Branch branch, IndexEntry entry, File file, boolean small)
    throws IOException, SAXException
  {
    String title = file.getName();
    int[]  size;
    String path  = getPath(file);

    if(file.isDirectory()) {
      size = branch.getThumbnailer().getDirSize(file);
    }
    else {
      title = FileUtils.extractFileName(title);

      if(small)
        size = branch.getThumbnailer().getSmallSize(file);
      else
        size = branch.getThumbnailer().getMediumSize(file);
    }

    entry.setFile(file);
    entry.setPath(path == null ? null : URLEncoder.encode(path, URL_ENCODING));
    entry.setTitle(title);
    entry.setIsDir(file.isDirectory());
    entry.setWidth(size[0]);
    entry.setHeight(size[1]);

    MetaInfoItem meta = findMetaInfo(branch.getImagesRoot(), file);
    if(meta != null) {
      if(meta.getTitle() != null) {
        entry.setTitle(meta.getTitle());
      }
      if(meta.getSubtitle() != null) {
        entry.setSubtitle(meta.getSubtitle());
        entry.setSubtitleMime(meta.getSubtitleMime());
      }
      if(meta.getComment() != null) {
        entry.setComment(meta.getComment());
        entry.setCommentMime(meta.getCommentMime());
      }
    }
  }

  public String getThumbnailMime(String uri)
  {
    return getBranch(uri).getThumbnailer().getMime();
  }

  public String getOriginMime(String uri, String path)
    throws IOException, LogicException
  {
    Branch branch = getBranch(uri);
    File   file   = new File(branch.getImagesRoot(), path);

    if(!file.exists()) return null;
    securePath(branch.getImagesRoot(), file);

    return FileUtils.getMime(FileUtils.extractFileExt(path));
  }

  public void writeDir(String uri, String path, OutputStream out)
    throws IOException, LogicException
  {
    Branch branch = getBranch(uri);
    File   file   = new File(branch.getImagesRoot(), path);

    securePath(branch.getImagesRoot(), file);
    branch.getThumbnailer().writeDir(file, out);
  }

  public void writeSmall(String uri, String path, OutputStream out)
    throws IOException, LogicException
  {
    Branch branch = getBranch(uri);
    File   file   = new File(branch.getImagesRoot(), path);

    securePath(branch.getImagesRoot(), file);
    branch.getThumbnailer().writeSmall(file, out);
  }

  public void writeMedium(String uri, String path, OutputStream out)
    throws IOException, LogicException
  {
    Branch branch = getBranch(uri);
    File   file   = new File(branch.getImagesRoot(), path);

    securePath(branch.getImagesRoot(), file);
    branch.getThumbnailer().writeMedium(file, out);
  }

  public void writeOrigin(String uri, String path, OutputStream out)
    throws IOException, LogicException
  {
    Branch          branch = getBranch(uri);
    FileInputStream in     = null;
    File            file   = new File(branch.getImagesRoot(), path);

    securePath(branch.getImagesRoot(), file);

    try {
      in  = new FileInputStream(file);
      FileUtils.copyStreams(in, out);
    }
    finally {
      if(in != null) in.close();
    }
  }

  protected MetaInfo getMetaInfo(Branch branch, File dir)
    throws IOException, SAXException
  {
    MetaInfo meta = (MetaInfo)branch.getMetaInfos().get(dir);
    if(meta != null) return meta;

    File metaFile = new File(dir, META_FILE_NAME);
    if(!metaFile.exists()) return null;

    meta = (MetaInfo)metaDigester.parse(new FileInputStream(metaFile));
    meta.setDir(dir);
    branch.getMetaInfos().put(dir, meta);

    return meta;
  }

  protected MetaInfoItem findMetaInfo(File rootDir, File file)
    throws IOException, SAXException
  {
    file    = file.getCanonicalFile();
    rootDir = rootDir.getCanonicalFile();

    File dir = file;
    if(!dir.isDirectory())
      dir = dir.getParentFile();

    MetaInfoItem metaItem = null;
    for(; metaItem == null; dir = dir.getParentFile()) {
      if(dir == null) break;

      MetaInfo meta = getMetaInfo(dir);
      if(meta != null) {
        metaItem = meta.findItem(file);
      }
      if(rootDir.equals(dir)) break;
    }

    return metaItem;
  }

  public boolean listDirectory(String uri, String dirName, int page, List table, List pages)
    throws IOException, LogicException, SAXException
  {
    Branch branch = getBranch(uri);
    File   dir    = new File(branch.getImagesRoot(), dirName);

    securePath(branch.getImagesRoot(), dir);
    if(!dir.exists()) return false;

    File[] children  = dir.listFiles(branch.getImagesFilter());
    int    pos       = page * branch.getColumns() * branch.getRows();

    Arrays.sort(children, branch.getFileNameComparator());
    branch.getMetaInfos().clear();           // FIXME do this more intelligent (?)

    // the pages list
    pages.clear();
    for(int i = 0; i < (int)Math.ceil((double)children.length / branch.getColumns() / branch.getRows()); i++) {
      pages.add(new PageItem(i, i == page));
    }

    // the main table
    table.clear();
    while(pos < children.length && pos < (page+1) * branch.getColumns() * branch.getRows()) {
      List row    = new ArrayList();
      int  rowPos = 0;

      table.add(row);

      while(rowPos < branch.getColumns() && pos < children.length) {
        String path  = getPath(children[pos]);
        String title = children[pos].getName();
        int[]  size;

        if(children[pos].isDirectory()) {
          size  = branch.getThumbnailer().getDirSize(children[pos]);
        }
        else {
          size  = branch.getThumbnailer().getSmallSize(children[pos]);
          title = FileUtils.extractFileName(title);
        }

        IndexEntry entry = new IndexEntry(children[pos],
          URLEncoder.encode(path, URL_ENCODING),
          title, children[pos].isDirectory(), size[0], size[1]);

        MetaInfoItem meta = findMetaInfo(branch.getImagesRoot(), children[pos]);
        if(meta != null) {
          if(meta.getTitle() != null) {
            entry.setTitle(meta.getTitle());
          }
          if(meta.getSubtitle() != null) {
            entry.setSubtitle(meta.getSubtitle());
            entry.setSubtitleMime(meta.getSubtitleMime());
          }
          if(meta.getComment() != null) {
            entry.setComment(meta.getComment());
            entry.setCommentMime(meta.getCommentMime());
          }
        }

        row.add(entry);
        rowPos++;
        pos++;
      }

      while(rowPos < branch.getColumns()) {
        row.add(null);
        rowPos++;
      }
    }

    return true;
  }

  protected String getPath(Branch branch, File file)
    throws IOException
  {
    String path     = file.getCanonicalPath();
    String rootPath = branch.getImagesRoot().getCanonicalPath();

    if(path.equals(rootPath)) return "";
    if(!rootPath.endsWith(File.separator)) rootPath += File.separator;

    if(!path.startsWith(rootPath))
      return null;

    return path.substring(rootPath.length());
  }

  protected ThumbnailPosition[] parseThumbnailPositions(String str)
  {
    List            list      = new ArrayList();
    StringTokenizer tokenizer = new StringTokenizer(str, ";");

    while(tokenizer.hasMoreTokens()) {
      StringTokenizer tokenParser
        = new StringTokenizer(tokenizer.nextToken(), ",");
      int    x            = Integer.parseInt(tokenParser.nextToken().trim());
      int    y            = Integer.parseInt(tokenParser.nextToken().trim());
      int    width        = Integer.parseInt(tokenParser.nextToken().trim());
      int    height       = Integer.parseInt(tokenParser.nextToken().trim());
      String horAlignStr  = tokenParser.nextToken().trim().toLowerCase();
      String vertAlignStr = tokenParser.nextToken().trim().toLowerCase();
      int    horAlign;
      int    vertAlign;

      if("l".equals(horAlignStr))
        horAlign = ThumbnailPosition.ALIGN_HOR_LEFT;
      else if("r".equals(horAlignStr))
        horAlign = ThumbnailPosition.ALIGN_HOR_RIGHT;
      else if("c".equals(horAlignStr))
        horAlign = ThumbnailPosition.ALIGN_HOR_CENTER;
      else
        throw new RuntimeException(
          "Cannot parse " + horAlignStr + " as horizontal alignment");

      if("t".equals(vertAlignStr))
        vertAlign = ThumbnailPosition.ALIGN_VERT_TOP;
      else if("b".equals(vertAlignStr))
        vertAlign = ThumbnailPosition.ALIGN_VERT_BOTTOM;
      else if("c".equals(vertAlignStr))
        vertAlign = ThumbnailPosition.ALIGN_VERT_CENTER;
      else
        throw new RuntimeException(
          "Cannot parse " + vertAlignStr + " as vertical alignment");

      list.add(new ThumbnailPosition(x, y, width, height, horAlign, vertAlign));
    }

    ThumbnailPosition[] res = new ThumbnailPosition[list.size()];

    for(int i = 0; i < res.length; i++)
      res[i] = (ThumbnailPosition)list.get(i);

    return res;
  }

  protected static final Logic instance = new Logic();

  public static Logic getLogic()
  {
    return instance;
  }

  /**
   * checks if given file is really under the parent directory
   */
  protected void securePath(File parentDir, File file)
    throws IOException, LogicException
  {
    if(parentDir == null || file == null) return;

    File partFile = file.getCanonicalFile();

    parentDir = parentDir.getCanonicalFile();
    while(partFile != null) {
      if(partFile.equals(parentDir)) return;
      partFile = partFile.getParentFile();
    }

    throw new LogicSecurityException(
      "[" + file.getCanonicalPath() + "] is outside of directory ["
      + parentDir.getCanonicalPath() + "]");
  }
}