/* (C) 2012 Pragmatic Software
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/
*/
package com.googlecode.networklog;
import android.util.Log;
import java.lang.StringBuilder;
import java.io.RandomAccessFile;
import java.io.FileNotFoundException;
import java.io.IOException;
public class LogfileLoader {
RandomAccessFile logfile = null;
LogEntry entry = new LogEntry();
FastParser parser = new FastParser(',');
int buffer_size = 1024 * 16;
byte[] buffer = new byte[buffer_size]; // read a nice sized chunk of data
byte[] partial_buffer = new byte[128]; // for holding partial lines from end of buffer
byte[] line = new byte[128]; // a single line in the log file
int buffer_length = 0;
int buffer_pos = 0;
short partial_buffer_length = 0;
short line_length = 0;
short line_pos = 0;
long read_so_far = 0;
long processed_so_far = 0;
StringBuilder sb = new StringBuilder(128);
char[] chars = new char[128];
long length = 0; // file length
public void reset() {
buffer_length = 0;
buffer_pos = 0;
partial_buffer_length = 0;
line_length = 0;
line_pos = 0;
read_so_far = 0;
processed_so_far = 0;
length = 0;
}
public void openLogfile(String filename) throws FileNotFoundException, IllegalArgumentException, IOException {
reset();
logfile = new RandomAccessFile(filename, "r");
getLength();
}
public void closeLogfile() throws IOException {
if(logfile != null) {
logfile.close();
logfile = null;
}
}
public long getLength() throws IOException {
length = logfile.length(); // cache in member variable
return length;
}
public long getLatestTimestamp() throws IOException {
long start_pos = length - 512;
if (start_pos < 0) {
start_pos = 0;
}
logfile.seek(start_pos);
String line;
if (start_pos > 0) {
line = logfile.readLine();
}
long timestamp = -1;
while (true) {
line = logfile.readLine();
if(line == null) {
return timestamp;
}
timestamp = Long.parseLong(line.split("[^0-9-]+", 2)[0]);
}
}
public long seekToTimestampPosition(long target) throws IOException {
return seekToTimestampPosition(target, false);
}
public long seekToTimestampPosition(long target, boolean seekAhead) throws IOException {
long result = 0;
long min = 0;
long max = getLength();
long mid;
long timestamp;
String line;
// nearest match binary search to find timestamp within logfile
while(max >= min) {
if(NetworkLog.state == NetworkLog.State.EXITING) {
closeLogfile();
return -1;
}
mid = (max + min) / 2;
if(MyLog.enabled && MyLog.level >= 7) {
MyLog.d(7, "[LogfileLoader] testing position " + mid);
}
logfile.seek(mid);
// discard line as we may be anywhere within it
line = logfile.readLine();
if(line != null) {
result = mid + line.length() + 1;
}
line = logfile.readLine();
if(line == null) {
MyLog.d("[LogfileLoader] No packets found within time range");
return -1;
}
result += line.length() + 1;
timestamp = Long.parseLong(line.split("[^0-9-]+", 2)[0]);
if(timestamp < target) {
min = mid + 1;
} else if(timestamp > target) {
max = mid - 1;
} else {
// found exact match
break;
}
}
if(seekAhead == true) {
// seek to latest match
long position = result;
logfile.seek(position);
// read and discard since we may be anywhere within a line
position += logfile.readLine().length() + 1;
while(true) {
line = logfile.readLine();
if(line == null) {
break;
}
timestamp = Long.parseLong(line.split("[^0-9-]+", 2)[0]);
if(timestamp > target) {
break;
}
// update new result position
result = position + line.length() + 1;
}
}
logfile.seek(result);
return result;
}
public byte[] getBuffer() {
return buffer;
}
public int getBufferLength() {
return buffer_length;
}
public long getReadSoFar() {
return read_so_far;
}
public boolean readChunk() throws IOException {
int i;
buffer_length = logfile.read(buffer);
buffer_pos = 0;
if(buffer_length != -1) {
read_so_far += buffer_length;
}
if(MyLog.enabled && MyLog.level >= 6) {
MyLog.d(6, "[LogfileLoader] read " + buffer_length + "; so far: " + read_so_far + " out of " + length);
}
if(buffer_length == -1) {
// end of file
MyLog.d("[LogfileLoader] Reached end of file");
return false;
}
// reset line
line_length = 0;
// start line with previous unfinished line
if(partial_buffer_length > 0) {
for(i = 0; i < partial_buffer_length; i++) {
line[line_length++] = partial_buffer[i];
}
// reset partial buffer
partial_buffer_length = 0;
}
return true;
}
public LogEntry readEntry() throws IOException {
int i;
while(true) {
if(buffer_pos >= buffer_length) {
if(readChunk() == false) {
// reached end of file
return null;
}
}
// extract and parse lines
while(buffer_pos < buffer_length) {
if(line_length >= line.length - 1) {
Log.w("NetworkLog", "Skipping too long entry: [" + new String(line, 0, line.length - 1) + "]");
line_length = 0;
// read remainder of long line
while(buffer_pos < buffer_length && buffer[buffer_pos] != '\n') {
buffer_pos++;
}
continue;
}
if(buffer[buffer_pos] != '\n') {
line[line_length++] = buffer[buffer_pos++];
} else {
// got line
buffer_pos++;
if(line_length == 0) {
continue;
}
processed_so_far += line_length;
for(i = 0; i < line_length; i++) {
chars[i] = (char)line[i];
}
String value;
parser.setLine(chars, line_length);
try {
entry.timestamp = parser.getLong();
value = parser.getString();
if(value == null) {
entry.in = null;
} else {
entry.in = value;
}
value = parser.getString();
if(value == null) {
entry.out = null;
} else {
entry.out = value;
}
entry.uidString = parser.getString();
entry.uid = Integer.parseInt(entry.uidString);
entry.src = parser.getString();
entry.spt = parser.getInt();
entry.dst = parser.getString();
entry.dpt = parser.getInt();
entry.len = parser.getInt();
// Check hasMore() to support legacy logfile entries that did not include a protocol field
if(parser.hasMore()) {
entry.proto = parser.getString();
} else {
entry.proto = "";
}
} catch (Exception e) {
Log.w("NetworkLog", "Skipping malformed entry", e);
line_length = 0;
continue;
}
// reset line
line_length = 0;
return entry;
}
}
if(buffer[buffer_pos - 1] != '\n') {
// no newline; must be last line of buffer
partial_buffer_length = 0;
for(i = 0; i < line_length; i++) {
partial_buffer[partial_buffer_length++] = line[i];
}
}
}
}
public long getProcessedSoFar() {
return processed_so_far;
}
}