package it.geosolutions.geocollect.android.core.mission.utils; import it.geosolutions.android.map.wfs.geojson.GeoJson; import it.geosolutions.geocollect.android.app.BuildConfig; import it.geosolutions.geocollect.android.app.R; import it.geosolutions.geocollect.android.core.Config; import it.geosolutions.geocollect.android.core.form.utils.FormUtils; import it.geosolutions.geocollect.android.core.login.LoginActivity; import it.geosolutions.geocollect.android.core.login.utils.LoginRequestInterceptor; import it.geosolutions.geocollect.android.core.mission.Mission; import it.geosolutions.geocollect.android.core.mission.MissionFeature; import it.geosolutions.geocollect.model.config.MissionTemplate; import it.geosolutions.geocollect.model.http.CommitResponse; import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import jsqlite.Database; import org.apache.http.HttpHeaders; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.entity.mime.MultipartEntity; import org.apache.http.entity.mime.content.ContentBody; import org.apache.http.entity.mime.content.FileBody; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.AbstractHttpMessage; import org.apache.http.message.BasicHeader; import org.apache.http.params.HttpConnectionParams; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.HTTP; import org.apache.http.protocol.HttpContext; import org.apache.http.util.EntityUtils; import android.content.Context; import android.content.SharedPreferences; import android.os.AsyncTask; import android.preference.PreferenceManager; import android.util.Log; import com.google.gson.Gson; import eu.geopaparazzi.library.util.ResourcesManager; /** * thread that send data to the server * separated from the UI * * @author Robert Oehler * */ public abstract class UploadTask extends AsyncTask<Void, Integer, CommitResponse> { /** * Tag for logging */ public static String TAG = "UploadTask"; private Context context; private String mediaUrl; private String missionID; private boolean deleteFromDisk; private MissionTemplate missionTemplate; private List<MissionFeature> featuresList; private String authorizationString; public UploadTask( Context pContext, MissionTemplate mMissionTemplate, List<MissionFeature> mFeaturesList ){ this.context = pContext; this.deleteFromDisk = true; this.missionTemplate = mMissionTemplate; this.featuresList = mFeaturesList; if ( missionTemplate !=null && missionTemplate.schema_seg != null){ this.missionID = missionTemplate.schema_seg.localSourceStore; } this.mediaUrl = Config.MAIN_SERVER_BASE_URL+Config.OPENSDI_PATH+Config.UPLOAD_MEDIA_PATH; } public abstract void dataDone(); public abstract void hideMedia(); public abstract void done(CommitResponse response); @Override protected CommitResponse doInBackground(Void... params) { CommitResponse result = null ; int defaultImageSize = Config.DEFAULT_MAX_PHOTO_SIZE; try { if ( missionTemplate != null && missionTemplate.config != null && missionTemplate.config.containsKey("maxImageSize") ){ defaultImageSize = Integer.parseInt((String) missionTemplate.config.get("maxImageSize")); } } catch (NumberFormatException e) { if(BuildConfig.DEBUG){ Log.e(TAG, e.getLocalizedMessage(), e); } } catch (NullPointerException e) { if(BuildConfig.DEBUG){ Log.e(TAG, e.getLocalizedMessage(), e); } } //it these are null there is no upload necessary, break if ( this.missionTemplate == null || this.featuresList == null){ result = new CommitResponse(); result.setMessage("no valid arguments provided"); result.setStatus(it.geosolutions.geocollect.model.http.Status.ERROR); return result; } //This upload task handles MissionFeature objects and do the JSON conversion only before upload //////////////////////////////////////////////////////////////////////////////////////////////////////////// GeoJson geogson = new GeoJson(); Gson gson = new Gson(); final int uploadAmount = featuresList.size(); for(MissionFeature featureToUpload : featuresList){ // Validate if(featureToUpload.typeName == null){ if(BuildConfig.DEBUG){ Log.e(TAG, "Cannot upload feature: "+featureToUpload.id); } continue; } String tableName = featureToUpload.typeName.endsWith(MissionTemplate.NEW_NOTICE_SUFFIX) ? missionTemplate.schema_seg.localSourceStore + MissionTemplate.NEW_NOTICE_SUFFIX : missionTemplate.schema_sop.localFormStore; hideMedia(); try { //1.send data // Convert to JSON // Align Feature properties if (featureToUpload.typeName.endsWith(MissionTemplate.NEW_NOTICE_SUFFIX)) {// new entry // Edit the MissionFeature for a better JSON compliance MissionUtils.alignPropertiesTypes(featureToUpload, missionTemplate.schema_seg.fields); } String featureIDString = MissionUtils.getFeatureGCID(featureToUpload); // Set the "MY_ORIG_ID" to link this feature to its photos if (featureToUpload.properties == null) { featureToUpload.properties = new HashMap<String, Object>(); } // Look for a "my_orig_id" property and populate it boolean hasMyOrigID = false; for(String inputKey: featureToUpload.properties.keySet()){ if(inputKey.equalsIgnoreCase(Mission.MY_ORIG_ID_STRING)){ featureToUpload.properties.put(inputKey, featureIDString); hasMyOrigID = true; } } // If not found, add it if(!hasMyOrigID){ featureToUpload.properties.put(Mission.MY_ORIG_ID_STRING, featureIDString); } MissionFeature toUpload; if (featureToUpload.typeName.endsWith(MissionTemplate.NEW_NOTICE_SUFFIX)) { toUpload = MissionUtils.alignMissionFeatureProperties(featureToUpload, missionTemplate.schema_seg.fields); // Auto set GCID toUpload.properties.put(MissionUtils.GCID_STRING, featureIDString); toUpload.properties.put(MissionUtils.GCID_STRING.toLowerCase(), featureIDString); } else { toUpload = MissionUtils.alignMissionFeatureProperties(featureToUpload, missionTemplate.schema_sop.fields); } String c = geogson.toJson(toUpload); String data = null; // Encode try { data = new String(c.getBytes("UTF-8")); } catch (UnsupportedEncodingException e) { if(BuildConfig.DEBUG){ Log.e(TAG, "error transforming missionfeature to gson", e); } } // send String resultString = sendJson( featureToUpload.typeName.endsWith(MissionTemplate.NEW_NOTICE_SUFFIX) ? missionTemplate.seg_form.url : missionTemplate.sop_form.url , data); // Get result result = getCommitResponse(gson, resultString); if(result == null){ //most likely network error result = new CommitResponse(); result.setMessage("network error transfering data"); result.setStatus(it.geosolutions.geocollect.model.http.Status.ERROR); return result; } if(result.isSuccess()){ //data upload successful //2. send media //UI update -> media upload dataDone(); // Resize images FormUtils.resizeImagesToMax(context, featureIDString, defaultImageSize); final String[] photoUrls = FormUtils.getPhotoUriStrings(context, featureIDString); // Send images CommitResponse photosSent = sendPhotos(photoUrls, result.getId(), featureIDString, uploadAmount); if(photosSent.isSuccess()){ //both successful, can delete this entry //1.delete table entry if desired if(this.deleteFromDisk){ final Database db = getSpatialiteDatabase(); PersistenceUtils.deleteMissionFeature( db , tableName , featureIDString); try { db.close(); } catch (jsqlite.Exception e) { // ignore } } //2.delete photos if(photoUrls != null){ for(String file : photoUrls){ File f = new File(file); if(f.exists()){ f.delete(); } } } //3.delete this entry as "uploadable" HashMap<String,ArrayList<String>> uploadables = PersistenceUtils.loadUploadables(this.context); ArrayList<String> uploadList = uploadables.get(tableName); if(uploadList != null){ uploadList.remove(featureIDString); } uploadables.put(tableName, uploadList); PersistenceUtils.saveUploadables(this.context, uploadables); }else{ //TODO data sent, photos failed, what to delete ? } }else{ // data upload failed //TODO report this or continue with others ? } } catch (Exception e) { Log.e("SendData", "ErrorSending Data",e); } } //////////////////////////////////////////////////////////////////////////////////////////////////////////// done(result); return result; } private CommitResponse sendPhotos(String[] filePaths, String dataSentID, String featureID, final int uploadAmount){ CommitResponse cr =null; //the output id int i = 0; //no media to send create a dummy commit response to return success if( filePaths == null || filePaths.length == 0 ){ cr = new CommitResponse(); cr.setStatus(it.geosolutions.geocollect.model.http.Status.SUCCESS); cr.setMessage(this.context.getString(R.string.no_media_to_send)); return cr; } //<MEDIA_URL>/<MISSIONID>/ORIGINID/outID/upload String mediaUrl = this.mediaUrl + "/" + this.missionID + "/" + featureID + "/" + dataSentID + "/upload"; Gson gson = new Gson(); for(String file : filePaths){ String res; try { File f = new File(new URI(file)) ; if(!f.exists()){ return cr; } res = sendMedia(f,mediaUrl ); } catch (URISyntaxException e) { Log.e("SendMedia","error sending media, unable read URI:" + file,e); return cr; } publishProgress((int) (i / (float) uploadAmount)); cr = getCommitResponse(gson, res); if(cr==null || !cr.isSuccess()){ return cr; } } return cr; } private String sendMedia(File file,String mediaUrl){ HttpClient httpClient = new DefaultHttpClient(); HttpContext localContext = new BasicHttpContext(); try { mediaUrl +="?name=" + file.getName(); URI url = new URI(mediaUrl); HttpPost httpPost = new HttpPost(url); Log.i("SendMedia","Sending data to:"+ mediaUrl); // MultipartEntity multiEntity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE); ContentBody cBody = new FileBody(file); MultipartEntity multipartContent = new MultipartEntity(); multipartContent.addPart("file", cBody); httpPost.setEntity(multipartContent); // If authentication info exists, add it to the request addAuthHeaders(httpPost); HttpResponse response = httpClient.execute(httpPost, localContext); return EntityUtils.toString(response.getEntity()); } catch (ClientProtocolException e) { Log.e("SendMedia","error sending media:",e); } catch (IOException e) { Log.e("SendMedia","error sending media:",e); } catch (URISyntaxException e) { Log.e("SendMedia", "error parsing media url",e); e.printStackTrace(); } return null; } protected String sendJson(final String url, final String json) throws ClientProtocolException, IOException { HttpClient client = new DefaultHttpClient(); HttpConnectionParams.setConnectionTimeout(client.getParams(), 10000); //Timeout Limit HttpResponse response; HttpPost post = new HttpPost(url); StringEntity se = new StringEntity( json.toString(), "UTF-8"); se.setContentType(new BasicHeader(HTTP.CONTENT_TYPE, "application/json")); se.setContentEncoding(new BasicHeader(HTTP.CHARSET_PARAM, "UTF-8")); post.setEntity(se); // If authentication info exists, add it to the request addAuthHeaders(post); response = client.execute(post); /*Checking response */ if(response!=null){ return EntityUtils.toString(response.getEntity()); } return null; } /** * Parse the result string to get a commit response * @param resultString * @return */ public static CommitResponse getCommitResponse(Gson gson, String resultString) { CommitResponse cr = null; try{ cr= gson.fromJson(resultString, CommitResponse.class); }catch(Exception e){ Log.e("SendData","Error parsing commit response:"+ resultString,e); } return cr; } public Database getSpatialiteDatabase(){ Database spatialiteDatabase = null; try { File sdcardDir = ResourcesManager.getInstance(this.context).getSdcardDir(); File spatialDbFile = new File(sdcardDir, "geocollect/genova.sqlite"); if (!spatialDbFile.getParentFile().exists()) { throw new RuntimeException(); } spatialiteDatabase = new jsqlite.Database(); spatialiteDatabase.open(spatialDbFile.getAbsolutePath(), jsqlite.Constants.SQLITE_OPEN_READWRITE | jsqlite.Constants.SQLITE_OPEN_CREATE); Log.v("MISSION_DETAIL", SpatialiteUtils.queryVersions(spatialiteDatabase)); Log.v("MISSION_DETAIL", spatialiteDatabase.dbversion()); return spatialiteDatabase; } catch (Exception e) { Log.v("MISSION_DETAIL", Log.getStackTraceString(e)); return null; } } /** * Checks preferences for login informations. * If found, add the Authorization header to the input request * @param request */ protected void addAuthHeaders(AbstractHttpMessage request){ // Get the Authorization from the Preferences if(authorizationString == null){ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); String email = prefs.getString(LoginActivity.PREFS_USER_EMAIL, null); String pass = prefs.getString(LoginActivity.PREFS_PASSWORD, null); if(email != null && pass != null){ authorizationString = LoginRequestInterceptor.getB64Auth(email, pass); } } // If there is an authorizationString, add the header if(authorizationString != null){ request.addHeader(new BasicHeader(HttpHeaders.AUTHORIZATION, authorizationString)); } } }