diff options
| author | Arne Schwabe <arne@rfc2549.org> | 2016-01-24 14:30:23 +0000 | 
|---|---|---|
| committer | Arne Schwabe <arne@rfc2549.org> | 2016-01-24 14:30:23 +0000 | 
| commit | 0e6e77c476586f0f6ec37da0b0996631dfbe9cd0 (patch) | |
| tree | e868d136e7b9b0ca3701b92bbaf4246ccb377457 | |
| parent | 8414c4f167feebda70397c5ebc8cc13728315731 (diff) | |
Implement persistent disk log cache.  (closes #340)
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  | 
