/*
* Kontalk Android client
* Copyright (C) 2017 Kontalk Devteam <devteam@kontalk.org>
* This program 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.
* 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kontalk.service;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Properties;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v4.content.LocalBroadcastManager;
import org.kontalk.Log;
import org.kontalk.R;
import org.kontalk.client.EndpointServer;
import org.kontalk.client.ServerList;
import org.kontalk.reporting.ReportingManager;
import org.kontalk.service.msgcenter.MessageCenterService;
import org.kontalk.util.Preferences;
import org.kontalk.util.SystemUtils;
/**
* Worker for downloading and caching locally a server list.
* This class doesn't need to be configured: it hides all the logic of picking
* a random server, connecting to it, downloading the server list and saving it
* in the application cache. Finally, it restarts the message center.
* @author Daniele Ricci
*/
public class ServerListUpdater extends BroadcastReceiver {
private static final String TAG = ServerListUpdater.class.getSimpleName();
private static ServerList sCurrentList;
private static DateFormat sTimestampFormat =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", Locale.US);
private final Context mContext;
private final LocalBroadcastManager mLocalBroadcastManager;
private UpdaterListener mListener;
public ServerListUpdater(Context context) {
mContext = context;
mLocalBroadcastManager = LocalBroadcastManager.getInstance(context);
}
public void setListener(UpdaterListener listener) {
mListener = listener;
}
public void start() {
/*
* We have a server list - either builtin or cached. Now pick a random
* server from the list and contact it for the latest server list.
*/
EndpointServer random = Preferences.getEndpointServer(mContext);
/* no server found -- notify to user */
if (random == null) {
Log.i(TAG, "no list to pick a random server from - aborting");
// notify to UI
if (mListener != null)
mListener.noData();
return;
}
// check for network
if (!SystemUtils.isNetworkConnectionAvailable(mContext)) {
if (mListener != null)
mListener.networkNotAvailable();
return;
}
// check for offline mode
if (Preferences.getOfflineMode()) {
if (mListener != null)
mListener.offlineModeEnabled();
return;
}
// register for and request connection status
IntentFilter f = new IntentFilter();
f.addAction(MessageCenterService.ACTION_CONNECTED);
f.addAction(MessageCenterService.ACTION_SERVERLIST);
mLocalBroadcastManager.registerReceiver(this, f);
MessageCenterService.requestConnectionStatus(mContext);
MessageCenterService.start(mContext);
}
public void cancel() {
unregisterReceiver();
}
private void unregisterReceiver() {
mLocalBroadcastManager.unregisterReceiver(this);
}
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (MessageCenterService.ACTION_CONNECTED.equals(action)) {
// request serverlist
MessageCenterService.requestServerList(mContext);
}
else if (MessageCenterService.ACTION_SERVERLIST.equals(action)) {
// we don't need this any more
unregisterReceiver();
String[] items = intent
.getStringArrayExtra(MessageCenterService.EXTRA_JIDLIST);
if (items != null && items.length > 0) {
String network = intent.getStringExtra(MessageCenterService.EXTRA_FROM);
Properties prop = new Properties();
Date now = new Date();
ServerList list = new ServerList(now);
prop.setProperty("timestamp", sTimestampFormat.format(now));
for (int i = 0; i < items.length; i++) {
String item = network + '|' + items[i];
prop.setProperty("server" + (i + 1), item);
list.add(new EndpointServer(item));
}
OutputStream out = null;
try {
out = new FileOutputStream(getCachedListFile(mContext));
prop.store(out, null);
out.close();
// update cached list
sCurrentList = list;
if (mListener != null)
mListener.updated(list);
}
catch (IOException e) {
if (mListener != null)
mListener.error(e);
}
finally {
try {
out.close();
}
catch (Exception e) {
// ignored
}
}
}
else {
if (mListener != null)
mListener.error(null);
}
}
}
public static void deleteCachedList(Context context) {
File file = getCachedListFile(context);
file.delete();
}
/** The path to the locally cached downloaded server list. */
private static File getCachedListFile(Context context) {
return new File(context.getCacheDir(), "serverlist.properties");
}
private static ServerList parseList(InputStream in) throws IOException {
Properties prop = new Properties();
prop.load(in);
try {
Date date = sTimestampFormat.parse(prop.getProperty("timestamp"));
ServerList list = new ServerList(date);
int i = 1;
String server;
while ((server = prop.getProperty("server" + i)) != null) {
list.add(new EndpointServer(server));
i++;
}
return list;
}
catch (Exception e) {
throw new IOException("parse error", e);
}
}
private static ServerList parseBuiltinList(Context context) throws IOException {
InputStream in = null;
try {
in = context.getResources()
.openRawResource(R.raw.serverlist);
return parseList(in);
}
finally {
try {
in.close();
}
catch (Exception e) {
// ignored
}
}
}
private static ServerList parseCachedList(Context context) throws IOException {
InputStream in = null;
try {
in = new FileInputStream(getCachedListFile(context));
return parseList(in);
}
finally {
try {
in.close();
}
catch (Exception e) {
// ignored
}
}
}
/** Returns (and loads if necessary) the current server list. */
public static ServerList getCurrentList(Context context) {
if (sCurrentList != null)
return sCurrentList;
ServerList builtin = null;
try {
builtin = parseBuiltinList(context);
sCurrentList = parseCachedList(context);
// use cached list if recent than
if (builtin.getDate().after(sCurrentList.getDate()))
sCurrentList = builtin;
}
catch (IOException e) {
if (builtin == null) {
ReportingManager.logException(e);
Log.w(TAG, "unable to load builtin server list", e);
}
sCurrentList = builtin;
}
return sCurrentList;
}
public interface UpdaterListener {
/** Called if either the cached list or the built-in list cannot be loaded.*/
void noData();
/** Called when network is not available. */
void networkNotAvailable();
/** Called when offline mode is active. */
void offlineModeEnabled();
/** Called if an error occurs during update. */
void error(Throwable e);
/** Called when list update has finished. */
void updated(ServerList list);
}
}