/*------------------------------------------------------------------------------
** Ident: Sogeti Smart Mobile Solutions
** Author: rene
** Copyright: (c) Apr 24, 2011 Sogeti Nederland B.V. All Rights Reserved.
**------------------------------------------------------------------------------
** Sogeti Nederland B.V. | No part of this file may be reproduced
** Distributed Software Engineering | or transmitted in any form or by any
** Lange Dreef 17 | means, electronic or mechanical, for the
** 4131 NJ Vianen | purpose, without the express written
** The Netherlands | permission of the copyright holder.
*------------------------------------------------------------------------------
*
* This file is part of OpenGPSTracker.
*
* OpenGPSTracker 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, either version 3 of the License, or
* (at your option) any later version.
*
* OpenGPSTracker 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 OpenGPSTracker. If not, see <http://www.gnu.org/licenses/>.
*
*/
package nl.sogeti.android.gpstracker.actions.tasks;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Environment;
import android.util.Log;
import android.view.Window;
import nl.sogeti.android.gpstracker.actions.utils.ProgressListener;
import nl.sogeti.android.gpstracker.db.GPStracking.Media;
import nl.sogeti.android.gpstracker.db.GPStracking.Tracks;
import nl.sogeti.android.gpstracker.db.GPStracking.Waypoints;
import nl.sogeti.android.gpstracker.util.Constants;
import nl.sogeti.android.gpstracker.util.ProgressAdmin;
import org.xmlpull.v1.XmlSerializer;
import java.io.*;
import java.nio.channels.FileChannel;
import java.util.concurrent.CancellationException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* Async XML creation task Execute without parameters (Void) Update posted with
* single Integer And result is a filename in a String
*
* @version $Id: XmlCreator.java 1218 2012-01-05 19:29:23Z rcgroot $
* @author rene (c) May 29, 2011, Sogeti B.V.
*/
public abstract class XmlCreator extends AsyncTask<Void, Integer, Uri>
{
private String TAG = "OGT.XmlCreator";
private String mExportDirectoryPath;
private boolean mNeedsBundling;
String mChosenName;
private ProgressListener mProgressListener;
protected Context mContext;
protected Uri mTrackUri;
String mFileName;
private String mErrorText;
private Exception mException;
private String mTask;
public XmlCreatorProgressAdmin mXmlCreatorProgressAdmin;
XmlCreator(Context context, Uri trackUri, String chosenFileName, ProgressListener listener)
{
mChosenName = chosenFileName;
mContext = context;
mTrackUri = trackUri;
mProgressListener = listener;
mXmlCreatorProgressAdmin = new XmlCreatorProgressAdmin();
String trackName = extractCleanTrackName();
mFileName = cleanFilename(mChosenName, trackName);
}
private String extractCleanTrackName()
{
Cursor trackCursor = null;
ContentResolver resolver = mContext.getContentResolver();
String trackName = "Untitled";
try
{
trackCursor = resolver.query(mTrackUri, new String[] { Tracks.NAME }, null, null, null);
if (trackCursor.moveToLast())
{
trackName = cleanFilename(trackCursor.getString(0), trackName);
}
}
finally
{
if (trackCursor != null)
{
trackCursor.close();
}
}
return trackName;
}
/**
* Calculated the total progress sum expected from a export to file This is
* the sum of the number of waypoints and media entries times 100. The whole
* number is doubled when compression is needed.
*/
public void determineProgressGoal()
{
if (mProgressListener != null)
{
Uri allWaypointsUri = Uri.withAppendedPath(mTrackUri, "waypoints");
Uri allMediaUri = Uri.withAppendedPath(mTrackUri, "media");
Cursor cursor = null;
ContentResolver resolver = mContext.getContentResolver();
try
{
cursor = resolver.query(allWaypointsUri, new String[] { "count(" + Waypoints.TABLE + "." + Waypoints._ID + ")" }, null, null, null);
if (cursor.moveToLast())
{
mXmlCreatorProgressAdmin.setWaypointCount(cursor.getInt(0));
}
cursor.close();
cursor = resolver.query(allMediaUri, new String[] { "count(" + Media.TABLE + "." + Media._ID + ")" }, null, null, null);
if (cursor.moveToLast())
{
mXmlCreatorProgressAdmin.setMediaCount(cursor.getInt(0));
}
cursor.close();
cursor = resolver.query(allMediaUri, new String[] { "count(" + Tracks._ID + ")" }, Media.URI + " LIKE ? and " + Media.URI + " NOT LIKE ?",
new String[] { "file://%", "%txt" }, null);
if (cursor.moveToLast())
{
mXmlCreatorProgressAdmin.setCompress( cursor.getInt(0) > 0 );
}
}
finally
{
if (cursor != null)
{
cursor.close();
}
}
}
else
{
Log.w(TAG, "Exporting " + mTrackUri + " without progress!");
}
}
/**
* Removes all non-word chars (\W) from the text
*
* @param fileName
* @param defaultName
* @return a string larger then 0 with either word chars remaining from the
* input or the default provided
*/
public static String cleanFilename(String fileName, String defaultName)
{
if (fileName == null || "".equals(fileName))
{
fileName = defaultName;
}
else
{
fileName = fileName.replaceAll("\\W", "");
fileName = (fileName.length() > 0) ? fileName : defaultName;
}
return fileName;
}
/**
* Includes media into the export directory and returns the relative path of
* the media
*
* @param inputFilePath
* @return file path relative to the export dir
* @throws IOException
*/
protected String includeMediaFile(String inputFilePath) throws IOException
{
mNeedsBundling = true;
File source = new File(inputFilePath);
File target = new File(mExportDirectoryPath + "/" + source.getName());
// Log.d( TAG, String.format( "Copy %s to %s", source, target ) );
if (source.exists())
{
FileChannel inChannel = new FileInputStream(source).getChannel();
FileChannel outChannel = new FileOutputStream(target).getChannel();
try
{
inChannel.transferTo(0, inChannel.size(), outChannel);
}
finally
{
if (inChannel != null) inChannel.close();
if (outChannel != null) outChannel.close();
}
}
else
{
Log.w( TAG, "Failed to add file to new XML export. Missing: "+inputFilePath );
}
mXmlCreatorProgressAdmin.addMediaProgress();
return target.getName();
}
/**
* Just to start failing early
*
* @throws IOException
*/
protected void verifySdCardAvailibility() throws IOException
{
String state = Environment.getExternalStorageState();
if (!Environment.MEDIA_MOUNTED.equals(state))
{
throw new IOException("The ExternalStorage is not mounted, unable to export files for sharing.");
}
}
/**
* Create a zip of the export directory based on the given filename
*
* @param fileName The directory to be replaced by a zipped file of the same
* name
* @param extension
* @return full path of the build zip file
* @throws IOException
*/
protected String bundlingMediaAndXml(String fileName, String extension) throws IOException
{
String zipFilePath;
if (fileName.endsWith(".zip") || fileName.endsWith(extension))
{
zipFilePath = Constants.getSdCardDirectory(mContext) + fileName;
}
else
{
zipFilePath = Constants.getSdCardDirectory(mContext) + fileName + extension;
}
String[] filenames = new File(mExportDirectoryPath).list();
byte[] buf = new byte[1024];
ZipOutputStream zos = null;
try
{
zos = new ZipOutputStream(new FileOutputStream(zipFilePath));
for (int i = 0; i < filenames.length; i++)
{
String entryFilePath = mExportDirectoryPath + "/" + filenames[i];
FileInputStream in = new FileInputStream(entryFilePath);
zos.putNextEntry(new ZipEntry(filenames[i]));
int len;
while ((len = in.read(buf)) >= 0)
{
zos.write(buf, 0, len);
}
zos.closeEntry();
in.close();
mXmlCreatorProgressAdmin.addCompressProgress();
}
}
finally
{
if (zos != null)
{
zos.close();
}
}
deleteRecursive(new File(mExportDirectoryPath));
return zipFilePath;
}
public static boolean deleteRecursive(File file)
{
if (file.isDirectory())
{
String[] children = file.list();
for (int i = 0; i < children.length; i++)
{
boolean success = deleteRecursive(new File(file, children[i]));
if (!success)
{
return false;
}
}
}
return file.delete();
}
public void setExportDirectoryPath(String exportDirectoryPath)
{
this.mExportDirectoryPath = exportDirectoryPath;
}
public String getExportDirectoryPath()
{
return mExportDirectoryPath;
}
public void quickTag(XmlSerializer serializer, String ns, String tag, String content) throws IllegalArgumentException, IllegalStateException, IOException
{
if( tag == null)
{
tag = "";
}
if( content == null)
{
content = "";
}
serializer.text("\n");
serializer.startTag(ns, tag);
serializer.text(content);
serializer.endTag(ns, tag);
}
public boolean needsBundling()
{
return mNeedsBundling;
}
public static String convertStreamToString(InputStream is) throws IOException
{
String result = "";
/*
* To convert the InputStream to String we use the Reader.read(char[]
* buffer) method. We iterate until the Reader return -1 which means
* there's no more data to read. We use the StringWriter class to produce
* the string.
*/
if (is != null)
{
Writer writer = new StringWriter();
char[] buffer = new char[8192];
try
{
Reader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
int n;
while ((n = reader.read(buffer)) != -1)
{
writer.write(buffer, 0, n);
}
}
finally
{
is.close();
}
result = writer.toString();
}
return result;
}
public static InputStream convertStreamToLoggedStream(String tag, InputStream is) throws IOException
{
String result = "";
/*
* To convert the InputStream to String we use the Reader.read(char[]
* buffer) method. We iterate until the Reader return -1 which means
* there's no more data to read. We use the StringWriter class to produce
* the string.
*/
if (is != null)
{
Writer writer = new StringWriter();
char[] buffer = new char[8192];
try
{
Reader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
int n;
while ((n = reader.read(buffer)) != -1)
{
writer.write(buffer, 0, n);
}
}
finally
{
is.close();
}
result = writer.toString();
}
InputStream in = new ByteArrayInputStream(result.getBytes("UTF-8"));
return in;
}
protected abstract String getContentType();
protected void handleError(String task, Exception e, String text)
{
Log.e(TAG, "Unable to save ", e);
mTask = task;
mException = e;
mErrorText = text;
cancel(false);
throw new CancellationException(text);
}
@Override
protected void onPreExecute()
{
if(mProgressListener!= null)
{
mProgressListener.started();
}
}
@Override
protected void onProgressUpdate(Integer... progress)
{
if(mProgressListener!= null)
{
mProgressListener.setProgress(mXmlCreatorProgressAdmin.getProgress());
}
}
@Override
protected void onPostExecute(Uri resultFilename)
{
if(mProgressListener!= null)
{
mProgressListener.finished(resultFilename);
}
}
@Override
protected void onCancelled()
{
if(mProgressListener!= null)
{
mProgressListener.finished(null);
mProgressListener.showError(mTask, mErrorText, mException);
}
}
public class XmlCreatorProgressAdmin extends ProgressAdmin
{
private boolean compressCount;
private boolean compressProgress;
private boolean uploadCount;
private boolean uploadProgress;
private int mediaCount;
private int mediaProgress;
private int waypointCount;
private int waypointProgress;
private long photoUploadCount ;
private long photoUploadProgress ;
public void addMediaProgress()
{
mediaProgress ++;
}
public void addCompressProgress()
{
compressProgress = true;
}
public void addUploadProgress()
{
uploadProgress = true;
}
public void addPhotoUploadProgress(long length)
{
photoUploadProgress += length;
}
/**
* Get the progress on scale 0 ... Window.PROGRESS_END
*
* @return Returns the progress as a int.
*/
@Override
public int getProgress()
{
int blocks = 0;
if( waypointCount > 0 ){ blocks++; }
if( mediaCount > 0 ){ blocks++; }
if( compressCount ){ blocks++; }
if( uploadCount ){ blocks++; }
if( photoUploadCount > 0 ){ blocks++; }
int progress;
if( blocks > 0 )
{
int blockSize = Window.PROGRESS_END / blocks;
progress = waypointCount > 0 ? blockSize * waypointProgress / waypointCount : 0;
progress += mediaCount > 0 ? blockSize * mediaProgress / mediaCount : 0;
progress += compressProgress ? blockSize : 0;
progress += uploadProgress ? blockSize : 0;
progress += photoUploadCount > 0 ? blockSize * photoUploadProgress / photoUploadCount : 0;
}
else
{
progress = 0;
}
//Log.d( TAG, "Progress updated to "+progress);
return progress;
}
public void setWaypointCount(int waypoint)
{
waypointCount = waypoint;
considerPublishProgress();
}
public void setMediaCount(int media)
{
mediaCount = media;
considerPublishProgress();
}
public void setCompress( boolean compress)
{
compressCount = compress;
considerPublishProgress();
}
public void setUpload( boolean upload)
{
uploadCount = upload;
considerPublishProgress();
}
public void setPhotoUpload(long length)
{
photoUploadCount += length;
considerPublishProgress();
}
public void addWaypointProgress(int i)
{
waypointProgress += i;
considerPublishProgress();
}
@Override
public void considerPublishProgress()
{
if(mustPublishProgress())
{
publishProgress();
}
}
}
}