summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArne Schwabe <arne@rfc2549.org>2016-01-24 14:30:23 +0000
committerArne Schwabe <arne@rfc2549.org>2016-01-24 14:30:23 +0000
commit0e6e77c476586f0f6ec37da0b0996631dfbe9cd0 (patch)
treee868d136e7b9b0ca3701b92bbaf4246ccb377457
parent8414c4f167feebda70397c5ebc8cc13728315731 (diff)
Implement persistent disk log cache. (closes #340)
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java2
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/LogFileHandler.java140
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java1
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/VpnStatus.java112
-rw-r--r--main/src/main/res/menu/logmenu.xml1
5 files changed, 215 insertions, 41 deletions
diff --git a/main/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java b/main/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java
index 2420194d..0ddd01b3 100644
--- a/main/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java
+++ b/main/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java
@@ -37,5 +37,7 @@ public class ICSOpenVPNApplication extends Application {
if (BuildConfig.DEBUG) {
//ACRA.init(this);
}
+
+ VpnStatus.initLogCache(getApplicationContext().getCacheDir());
}
}
diff --git a/main/src/main/java/de/blinkt/openvpn/core/LogFileHandler.java b/main/src/main/java/de/blinkt/openvpn/core/LogFileHandler.java
new file mode 100644
index 00000000..e3422df0
--- /dev/null
+++ b/main/src/main/java/de/blinkt/openvpn/core/LogFileHandler.java
@@ -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
+ }
+ }
+
+}
diff --git a/main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java b/main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java
index 85336ac3..55d4f55f 100644
--- a/main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java
+++ b/main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java
@@ -493,6 +493,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
}
// Just in case unregister for state
VpnStatus.removeStateListener(this);
+ VpnStatus.flushLog();
}
diff --git a/main/src/main/java/de/blinkt/openvpn/core/VpnStatus.java b/main/src/main/java/de/blinkt/openvpn/core/VpnStatus.java
index 3ff41eee..c2085502 100644
--- a/main/src/main/java/de/blinkt/openvpn/core/VpnStatus.java
+++ b/main/src/main/java/de/blinkt/openvpn/core/VpnStatus.java
@@ -12,6 +12,8 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
import android.os.Build;
+import android.os.HandlerThread;
+import android.os.Message;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -98,8 +100,8 @@ public class VpnStatus {
break;
}
- while(message.endsWith(","))
- message = message.substring(0, message.length()-1);
+ while (message.endsWith(","))
+ message = message.substring(0, message.length() - 1);
String prefix = c.getString(mLastStateresid) + ":";
String status = mLaststate;
@@ -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 {
LEVEL_CONNECTED,
LEVEL_VPNPAUSED,
@@ -122,7 +134,7 @@ public class VpnStatus {
LEVEL_START,
LEVEL_AUTH_FAILED,
LEVEL_WAITING_FOR_USER_INPUT,
- UNKNOWN_LEVEL;
+ UNKNOWN_LEVEL
}
public enum LogLevel {
@@ -167,18 +179,24 @@ public class VpnStatus {
private static ConnectionStatus mLastLevel = ConnectionStatus.LEVEL_NOTCONNECTED;
+ private static final LogFileHandler mLogFileHandler;
+
static {
logbuffer = new LinkedList<>();
logListener = new Vector<>();
stateListener = new Vector<>();
byteCountListener = new Vector<>();
+
+ HandlerThread mHandlerThread = new HandlerThread("LogFileWriter", Thread.MIN_PRIORITY);
+ mHandlerThread.start();
+ mLogFileHandler = new LogFileHandler(mHandlerThread.getLooper());
+
logInformation();
+
}
public static class LogItem implements Parcelable {
-
-
private Object[] mArgs = null;
private String mMessage = null;
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 {
void newLog(LogItem logItem);
}
@@ -375,6 +388,7 @@ public class VpnStatus {
public synchronized static void clearLog() {
logbuffer.clear();
logInformation();
+ mLogFileHandler.sendEmptyMessage(LogFileHandler.TRIM_LOG_FILE);
}
private static void logInformation() {
@@ -409,32 +423,34 @@ public class VpnStatus {
}
private static int getLocalizedState(String state) {
- if (state.equals("CONNECTING"))
- return R.string.state_connecting;
- else if (state.equals("WAIT"))
- return R.string.state_wait;
- else if (state.equals("AUTH"))
- return R.string.state_auth;
- else if (state.equals("GET_CONFIG"))
- return R.string.state_get_config;
- else if (state.equals("ASSIGN_IP"))
- return R.string.state_assign_ip;
- else if (state.equals("ADD_ROUTES"))
- return R.string.state_add_routes;
- else if (state.equals("CONNECTED"))
- return R.string.state_connected;
- else if (state.equals("DISCONNECTED"))
- return R.string.state_disconnected;
- else if (state.equals("RECONNECTING"))
- return R.string.state_reconnecting;
- else if (state.equals("EXITING"))
- return R.string.state_exiting;
- else if (state.equals("RESOLVE"))
- return R.string.state_resolve;
- else if (state.equals("TCP_CONNECT"))
- return R.string.state_tcp_connect;
- else
- return R.string.unknown_state;
+ switch (state) {
+ case "CONNECTING":
+ return R.string.state_connecting;
+ case "WAIT":
+ return R.string.state_wait;
+ case "AUTH":
+ return R.string.state_auth;
+ case "GET_CONFIG":
+ return R.string.state_get_config;
+ case "ASSIGN_IP":
+ return R.string.state_assign_ip;
+ case "ADD_ROUTES":
+ return R.string.state_add_routes;
+ case "CONNECTED":
+ return R.string.state_connected;
+ case "DISCONNECTED":
+ return R.string.state_disconnected;
+ case "RECONNECTING":
+ return R.string.state_reconnecting;
+ case "EXITING":
+ return R.string.state_exiting;
+ case "RESOLVE":
+ return R.string.state_resolve;
+ case "TCP_CONNECT":
+ return R.string.state_tcp_connect;
+ default:
+ return R.string.unknown_state;
+ }
}
@@ -536,17 +552,33 @@ public class VpnStatus {
newLogItem(new LogItem(LogLevel.DEBUG, resourceId, args));
}
+ private static void newLogItem(LogItem logItem) {
+ newLogItem(logItem, false);
+ }
+
+
+ synchronized static void newLogItem(LogItem logItem, boolean cachedLine) {
+ if (cachedLine) {
+ logbuffer.addFirst(logItem);
+ } else {
+ logbuffer.addLast(logItem);
+ Message m = mLogFileHandler.obtainMessage(LogFileHandler.LOG_MESSAGE, logItem);
+ mLogFileHandler.sendMessage(m);
+ }
+
+ if (logbuffer.size() > MAXLOGENTRIES + MAXLOGENTRIES / 2) {
+ while (logbuffer.size() > MAXLOGENTRIES)
+ logbuffer.removeFirst();
+ mLogFileHandler.sendMessage(mLogFileHandler.obtainMessage(LogFileHandler.TRIM_LOG_FILE));
+ }
- private synchronized static void newLogItem(LogItem logItem) {
- logbuffer.addLast(logItem);
- if (logbuffer.size() > MAXLOGENTRIES)
- logbuffer.removeFirst();
for (LogListener ll : logListener) {
ll.newLog(logItem);
}
}
+
public static void logError(String msg) {
newLogItem(new LogItem(LogLevel.ERROR, msg));
diff --git a/main/src/main/res/menu/logmenu.xml b/main/src/main/res/menu/logmenu.xml
index a588081d..b8c493bc 100644
--- a/main/src/main/res/menu/logmenu.xml
+++ b/main/src/main/res/menu/logmenu.xml
@@ -39,5 +39,4 @@
android:icon="@drawable/ic_menu_edit"
android:showAsAction="withText|ifRoom"
android:title="@string/edit_vpn"/>
-
</menu> \ No newline at end of file