/*
* Artcodes recognises a different marker scheme that allows the
* creation of aesthetically pleasing, even beautiful, codes.
* Copyright (C) 2013-2016 The University of Nottingham
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package uk.ac.horizon.artcodes.server;
import android.content.Context;
import android.content.SharedPreferences;
import android.location.Location;
import android.net.Uri;
import android.util.Log;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import okhttp3.FormBody;
import okhttp3.Request;
import okhttp3.RequestBody;
import uk.ac.horizon.artcodes.Artcodes;
import uk.ac.horizon.artcodes.ExperienceParser;
import uk.ac.horizon.artcodes.Feature;
import uk.ac.horizon.artcodes.GoogleAnalytics;
import uk.ac.horizon.artcodes.R;
import uk.ac.horizon.artcodes.account.Account;
import uk.ac.horizon.artcodes.account.AppEngineAccount;
import uk.ac.horizon.artcodes.account.LocalAccount;
import uk.ac.horizon.artcodes.model.Action;
import uk.ac.horizon.artcodes.model.Experience;
import uk.ac.horizon.artcodes.model.ScanEvent;
public class AppEngineServer implements ArtcodeServer
{
private static final String starred_tag = "starred";
private static final String recent_tag = "recent";
private static final String accounts_tag = "accounts";
private static final String prefix = "google:";
private final Context context;
private final Gson gson;
private final LocalAccount localAccount;
private final List<Account> accounts = new ArrayList<>();
public AppEngineServer(Context context)
{
this.context = context;
this.gson = ExperienceParser.createGson(context);
final List<String> accountIDs = loadIDs(Account.class, accounts_tag);
localAccount = new LocalAccount(context, gson);
if (!accountIDs.contains("local"))
{
if (Feature.get(context, R.bool.feature_show_local).isEnabled())
{
accountIDs.add("local");
saveIDs(Account.class, accounts_tag, accountIDs);
}
}
for (String accountID : accountIDs)
{
Account account = createAccount(accountID);
if (account != null)
{
accounts.add(account);
}
}
}
public Account createAccount(String id)
{
if (id.startsWith(prefix))
{
String name = id.substring(prefix.length());
return new AppEngineAccount(context, name, gson);
}
else if (id.equals("local"))
{
if (Feature.get(context, R.bool.feature_show_local).isEnabled())
{
return localAccount;
}
}
return null;
}
@Override
public void add(Account account)
{
if (getAccount(account.getId()) != null)
{
return;
}
accounts.add(Math.max(accounts.size() - 1, 0), account);
List<String> accountIDs = new ArrayList<>();
for (Account accountItem : accounts)
{
accountIDs.add(accountItem.getId());
}
saveIDs(Account.class, accounts_tag, accountIDs);
}
public void saveRecent(List<String> ids)
{
saveIDs(Experience.class, recent_tag, ids);
}
public void saveStarred(List<String> ids)
{
saveIDs(Experience.class, starred_tag, ids);
}
@Override
public Account getAccount(String id)
{
for (Account account : accounts)
{
if (account.getId().equals(id))
{
return account;
}
}
return null;
}
@Override
public List<Account> getAccounts()
{
return accounts;
}
@Override
public void loadExperience(final String id, final LoadCallback<Experience> callback)
{
load(id, new JsonCallback<>(Experience.class, gson, context, callback));
}
@Override
public void loadRecent(LoadCallback<List<String>> callback)
{
callback.loaded(loadIDs(Experience.class, recent_tag));
}
@Override
public void loadRecommended(final LoadCallback<Map<String, List<String>>> callback, Location location)
{
String url = "http://aestheticodes.appspot.com/recommended";
if (location != null)
{
url = url + "?lat=" + location.getLatitude() + "&lon=" + location.getLongitude();
}
load(url, new JsonCallback<>(new TypeToken<Map<String, List<String>>>()
{
}.getType(), gson, context, callback));
}
@Override
public void loadStarred(LoadCallback<List<String>> callback)
{
callback.loaded(loadIDs(Experience.class, starred_tag));
}
@Override
public void logScan(final String uri, final Action action)
{
final SharedPreferences preferences = context.getSharedPreferences("History", Context.MODE_PRIVATE);
final String historyJSON = preferences.getString(uri, "[]");
final List<ScanEvent> history = gson.fromJson(historyJSON, new TypeToken<List<ScanEvent>>()
{
}.getType());
// Only log scan event if an identical scan hasn't happened recently
if (!history.isEmpty())
{
final ScanEvent lastEvent = history.get(history.size() - 1);
if (System.currentTimeMillis() - lastEvent.getTimestamp() < 1000)
{
String lastAction = gson.toJson(lastEvent.getAction());
String currentAction = gson.toJson(action);
if (currentAction.equals(lastAction))
{
return;
}
}
}
history.add(new ScanEvent(action));
preferences.edit().putString(uri, gson.toJson(history)).apply();
if (uri != null && (uri.startsWith("http:") || (uri.startsWith("https:"))))
{
new Thread(new Runnable()
{
@Override
public void run()
{
try
{
final RequestBody body = new FormBody.Builder()
.add("experience", uri)
.build();
final Request request = new Request.Builder()
.post(body)
.url("https://aestheticodes.appspot.com/interaction")
.header("Authorization", context.getString(R.string.oauth_client_id))
.header("User-Agent", Artcodes.userAgent)
.build();
Artcodes.httpClient.newCall(request).execute();
}
catch (Exception e)
{
GoogleAnalytics.trackException(e);
}
}
}).start();
}
}
@Override
public List<ScanEvent> getScanHistory(String id)
{
final SharedPreferences preferences = context.getSharedPreferences("History", Context.MODE_PRIVATE);
final String historyJSON = preferences.getString(id, "[]");
return gson.fromJson(historyJSON, new TypeToken<List<ScanEvent>>()
{
}.getType());
}
@Override
public void search(String query, LoadCallback<List<String>> callback)
{
String url = Uri.parse("https://aestheticodes.appspot.com/search")
.buildUpon()
.appendQueryParameter("q", query)
.build().toString();
load(url, new JsonCallback<>(new TypeToken<List<String>>()
{
}.getType(), gson, context, callback));
}
private void load(String uri, URILoaderCallback callback)
{
SharedPreferences preferences = context.getSharedPreferences(Account.class.getName(), Context.MODE_PRIVATE);
String accountID = preferences.getString(uri, null);
if (accountID != null)
{
Account account = getAccount(accountID);
if (account != null)
{
if (account.load(uri, callback))
{
return;
}
}
}
for (Account account : getAccounts())
{
if (account.load(uri, callback))
{
return;
}
}
localAccount.load(uri, callback);
}
private List<String> loadIDs(Class<?> clazz, String name)
{
SharedPreferences preferences = context.getSharedPreferences(clazz.getName(), Context.MODE_PRIVATE);
String jsonPreferences = preferences.getString(name, "[]");
Log.i("ids", name + " = " + jsonPreferences);
return gson.fromJson(jsonPreferences, new TypeToken<List<String>>()
{
}.getType());
}
private void saveIDs(Class<?> clazz, String name, List<String> ids)
{
SharedPreferences preferences = context.getSharedPreferences(clazz.getName(), Context.MODE_PRIVATE);
String jsonPreferences = gson.toJson(ids);
preferences.edit().putString(name, jsonPreferences).apply();
}
}