0
0
mirror of https://github.com/schwabe/ics-openvpn.git synced 2024-09-20 12:02:28 +02:00

Implement persistent disk log cache. (closes #340)

This commit is contained in:
Arne Schwabe 2016-01-24 14:30:23 +00:00
parent 8414c4f167
commit 0e6e77c476
5 changed files with 215 additions and 41 deletions

View File

@ -37,5 +37,7 @@ public class ICSOpenVPNApplication extends Application {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
//ACRA.init(this); //ACRA.init(this);
} }
VpnStatus.initLogCache(getApplicationContext().getCacheDir());
} }
} }

View File

@ -0,0 +1,140 @@
/*
* Copyright (c) 2012-2015 Arne Schwabe
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
*/
package de.blinkt.openvpn.core;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Parcel;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* Created by arne on 23.01.16.
*/
class LogFileHandler extends Handler {
static final int TRIM_LOG_FILE = 100;
static final int FLUSH_TO_DISK = 101;
static final int LOG_INIT = 102;
public static final int LOG_MESSAGE = 103;
private static FileOutputStream mLogFile;
private static BufferedOutputStream mBufLogfile;
public static final String LOGFILE_NAME = "logcache.dat";
public LogFileHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
try {
if (msg.what == LOG_INIT) {
readLogCache((File) msg.obj);
openLogFile((File) msg.obj);
} else if (msg.what == LOG_MESSAGE && msg.obj instanceof VpnStatus.LogItem) {
// Ignore log messages if not yet initialized
if (mLogFile == null)
return;
writeLogItemToDisk((VpnStatus.LogItem) msg.obj);
} else if (msg.what == TRIM_LOG_FILE) {
trimLogFile();
for (VpnStatus.LogItem li : VpnStatus.getlogbuffer())
writeLogItemToDisk(li);
} else if (msg.what == FLUSH_TO_DISK) {
flushToDisk();
}
} catch (IOException e) {
e.printStackTrace();
VpnStatus.logError("Error during log cache: " + msg.what);
VpnStatus.logException(e);
}
}
private void flushToDisk() throws IOException {
mLogFile.flush();
}
private static void trimLogFile() {
try {
mBufLogfile.flush();
mLogFile.getChannel().truncate(0);
} catch (IOException e) {
e.printStackTrace();
}
}
private void writeLogItemToDisk(VpnStatus.LogItem li) throws IOException {
Parcel p = Parcel.obtain();
li.writeToParcel(p, 0);
// We do not really care if the log cache breaks between Android upgrades,
// write binary format to disc
byte[] liBytes = p.marshall();
mLogFile.write(liBytes.length & 0xff);
mLogFile.write(liBytes.length >> 8);
mLogFile.write(liBytes);
p.recycle();
}
private void openLogFile (File cacheDir) throws FileNotFoundException {
File logfile = new File(cacheDir, LOGFILE_NAME);
mLogFile = new FileOutputStream(logfile);
mBufLogfile = new BufferedOutputStream(mLogFile);
}
private void readLogCache(File cacheDir) {
File logfile = new File(cacheDir, LOGFILE_NAME);
if (!logfile.exists() || !logfile.canRead())
return;
VpnStatus.logDebug("Reread log items from cache file");
try {
BufferedInputStream logFile = new BufferedInputStream(new FileInputStream(logfile));
byte[] buf = new byte[8192];
int read = logFile.read(buf, 0, 2);
while (read > 0) {
// Marshalled LogItem
int len = (0xff & buf[0]) | buf[1] << 8;
read = logFile.read(buf, 0, len);
Parcel p = Parcel.obtain();
p.unmarshall(buf, 0, read);
p.setDataPosition(0);
VpnStatus.LogItem li = VpnStatus.LogItem.CREATOR.createFromParcel(p);
VpnStatus.newLogItem(li, true);
p.recycle();
//Next item
read = logFile.read(buf, 0, 2);
}
} catch (java.io.IOException e) {
VpnStatus.logError("Reading cached logfile failed");
VpnStatus.logException(e);
e.printStackTrace();
// ignore reading file error
}
}
}

View File

@ -493,6 +493,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
} }
// Just in case unregister for state // Just in case unregister for state
VpnStatus.removeStateListener(this); VpnStatus.removeStateListener(this);
VpnStatus.flushLog();
} }

View File

