/* * Copyright (C) 2011 Alex Kuiper * * This file is part of PageTurner * * PageTurner 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. * * PageTurner 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 PageTurner. If not, see <http://www.gnu.org/licenses/>.* */ package net.nightwhistler.pageturner.sync; import android.content.Context; import com.google.inject.Inject; import jedi.option.Option; import net.nightwhistler.pageturner.Configuration; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.HttpContext; import org.apache.http.util.EntityUtils; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import roboguice.inject.ContextSingleton; import java.io.IOException; import java.net.URLEncoder; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import static jedi.option.Options.none; import static jedi.option.Options.some; @ContextSingleton public class PageTurnerWebProgressService implements ProgressService { private static final Logger LOG = LoggerFactory.getLogger(PageTurnerWebProgressService.class); private Configuration config; private HttpClient client; private HttpContext httpContext; private static final int HTTP_SUCCESS = 200; private static final int HTTP_FORBIDDEN = 403; private static final int HTTP_NOT_FOUND = 404; private SimpleDateFormat dateFormat; @Inject public PageTurnerWebProgressService(Context context, Configuration config, HttpClient client) { this.httpContext = new BasicHttpContext(); this.config = config; this.client = client; this.dateFormat = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); // explicitly set timezone of input if needed dateFormat.setTimeZone(java.util.TimeZone.getTimeZone("Zulu")); } @Override public Option<List<BookProgress>> getProgress(String fileName) throws AccessException { String userId = this.config.getSynchronizationEmail(); String accessKey = this.config.getSynchronizationAccessKey(); if ( "".equals( userId ) || "".equals(fileName) ) { LOG.debug( "Empty username or filename. Aborting sync. (" + userId + " / " + fileName + ")" ); return none(); } String key = computeKey(fileName); LOG.debug( "Doing progress query for key: " + key ); HttpGet get = new HttpGet( getSyncServerURL() + key + "?accessKey=" + URLEncoder.encode(accessKey) ); get.setHeader("User-Agent", config.getUserAgent() ); try { HttpResponse response = client.execute(get); int statusCode = response.getStatusLine().getStatusCode(); LOG.debug( "Got status " + statusCode + " from server."); if ( statusCode == HTTP_FORBIDDEN ) { throw new AccessException( EntityUtils.toString(response.getEntity()) ); } if ( statusCode == HTTP_NOT_FOUND ) { return some(new ArrayList<>()); } if ( statusCode != HTTP_SUCCESS ) { return none(); } String responseString = EntityUtils.toString(response.getEntity()); JSONArray jsonArray = new JSONArray(responseString); List<BookProgress> result = new ArrayList<>(); for ( int i=0; i < jsonArray.length(); i++ ) { JSONObject json = jsonArray.getJSONObject(i); int index = json.getInt("bookIndex"); int progress = json.getInt("progress"); int percentage = json.getInt("percentage"); Date timeStamp = dateFormat.parse( json.getString("storedOn") ); String deviceName = json.getString("deviceName"); result.add( new BookProgress(fileName, index, progress, percentage, timeStamp, deviceName ) ); } return some(result); } catch (IOException e) { LOG.error( "Got error while querying server", e ); } catch (JSONException json ) { LOG.error( "Error reading response", json ); } catch (ParseException p ) { LOG.error( "Invalid date", p ); } catch ( IllegalStateException s ) { LOG.error( "Tried query in illegal state", s ); } return none(); } @Override public void storeProgress(String fileName, int index, int progress, int percentage) { if ( ! config.isSyncEnabled() ) { return; } String key = computeKey(fileName); HttpPost post = new HttpPost( getSyncServerURL() + key ); String filePart = fileName; if ( fileName.indexOf("/") != -1 ) { filePart = fileName.substring( fileName.lastIndexOf('/') ); } try { List<NameValuePair> pairs = new ArrayList<NameValuePair>(); pairs.add( new BasicNameValuePair("bookIndex", "" + index ) ); pairs.add(new BasicNameValuePair("progress", "" + progress )); pairs.add(new BasicNameValuePair("title", Integer.toHexString( filePart.hashCode() ))); pairs.add(new BasicNameValuePair("deviceName", this.config.getDeviceName() )); pairs.add(new BasicNameValuePair("percentage", "" + percentage )); pairs.add(new BasicNameValuePair("userId", Integer.toHexString( this.config.getSynchronizationEmail().hashCode() ))); pairs.add(new BasicNameValuePair("accessKey", this.config.getSynchronizationAccessKey())); post.setEntity( new UrlEncodedFormEntity(pairs) ); post.setHeader("User-Agent", config.getUserAgent() ); HttpResponse response = client.execute(post, this.httpContext); if ( response.getStatusLine().getStatusCode() == HTTP_FORBIDDEN ) { throw new AccessException( EntityUtils.toString(response.getEntity()) ); } LOG.debug("Got status " + response.getStatusLine().getStatusCode() + " from server."); } catch (Exception io) { LOG.error("Got error while POSTing update:", io); //fail silently } } private String getSyncServerURL() { String url = config.getSyncServerURL(); if ( ! url.endsWith( "/" ) ) { url = url + "/"; } return url; } private String computeKey( String fileName ) { String filePart = fileName; if ( fileName.indexOf("/") != -1 ) { filePart = fileName.substring( fileName.lastIndexOf('/') ); } String plainTextKey = this.config.getSynchronizationEmail() + ":" + filePart; String hash = Integer.toHexString( plainTextKey.hashCode() ); return hash; } }