/* GNU General Public License CacheWolf is a software for PocketPC, Win and Linux that enables paperless caching. It supports the sites geocaching.com and opencaching.de Copyright (C) 2006 CacheWolf development team See http://www.cachewolf.de/ for more information. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package CacheWolf.exp; import com.stevesoft.ewe_pat.Regex; import CacheWolf.MainForm; import CacheWolf.Preferences; import CacheWolf.controls.ExecutePanel; import CacheWolf.controls.InfoBox; import CacheWolf.database.CacheDB; import CacheWolf.database.CacheHolder; import CacheWolf.database.CacheHolderDetail; import CacheWolf.database.CacheImage; import CacheWolf.database.CacheType; import CacheWolf.utils.Common; import CacheWolf.utils.Files; import CacheWolf.utils.MyLocale; import CacheWolf.utils.SafeXML; import ewe.filechooser.FileChooser; import ewe.filechooser.FileChooserBase; import ewe.io.File; import ewe.net.URL; import ewe.sys.Handle; import ewe.ui.CellConstants; import ewe.ui.CheckBoxGroup; import ewe.ui.ControlEvent; import ewe.ui.Event; import ewe.ui.Form; import ewe.ui.FormBase; import ewe.ui.Panel; import ewe.ui.ProgressBarForm; import ewe.ui.mCheckBox; import ewe.ui.mInput; import ewe.ui.mLabel; import ewe.util.Hashtable; public class GarminPicExporter { /************************************************************************************ * Exports pictures and spoiler pictures into a directory structure * * See: http://garmin.blogs.com/softwareupdates/2012/01/geocaching-with-photos.html * ************************************************************************************/ private static String SPOILERREGEX = "Spoiler|Hilfe|Help|Hinweis|Hint[^a-zA-Z]"; CacheDB cacheDB; Hashtable picsCopied = new Hashtable(40); /* This table is used by copyImages to check whether a picture has been copied already. * Normally it should be created in copyImages (and destroyed upon return). To avoid * unneeded object generation, it is created here and cleared in copyImages (which is a * much faster operation). */ int nonJPGimages = 0; int whichPics; // 0=ALL, 1=SPOILER ONLY, 2=OTHERS ONLY, 3=SPOILERS + ALL PICS for non TRADIS boolean removeGC; boolean resizeLongEdge; boolean restrictToJpg; int maxLongEdge; Regex spoilerRex; public GarminPicExporter() { cacheDB = MainForm.profile.cacheDB; } public final static String expName = "GARMIN"; public void doIt() { // Select destination directory FileChooser fc = new FileChooser(FileChooserBase.DIRECTORY_SELECT, Preferences.itself().getExportPath(expName)); fc.setTitle("Select target directory:"); String targetDir; if (fc.execute() == FormBase.IDCANCEL) return; targetDir = fc.getChosen() + "/"; Preferences.itself().setExportPref(expName, targetDir); // Select export options OptionsForm options = new OptionsForm(); if (options.execute() == FormBase.IDCANCEL) return; whichPics = options.getWhichPics(); resizeLongEdge = options.getResizeLongEdge(); maxLongEdge = options.getMaxLongEdge(); restrictToJpg = options.getRestrictToJpg(); spoilerRex = new Regex(options.getSplrRegex()); spoilerRex.setIgnoreCase(true); // Keep user updated about our progress ProgressBarForm pbf = new ProgressBarForm(); Handle h = new Handle(); pbf.showMainTask = false; pbf.setTask(h, "Exporting ..."); pbf.exec(); int exportErrors = 0; int counter = cacheDB.countVisible(); // Main loop over visible caches for (int i = 0; i < counter; i++) { h.progress = (float) (i + 1) / (float) counter; h.changed(); CacheHolder ch = cacheDB.get(i); if (ch.isVisible()) { if (ch.isIncomplete()) { exportErrors++; Preferences.itself().log("GarminPicExporter: skipping export of incomplete waypoint " + ch.getCode()); continue; } exportErrors += copyImages(ch, targetDir); } //if is black, filtered } pbf.exit(0); if (exportErrors > 0) { new InfoBox(MyLocale.getMsg(5500, "Error"), exportErrors + " errors during export. See log for details.").wait(FormBase.OKB); } if (nonJPGimages > 0) { new InfoBox("Some pictures not copied", nonJPGimages + " pictures were not copied because Garmin GPS can only handle the JPG format. See log for details.").wait(FormBase.OKB); } } private int copyImages(CacheHolder ch, String targetDir) { String dirName = ""; CacheHolderDetail det = ch.getDetails(); if (det == null) return 1; // No details; increment export errors int nImg = det.images.size(); if (nImg == 0) return 0; // Nothing to copy int retCode = 0; boolean need2CreateDir = true; // Flag to remember whether dirs have been created picsCopied.clear(); // Clear the hashtable which keeps track of pictures copied for (int i = nImg - 1; i >= 0; i--) { // Start from top to get more pics with sensible names // The pictures embedded in the text description have no title and are at the beginning CacheImage imgInfo = det.images.get(i); // Skip this pic if it was already copied if (picsCopied.containsKey(imgInfo.getFilename())) { continue; } try { boolean copyPic = false; // Does the pic satisfy the criteria for copying? boolean isSpoiler = false; if (spoilerRex.search(imgInfo.getTitle())) { isSpoiler = true; if (whichPics != 2) copyPic = true; } else { // Normal Pic if (whichPics == 0 || whichPics == 2 || (whichPics == 3 && ch.getType() != CacheType.CW_TYPE_TRADITIONAL)) copyPic = true; } if (copyPic) { // We have to copy this picture if (restrictToJpg) { if (!imgInfo.getFilename().toLowerCase().endsWith("jpg")) { // relies on filename being lower case // Garmin GPS can only handle jpg files Preferences.itself().log("GarminPicExporter: Warning: Picture " + imgInfo.getFilename() + " not copied as Garmin GPS can only handle jpg files"); nonJPGimages++; continue; // Move to next pic } } String extension = Common.getExtension(imgInfo.getFilename()).toUpperCase(); // garmin loves uppercase? // Now we have a jpg and need to ensure that the directory structure // has been created before copying the picture if (need2CreateDir) { dirName = createPicDir(targetDir, ch.getCode()); if (dirName == null) return 1; // Failed to create dir need2CreateDir = false; } picsCopied.put(imgInfo.getFilename(), null); // Remember that we copied this picture String dstFilename = getFilenameFromTitle(imgInfo.getTitle()); if (dstFilename.length() == 0) { dstFilename = getFileNameFromUrl(imgInfo.getURL()); if (dstFilename.length() == 0) { dstFilename = Common.changeExtension(imgInfo.getFilename(), extension); } else { dstFilename = Common.changeExtension(dstFilename, extension); } } else { dstFilename = dstFilename + extension; } String dstPath = dirName; if (isSpoiler) { dstPath = appendDir(dirName, "Spoilers/"); } Files.copy(MainForm.profile.dataDir + imgInfo.getFilename(), dstPath + dstFilename); } } catch (Exception ex) { Preferences.itself().log("GarminPicExporter: Error copying file " + imgInfo.getFilename()); retCode = 1; // Signal error to calling program } } return retCode; } private String getFileNameFromUrl(String url) { if (url == null || url.length() == 0) return ""; int dot = url.lastIndexOf('/'); if (dot < 0) return ""; String ret = url.substring(dot + 1, url.length()); return URL.decodeURL(ret); } private String getFilenameFromTitle(String fileName) { // The next line should not be necessary as picture titles should be correctly stored in UTF8 internally // Unfortunately this is not the case, e.g. GC1ZHRK if (fileName.indexOf("&") >= 0) fileName = SafeXML.html2iso8859s1(fileName); int len = fileName.length(); StringBuffer s = new StringBuffer(len); for (int i = 0; i < len; i++) { char ch = fileName.charAt(i); if ((ch == ' ') || (ch == '_') || ((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')) || ((ch >= '0') && (ch <= '9')) || (ch == '�') || (ch == '�') || (ch == '�') || (ch == '�') || (ch == '�') || (ch == '�') || (ch == '�')) s.append(ch); } return s.toString(); } /** * Create the directories to receive the pictures * * Photos \exportDir\Last Character\Second To Last Character\Full Code\ * * Spoiler Photos <Photos Path>\Spoilers\ * * If the geocache has only three characters total, a 0 (zero) is used for the second to last character. * * The exportDir has to be copied to */ private String createPicDir(String targetDir, String wayPoint) { String dirName; int len = wayPoint.length(); String dir1, dir2; dir1 = wayPoint.charAt(len - 1) + "/"; if (len > 3) dir2 = wayPoint.charAt(len - 2) + "/"; else dir2 = "0"; try { String GCF = "GeocachePhotos/"; dirName = appendDir(appendDir(appendDir(appendDir(targetDir, GCF), dir1), dir2), wayPoint); } catch (Exception ex) { Preferences.itself().log("GarminPicExporter: Error creating directories for cache " + wayPoint + "\n" + ex.getMessage()); return null; // Signal error to calling program } return dirName; } /** * Creates a sub-directory within a base directory * * @param baseDir * The full path of the base directory to be extended * @param appendDir * The name of the sub directory * @return The full path pointing to the newly created sub-directory */ private String appendDir(String baseDir, String appendDir) { File file = new File(baseDir + appendDir); if (!file.exists()) file.createDir(); return file.getAbsolutePath() + "/"; } /********************************************** * Form for options ********************************************** */ private class OptionsForm extends Form { Panel pnlWhichPics = new Panel(); mCheckBox chkAllPics, chkSplrOnly, chkPicsOnly, chkRestrictToJpg, chkSplrPlusNonTradi; CheckBoxGroup chkPics2Copy = new CheckBoxGroup(); mInput inpSplrRegex; mCheckBox chkResizeLongEdge; mLabel lblMaxLongEdge; mInput inpMaxLongEdge; private final ExecutePanel executePanel; public OptionsForm() { super(); //this.setPreferredSize(400,300); this.setTitle("Export Options"); // Which pictures to export (Radiobuttons) pnlWhichPics.addLast(chkAllPics = new mCheckBox("All"), CellConstants.DONTSTRETCH, CellConstants.WEST); pnlWhichPics.addLast(chkSplrOnly = new mCheckBox("Only Spoilers"), CellConstants.DONTSTRETCH, CellConstants.WEST); pnlWhichPics.addLast(chkPicsOnly = new mCheckBox("Only Pictures"), CellConstants.DONTSTRETCH, CellConstants.WEST); pnlWhichPics.addLast(chkSplrPlusNonTradi = new mCheckBox("All Spoilers plus all pics for non-tradi Caches"), CellConstants.DONTSTRETCH, CellConstants.WEST); chkAllPics.setGroup(chkPics2Copy); chkSplrOnly.setGroup(chkPics2Copy); chkPicsOnly.setGroup(chkPics2Copy); chkSplrPlusNonTradi.setGroup(chkPics2Copy); chkPics2Copy.setInt(0); pnlWhichPics.addLast(chkRestrictToJpg = new mCheckBox("Only jpg"), CellConstants.DONTSTRETCH, CellConstants.WEST); chkRestrictToJpg.setState(true); addLast(new mLabel("Which pictures to export"), CellConstants.DONTSTRETCH, CellConstants.TOP | CellConstants.WEST); pnlWhichPics.setBorder(BDR_OUTLINE | BF_RECT, 3); addLast(pnlWhichPics, CellConstants.HSTRETCH, CellConstants.TOP | CellConstants.WEST); addLast(new mLabel("Regular Expression to identify spoiler pictures (ignore case):")); addLast(inpSplrRegex = new mInput(SPOILERREGEX), CellConstants.HSTRETCH, CellConstants.HFILL | CellConstants.TOP | CellConstants.WEST); // Should the longest edge be resized and if so to which max length //TODO addLast(chkResizeLongEdge=new mCheckBox("Resize long edge of large pictures (takes much longer)")); //addNext(lblMaxLongEdge=new mLabel("Max. long edge:"),CellConstants.DONTSTRETCH,CellConstants.TOP|CellConstants.WEST); //addLast(inpMaxLongEdge=new mInput("1000"),CellConstants.DONTSTRETCH,CellConstants.TOP|CellConstants.WEST); //lblMaxLongEdge.set(Disabled,true); //inpMaxLongEdge.set(Disabled,true); // Buttons to control closing of the form executePanel = new ExecutePanel(this); } public void onEvent(Event ev) { if (ev instanceof ControlEvent && ev.type == ControlEvent.PRESSED) { if (ev.target == executePanel.cancelButton) { this.close(FormBase.IDCANCEL); } if (ev.target == executePanel.applyButton) { this.close(FormBase.IDOK); } if (ev.target == chkResizeLongEdge) { lblMaxLongEdge.set(Disabled, !chkResizeLongEdge.getState()); inpMaxLongEdge.set(Disabled, !chkResizeLongEdge.getState()); this.repaint(); } } super.onEvent(ev); } public int getWhichPics() { return chkPics2Copy.getInt(); } public String getSplrRegex() { return inpSplrRegex.getText(); } public boolean getResizeLongEdge() { return false; //TODO return chkResizeLongEdge.getState(); } public int getMaxLongEdge() { return 100000; //TODO return Common.parseInt(inpMaxLongEdge.getText()); } public boolean getRestrictToJpg() { return chkRestrictToJpg.getState(); } } }