@ -12,6 +12,8 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature; import android.content.pm.Signature;
import android.os.Build; import android.os.Build;
import android.os.HandlerThread;
import android.os.Message;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import android.text.TextUtils; import android.text.TextUtils;
@ -112,6 +114,16 @@ public class VpnStatus {
} }
public static void initLogCache(File cacheDir) {
Message m = mLogFileHandler.obtainMessage(LogFileHandler.LOG_INIT, cacheDir);
mLogFileHandler.sendMessage(m);
}
public static void flushLog() {
mLogFileHandler.sendEmptyMessage(LogFileHandler.FLUSH_TO_DISK);
}
public enum ConnectionStatus { public enum ConnectionStatus {
LEVEL_CONNECTED, LEVEL_CONNECTED,
LEVEL_VPNPAUSED, LEVEL_VPNPAUSED,
@ -122,7 +134,7 @@ public class VpnStatus {
LEVEL_START, LEVEL_START,
LEVEL_AUTH_FAILED, LEVEL_AUTH_FAILED,
LEVEL_WAITING_FOR_USER_INPUT, LEVEL_WAITING_FOR_USER_INPUT,
UNKNOWN_LEVEL; UNKNOWN_LEVEL
} }
public enum LogLevel { public enum LogLevel {
@ -167,18 +179,24 @@ public class VpnStatus {
private static ConnectionStatus mLastLevel = ConnectionStatus.LEVEL_NOTCONNECTED; private static ConnectionStatus mLastLevel = ConnectionStatus.LEVEL_NOTCONNECTED;
private static final LogFileHandler mLogFileHandler;
static { static {
logbuffer = new LinkedList<>(); logbuffer = new LinkedList<>();
logListener = new Vector<>(); logListener = new Vector<>();
stateListener = new Vector<>(); stateListener = new Vector<>();
byteCountListener = new Vector<>(); byteCountListener = new Vector<>();
HandlerThread mHandlerThread = new HandlerThread("LogFileWriter", Thread.MIN_PRIORITY);
mHandlerThread.start();
mLogFileHandler = new LogFileHandler(mHandlerThread.getLooper());
logInformation(); logInformation();
} }
public static class LogItem implements Parcelable { public static class LogItem implements Parcelable {
private Object[] mArgs = null; private Object[] mArgs = null;
private String mMessage = null; private String mMessage = null;
private int mRessourceId; private int mRessourceId;
@ -350,11 +368,6 @@ public class VpnStatus {
} }
} }
public void saveLogToDisk(Context c) {
File logOut = new File(c.getCacheDir(), "log.xml");
}
public interface LogListener { public interface LogListener {
void newLog(LogItem logItem); void newLog(LogItem logItem);
} }
@ -375,6 +388,7 @@ public class VpnStatus {
public synchronized static void clearLog() { public synchronized static void clearLog() {
logbuffer.clear(); logbuffer.clear();
logInformation(); logInformation();
mLogFileHandler.sendEmptyMessage(LogFileHandler.TRIM_LOG_FILE);
} }
private static void logInformation() { private static void logInformation() {
@ -409,32 +423,34 @@ public class VpnStatus {
} }
private static int getLocalizedState(String state) { private static int getLocalizedState(String state) {
if (state.equals("CONNECTING")) switch (state) {
case "CONNECTING":
return R.string.state_connecting; return R.string.state_connecting;
else if (state.equals("WAIT")) case "WAIT":
return R.string.state_wait; return R.string.state_wait;
else if (state.equals("AUTH")) case "AUTH":
return R.string.state_auth; return R.string.state_auth;
else if (state.equals("GET_CONFIG")) case "GET_CONFIG":
return R.string.state_get_config; return R.string.state_get_config;
else if (state.equals("ASSIGN_IP")) case "ASSIGN_IP":
return R.string.state_assign_ip; return R.string.state_assign_ip;
else if (state.equals("ADD_ROUTES")) case "ADD_ROUTES":
return R.string.state_add_routes; return R.string.state_add_routes;
else if (state.equals("CONNECTED")) case "CONNECTED":
return R.string.state_connected; return R.string.state_connected;
else if (state.equals("DISCONNECTED")) case "DISCONNECTED":
return R.string.state_disconnected; return R.string.state_disconnected;
else if (state.equals("RECONNECTING")) case "RECONNECTING":
return R.string.state_reconnecting; return R.string.state_reconnecting;
else if (state.equals("EXITING")) case "EXITING":
return R.string.state_exiting; return R.string.state_exiting;
else if (state.equals("RESOLVE")) case "RESOLVE":
return R.string.state_resolve; return R.string.state_resolve;
else if (state.equals("TCP_CONNECT")) case "TCP_CONNECT":
return R.string.state_tcp_connect; return R.string.state_tcp_connect;
else default:
return R.string.unknown_state; return R.string.unknown_state;
}
} }
@ -536,17 +552,33 @@ public class VpnStatus {
newLogItem(new LogItem(LogLevel.DEBUG, resourceId, args)); newLogItem(new LogItem(LogLevel.DEBUG, resourceId, args));
} }
private static void newLogItem(LogItem logItem) {
newLogItem(logItem, false);
}
private synchronized static void newLogItem(LogItem logItem) {
synchronized static void newLogItem(LogItem logItem, boolean cachedLine) {
if (cachedLine) {
logbuffer.addFirst(logItem);
} else {
logbuffer.addLast(logItem); logbuffer.addLast(logItem);
if (logbuffer.size() > MAXLOGENTRIES) Message m = mLogFileHandler.obtainMessage(LogFileHandler.LOG_MESSAGE, logItem);
mLogFileHandler.sendMessage(m);
}
if (logbuffer.size() > MAXLOGENTRIES + MAXLOGENTRIES / 2) {
while (logbuffer.size() > MAXLOGENTRIES)
logbuffer.removeFirst(); logbuffer.removeFirst();
mLogFileHandler.sendMessage(mLogFileHandler.obtainMessage(LogFileHandler.TRIM_LOG_FILE));
}
for (LogListener ll : logListener) { for (LogListener ll : logListener) {
ll.newLog(logItem); ll.newLog(logItem);
} }
} }
public static void logError(String msg) { public static void logError(String msg) {
newLogItem(new LogItem(LogLevel.ERROR, msg)); newLogItem(new LogItem(LogLevel.ERROR, msg));

View File

@ -39,5 +39,4 @@
android:icon="@drawable/ic_menu_edit" android:icon="@drawable/ic_menu_edit"
android:showAsAction="withText|ifRoom" android:showAsAction="withText|ifRoom"
android:title="@string/edit_vpn"/> android:title="@string/edit_vpn"/>
</menu> </menu>