diff options
Diffstat (limited to 'main/src/cgeo/geocaching/export')
| -rw-r--r-- | main/src/cgeo/geocaching/export/AbstractExport.java | 32 | ||||
| -rw-r--r-- | main/src/cgeo/geocaching/export/Export.java | 29 | ||||
| -rw-r--r-- | main/src/cgeo/geocaching/export/ExportFactory.java | 65 | ||||
| -rw-r--r-- | main/src/cgeo/geocaching/export/FieldnoteExport.java | 257 | ||||
| -rw-r--r-- | main/src/cgeo/geocaching/export/GpxExport.java | 301 |
5 files changed, 684 insertions, 0 deletions
diff --git a/main/src/cgeo/geocaching/export/AbstractExport.java b/main/src/cgeo/geocaching/export/AbstractExport.java new file mode 100644 index 0000000..85b060b --- /dev/null +++ b/main/src/cgeo/geocaching/export/AbstractExport.java @@ -0,0 +1,32 @@ +package cgeo.geocaching.export; + +import cgeo.geocaching.cgeoapplication; + +abstract class AbstractExport implements Export { + private final String name; + + protected AbstractExport(final String name) { + this.name = name; + } + + public String getName() { + return name; + } + + /** + * Generates a localized string from a resource id. + * + * @param resourceId + * the resource id of the string + * @return localized string + */ + protected static String getString(int resourceId) { + return cgeoapplication.getInstance().getString(resourceId); + } + + @Override + public String toString() { + // used in the array adapter of the dialog showing the exports + return getName(); + } +} diff --git a/main/src/cgeo/geocaching/export/Export.java b/main/src/cgeo/geocaching/export/Export.java new file mode 100644 index 0000000..7a2b075 --- /dev/null +++ b/main/src/cgeo/geocaching/export/Export.java @@ -0,0 +1,29 @@ +package cgeo.geocaching.export; + +import cgeo.geocaching.cgCache; + +import android.app.Activity; + +import java.util.List; + +/** + * Represents an exporter to export a {@link List} of {@link cgCache} to various formats. + */ +interface Export { + /** + * Export a {@link List} of {@link cgCache} to various formats. + * + * @param caches + * The {@link List} of {@link cgCache} to be exported + * @param activity + * optional: Some exporters might have an UI which requires an {@link Activity} + */ + public void export(List<cgCache> caches, Activity activity); + + /** + * Get the localized name of this exporter. + * + * @return localized name + */ + public String getName(); +} diff --git a/main/src/cgeo/geocaching/export/ExportFactory.java b/main/src/cgeo/geocaching/export/ExportFactory.java new file mode 100644 index 0000000..8b3df58 --- /dev/null +++ b/main/src/cgeo/geocaching/export/ExportFactory.java @@ -0,0 +1,65 @@ +package cgeo.geocaching.export; + +import cgeo.geocaching.R; +import cgeo.geocaching.cgCache; +import cgeo.geocaching.utils.Log; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.widget.ArrayAdapter; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Factory to create a dialog with all available exporters. + */ +public abstract class ExportFactory { + + /** + * Contains instances of all available exporter classes. + */ + private static final List<Class<? extends Export>> exporterClasses; + + static { + final ArrayList<Class<? extends Export>> temp = new ArrayList<Class<? extends Export>>(); + temp.add(FieldnoteExport.class); + temp.add(GpxExport.class); + exporterClasses = Collections.unmodifiableList(temp); + } + + /** + * Creates a dialog so that the user can select an exporter. + * + * @param caches + * The {@link List} of {@link cgCache} to be exported + * @param activity + * The {@link Activity} in whose context the dialog should be shown + */ + public static void showExportMenu(final List<cgCache> caches, final Activity activity) { + final AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setTitle(R.string.export).setIcon(android.R.drawable.ic_menu_share); + + final ArrayList<Export> export = new ArrayList<Export>(); + for (Class<? extends Export> exporterClass : exporterClasses) { + try { + export.add(exporterClass.newInstance()); + } catch (Exception ex) { + Log.e("showExportMenu", ex); + } + } + + final ArrayAdapter<Export> adapter = new ArrayAdapter<Export>(activity, android.R.layout.select_dialog_item, export); + + builder.setAdapter(adapter, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + final Export selectedExport = adapter.getItem(item); + selectedExport.export(caches, activity); + } + }); + + builder.create().show(); + } +}
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/export/FieldnoteExport.java b/main/src/cgeo/geocaching/export/FieldnoteExport.java new file mode 100644 index 0000000..05fb828 --- /dev/null +++ b/main/src/cgeo/geocaching/export/FieldnoteExport.java @@ -0,0 +1,257 @@ +package cgeo.geocaching.export; + +import cgeo.geocaching.LogEntry; +import cgeo.geocaching.R; +import cgeo.geocaching.cgCache; +import cgeo.geocaching.cgeoapplication; +import cgeo.geocaching.activity.ActivityMixin; +import cgeo.geocaching.activity.Progress; +import cgeo.geocaching.enumerations.LogType; +import cgeo.geocaching.utils.Log; + +import org.apache.commons.lang3.StringUtils; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.ProgressDialog; +import android.os.AsyncTask; +import android.os.Environment; +import android.view.View; +import android.widget.Button; +import android.widget.CheckBox; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Exports offline-logs in the Groundspeak Field Note format.<br> + * <br> + * + * Field Notes are simple plain text files, but poorly documented. Syntax:<br> + * <code>GCxxxxx,yyyy-mm-ddThh:mm:ssZ,Found it,"logtext"</code> + */ +class FieldnoteExport extends AbstractExport { + private static final File exportLocation = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/field-notes"); + private static final SimpleDateFormat fieldNoteDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + + protected FieldnoteExport() { + super(getString(R.string.export_fieldnotes)); + } + + /** + * A dialog to allow the user to set options for the export. + * + * Currently available options are: upload field notes, only new logs since last export/upload + */ + private class ExportOptionsDialog extends AlertDialog { + public ExportOptionsDialog(final List<cgCache> caches, final Activity activity) { + super(activity); + + View layout = activity.getLayoutInflater().inflate(R.layout.fieldnote_export_dialog, null); + setView(layout); + + ((Button) layout.findViewById(R.id.export)).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + dismiss(); + new ExportTask( + caches, + activity, + ((CheckBox) findViewById(R.id.upload)).isChecked(), + ((CheckBox) findViewById(R.id.onlynew)).isChecked()) + .execute((Void) null); + } + }); + } + } + + @Override + public void export(final List<cgCache> caches, final Activity activity) { + if (null == activity) { + // No activity given, so no user interaction possible. + // Start export with default parameters. + new ExportTask(caches, null, false, false).execute((Void) null); + } else { + // Show configuration dialog + new ExportOptionsDialog(caches, activity).show(); + } + } + + private class ExportTask extends AsyncTask<Void, Integer, Boolean> { + private final List<cgCache> caches; + private final Activity activity; + private final boolean onlyNew; + private final Progress progress = new Progress(); + private File exportFile; + + private static final int STATUS_UPLOAD = -1; + + /** + * Instantiates and configurates the task for exporting field notes. + * + * @param caches + * The {@link List} of {@link cgCache} to be exported + * @param activity + * optional: Show a progress bar and toasts + * @param upload + * Upload the Field Note to geocaching.com + * @param onlyNew + * Upload/export only new logs since last export + */ + public ExportTask(final List<cgCache> caches, final Activity activity, final boolean upload, final boolean onlyNew) { + this.caches = caches; + this.activity = activity; + this.onlyNew = onlyNew; + } + + @Override + protected void onPreExecute() { + if (null != activity) { + progress.show(activity, null, getString(R.string.export) + ": " + getName(), ProgressDialog.STYLE_HORIZONTAL, null); + progress.setMaxProgressAndReset(caches.size()); + } + } + + @Override + protected Boolean doInBackground(Void... params) { + final StringBuilder fieldNoteBuffer = new StringBuilder(); + + // We need our own HashMap because LogType will give us localized and maybe + // different strings than gc.com expects in the field note + // We only need such logtypes that are possible to log via c:geo + Map<LogType, String> logTypes = new HashMap<LogType, String>(); + logTypes.put(LogType.LOG_FOUND_IT, "Found it"); + logTypes.put(LogType.LOG_DIDNT_FIND_IT, "Didn't find it"); + logTypes.put(LogType.LOG_NOTE, "Write Note"); + logTypes.put(LogType.LOG_NEEDS_ARCHIVE, "Needs archived"); + logTypes.put(LogType.LOG_NEEDS_MAINTENANCE, "Needs Maintenance"); + logTypes.put(LogType.LOG_WILL_ATTEND, "Will Attend"); + logTypes.put(LogType.LOG_ATTENDED, "Attended"); + logTypes.put(LogType.LOG_WEBCAM_PHOTO_TAKEN, "Webcam Photo Taken"); + + for (int i = 0; i < caches.size(); i++) { + try { + final cgCache cache = caches.get(i); + if (cache.isLogOffline()) { + LogEntry log = cgeoapplication.getInstance().loadLogOffline(cache.getGeocode()); + if (null != logTypes.get(log.type)) { + fieldNoteBuffer.append(cache.getGeocode()) + .append(',') + .append(fieldNoteDateFormat.format(new Date(log.date))) + .append(',') + .append(logTypes.get(log.type)) + .append(",\"") + .append(StringUtils.replaceChars(log.log, '"', '\'')) + .append("\"\n"); + } + } + publishProgress(i + 1); + } catch (Exception e) { + Log.e("FieldnoteExport.ExportTask generation", e); + return false; + } + } + + fieldNoteBuffer.append("\n"); + + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + exportLocation.mkdirs(); + + SimpleDateFormat fileNameDateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); + exportFile = new File(exportLocation.toString() + '/' + fileNameDateFormat.format(new Date()) + ".txt"); + + OutputStream os = null; + Writer fw = null; + try { + os = new FileOutputStream(exportFile); + fw = new OutputStreamWriter(os, "ISO-8859-1"); // TODO: gc.com doesn't support UTF-8 + fw.write(fieldNoteBuffer.toString()); + } catch (IOException e) { + Log.e("FieldnoteExport.ExportTask export", e); + return false; + } finally { + if (fw != null) { + try { + fw.close(); + } catch (IOException e) { + Log.e("FieldnoteExport.ExportTask export", e); + return false; + } + } + } + } else { + return false; + } + + /* + * if (upload) { + * TODO Use multipart POST request for uploading + * publishProgress(STATUS_UPLOAD); + * + * final Parameters uploadParams = new Parameters( + * "__EVENTTARGET", "", + * "__EVENTARGUMENT", "", + * "__VIEWSTATE", "", + * //TODO "ctl00$ContentBody$chkSuppressDate", "on", + * "ctl00$ContentBody$FieldNoteLoader", fieldNoteBuffer.toString(), + * "ctl00$ContentBody$btnUpload", "Upload Field Note"); + * final String uri = "http://www.geocaching.com/my/uploadfieldnotes.aspx"; + * + * String page = Network.getResponseData(Network.postRequest(uri, uploadParams)); + * if (!Login.getLoginStatus(page)) { + * final StatusCode loginState = Login.login(); + * if (loginState == StatusCode.NO_ERROR) { + * page = Network.getResponseData(Network.postRequest(uri, uploadParams)); + * } else { + * Log.e(Settings.tag, "FieldnoteExport.ExportTask upload: No login (error: " + loginState + ")"); + * return false; + * } + * } + * + * if (StringUtils.isBlank(page)) { + * Log.e(Settings.tag, "FieldnoteExport.ExportTask upload: No data from server"); + * return false; + * } + * } + */ + + return true; + } + + @Override + protected void onPostExecute(Boolean result) { + if (null != activity) { + progress.dismiss(); + + if (result) { + if (onlyNew) { + // update last export time in settings + } + ActivityMixin.showToast(activity, getName() + ' ' + getString(R.string.export_exportedto) + ": " + exportFile.toString()); + } else { + ActivityMixin.showToast(activity, getString(R.string.export_failed)); + } + } + } + + @Override + protected void onProgressUpdate(Integer... status) { + if (null != activity) { + if (STATUS_UPLOAD == status[0]) { + progress.setMessage(getString(R.string.export_fieldnotes_uploading)); + } else { + progress.setProgress(status[0]); + } + } + } + } +} diff --git a/main/src/cgeo/geocaching/export/GpxExport.java b/main/src/cgeo/geocaching/export/GpxExport.java new file mode 100644 index 0000000..2c833a2 --- /dev/null +++ b/main/src/cgeo/geocaching/export/GpxExport.java @@ -0,0 +1,301 @@ +package cgeo.geocaching.export; + +import cgeo.geocaching.R; +import cgeo.geocaching.cgCache; +import cgeo.geocaching.LogEntry; +import cgeo.geocaching.cgeoapplication; +import cgeo.geocaching.activity.ActivityMixin; +import cgeo.geocaching.activity.Progress; +import cgeo.geocaching.enumerations.CacheAttribute; +import cgeo.geocaching.enumerations.LoadFlags; +import cgeo.geocaching.utils.BaseUtils; +import cgeo.geocaching.utils.Log; + +import org.apache.commons.lang3.StringEscapeUtils; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.os.AsyncTask; +import android.os.Environment; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; + +class GpxExport extends AbstractExport { + private static final File exportLocation = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/gpx-export"); + private static final SimpleDateFormat dateFormatZ = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + + protected GpxExport() { + super(getString(R.string.export_gpx)); + } + + @Override + public void export(final List<cgCache> caches, final Activity activity) { + new ExportTask(caches, activity).execute((Void) null); + } + + private class ExportTask extends AsyncTask<Void, Integer, Boolean> { + private final List<cgCache> caches; + private final Activity activity; + private final Progress progress = new Progress(); + private File exportFile; + + /** + * Instantiates and configures the task for exporting field notes. + * + * @param caches + * The {@link List} of {@link cgCache} to be exported + * @param activity + * optional: Show a progress bar and toasts + */ + public ExportTask(final List<cgCache> caches, final Activity activity) { + this.caches = caches; + this.activity = activity; + } + + @Override + protected void onPreExecute() { + if (null != activity) { + progress.show(activity, null, getString(R.string.export) + ": " + getName(), ProgressDialog.STYLE_HORIZONTAL, null); + progress.setMaxProgressAndReset(caches.size()); + } + } + + @Override + protected Boolean doInBackground(Void... params) { + // quick check for being able to write the GPX file + if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + return false; + } + + // FIXME: complete export is created in memory. That should be some file stream instead. + final StringBuilder gpx = new StringBuilder(); + + gpx.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); + gpx.append("<gpx version=\"1.0\" creator=\"c:geo - http://www.cgeo.org\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://www.topografix.com/GPX/1/0\" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd http://www.groundspeak.com/cache/1/0/1 http://www.groundspeak.com/cache/1/0/1/cache.xsd\">"); + + try { + for (int i = 0; i < caches.size(); i++) { + cgCache cache = caches.get(i); + + if (!cache.isDetailed()) { + cache = cgeoapplication.getInstance().loadCache(caches.get(i).getGeocode(), LoadFlags.LOAD_ALL_DB_ONLY); + } + + gpx.append("<wpt "); + gpx.append("lat=\"" + cache.getCoords().getLatitude() + "\" "); + gpx.append("lon=\"" + cache.getCoords().getLongitude() + "\">"); + + gpx.append("<time>"); + gpx.append(StringEscapeUtils.escapeXml(dateFormatZ.format(cache.getHiddenDate()))); + gpx.append("</time>"); + + gpx.append("<name>"); + gpx.append(StringEscapeUtils.escapeXml(cache.getGeocode())); + gpx.append("</name>"); + + gpx.append("<desc>"); + gpx.append(StringEscapeUtils.escapeXml(cache.getName())); + gpx.append("</desc>"); + + gpx.append("<sym>"); + gpx.append(cache.isFound() ? "Geocache Found" : "Geocache"); + gpx.append("</sym>"); + + gpx.append("<type>"); + gpx.append(StringEscapeUtils.escapeXml("Geocache|" + cache.getType().toString())); //TODO: Correct (english) string + gpx.append("</type>"); + + gpx.append("<groundspeak:cache "); + gpx.append("available=\"" + (!cache.isDisabled() ? "True" : "False")); + gpx.append("\" archived=\"" + (cache.isArchived() ? "True" : "False") + "\" "); + gpx.append("xmlns:groundspeak=\"http://www.groundspeak.com/cache/1/0/1\">"); + + gpx.append("<groundspeak:name>"); + gpx.append(StringEscapeUtils.escapeXml(cache.getName())); + gpx.append("</groundspeak:name>"); + + gpx.append("<groundspeak:placed_by>"); + gpx.append(StringEscapeUtils.escapeXml(cache.getOwner())); + gpx.append("</groundspeak:placed_by>"); + + gpx.append("<groundspeak:owner>"); + gpx.append(StringEscapeUtils.escapeXml(cache.getOwnerReal())); + gpx.append("</groundspeak:owner>"); + + gpx.append("<groundspeak:type>"); + gpx.append(StringEscapeUtils.escapeXml(cache.getType().toString())); //TODO: Correct (english) string + gpx.append("</groundspeak:type>"); + + gpx.append("<groundspeak:container>"); + gpx.append(StringEscapeUtils.escapeXml(cache.getSize().toString())); //TODO: Correct (english) string + gpx.append("</groundspeak:container>"); + + if (cache.hasAttributes()) { + //TODO: Attribute conversion required: English verbose name, gpx-id + gpx.append("<groundspeak:attributes>"); + + for (String attribute : cache.getAttributes()) { + final CacheAttribute attr = CacheAttribute.getByGcRawName(CacheAttribute.trimAttributeName(attribute)); + final boolean enabled = attribute.endsWith(CacheAttribute.INTERNAL_YES); + + gpx.append("<groundspeak:attribute id=\""); + gpx.append(attr.id); + gpx.append("\" inc=\""); + if (enabled) { + gpx.append('1'); + } else { + gpx.append('0'); + } + gpx.append("\">"); + gpx.append(StringEscapeUtils.escapeXml(attr.getL10n(enabled))); + gpx.append("</groundspeak:attribute>"); + } + + gpx.append("</groundspeak:attributes>"); + } + + gpx.append("<groundspeak:difficulty>"); + gpx.append(Float.toString(cache.getDifficulty())); + gpx.append("</groundspeak:difficulty>"); + + gpx.append("<groundspeak:terrain>"); + gpx.append(Float.toString(cache.getTerrain())); + gpx.append("</groundspeak:terrain>"); + + gpx.append("<groundspeak:country>"); + gpx.append(StringEscapeUtils.escapeXml(cache.getLocation())); + gpx.append("</groundspeak:country>"); + + gpx.append("<groundspeak:state>"); + gpx.append(StringEscapeUtils.escapeXml(cache.getLocation())); + gpx.append("</groundspeak:state>"); + + gpx.append("<groundspeak:short_description html=\""); + if (BaseUtils.containsHtml(cache.getShortDescription())) { + gpx.append("True"); + } else { + gpx.append("False"); + } + gpx.append("\">"); + gpx.append(StringEscapeUtils.escapeXml(cache.getShortDescription())); + gpx.append("</groundspeak:short_description>"); + + gpx.append("<groundspeak:long_description html=\""); + if (BaseUtils.containsHtml(cache.getDescription())) { + gpx.append("True"); + } else { + gpx.append("False"); + } + gpx.append("\">"); + gpx.append(StringEscapeUtils.escapeXml(cache.getDescription())); + gpx.append("</groundspeak:long_description>"); + + gpx.append("<groundspeak:encoded_hints>"); + gpx.append(StringEscapeUtils.escapeXml(cache.getHint())); + gpx.append("</groundspeak:encoded_hints>"); + + gpx.append("</groundspeak:cache>"); + + //TODO: Waypoints + + if (cache.getLogs().size() > 0) { + gpx.append("<groundspeak:logs>"); + + for (LogEntry log : cache.getLogs()) { + gpx.append("<groundspeak:log id=\""); + gpx.append(log.id); + gpx.append("\">"); + + gpx.append("<groundspeak:date>"); + gpx.append(StringEscapeUtils.escapeXml(dateFormatZ.format(new Date(log.date)))); + gpx.append("</groundspeak:date>"); + + gpx.append("<groundspeak:type>"); + gpx.append(StringEscapeUtils.escapeXml(log.type.type)); + gpx.append("</groundspeak:type>"); + + gpx.append("<groundspeak:finder id=\"\">"); + gpx.append(StringEscapeUtils.escapeXml(log.author)); + gpx.append("</groundspeak:finder>"); + + gpx.append("<groundspeak:text encoded=\"False\">"); + gpx.append(StringEscapeUtils.escapeXml(log.log)); + gpx.append("</groundspeak:text>"); + + gpx.append("</groundspeak:log>"); + } + + gpx.append("</groundspeak:logs>"); + } + + gpx.append("</wpt>"); + + publishProgress(i + 1); + } + } catch (Exception e) { + Log.e("GpxExport.ExportTask generation", e); + return false; + } + + gpx.append("</gpx>"); + + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + exportLocation.mkdirs(); + + SimpleDateFormat fileNameDateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); + exportFile = new File(exportLocation.toString() + '/' + fileNameDateFormat.format(new Date()) + ".gpx"); + + OutputStream os = null; + Writer fw = null; + try { + os = new FileOutputStream(exportFile); + fw = new OutputStreamWriter(os, "UTF-8"); + fw.write(gpx.toString()); + } catch (IOException e) { + Log.e("GpxExport.ExportTask export", e); + return false; + } finally { + if (fw != null) { + try { + fw.close(); + } catch (IOException e) { + Log.e("GpxExport.ExportTask export", e); + return false; + } + } + } + } else { + return false; + } + + return true; + } + + @Override + protected void onPostExecute(Boolean result) { + if (null != activity) { + progress.dismiss(); + if (result) { + ActivityMixin.showToast(activity, getName() + ' ' + getString(R.string.export_exportedto) + ": " + exportFile.toString()); + } else { + ActivityMixin.showToast(activity, getString(R.string.export_failed)); + } + } + } + + @Override + protected void onProgressUpdate(Integer... status) { + if (null != activity) { + progress.setProgress(status[0]); + } + } + } +} |
