Java tutorial
/* * CatSaver Copyright (C) 2015 HiHex Ltd. * * 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 hihex.cs; import android.content.Context; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Throwables; import com.google.common.util.concurrent.UncheckedExecutionException; import java.io.IOException; import java.io.Writer; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Pattern; /** * Shared configuration between the WebServer and LogCollector. */ public final class Config { public static final int[] EMPTY_PID_ARRAY = {}; public static final int[] EMPTY_PID_ARRAY_LIVE_ALLOWED = {}; public final Context context; public final Preferences preferences; public final LogFiles logFiles; public final ChunkRenderer renderer; public final PidDatabase pidDatabase = new PidDatabase(); private final AtomicInteger mSystemServerPid = new AtomicInteger(-1); private final AtomicInteger mDebuggerdPid = new AtomicInteger(-1); public Config(final Context context) { this.context = context; preferences = new Preferences(context); logFiles = new LogFiles(context); renderer = new ChunkRenderer(context); } public void refreshPids() { mSystemServerPid.set(-1); mDebuggerdPid.set(-1); pidDatabase.refresh(); } private int findPidForExactProcessName(final AtomicInteger cache, final String processName) { final int pid = cache.get(); if (pid != -1) { return pid; } final int newPid = pidDatabase.findPidForExactProcessName(processName); if (!cache.compareAndSet(-1, newPid)) { return cache.get(); } else { return newPid; } } public int systemServerPid() { return findPidForExactProcessName(mSystemServerPid, "system_server"); } public int debuggerdPid() { return findPidForExactProcessName(mDebuggerdPid, "/system/bin/debuggerd"); } public Optional<Writer> startRecording(final int pid, final Optional<String> processName, final Date timestamp) throws IOException { final Writer[] optWriter = { null }; try { pidDatabase.startRecording(pid, processName, logFiles, timestamp, new Function<PidEntry, Void>() { @Override public Void apply(final PidEntry entry) { final Writer writer = entry.writer.get(); optWriter[0] = writer; try { renderer.writeHeader(writer, entry.pid, processName.or(entry.processName), timestamp); } catch (final IOException e) { throw new UncheckedExecutionException(e); } return null; } }); } catch (final UncheckedExecutionException e) { final Throwable cause = e.getCause(); Throwables.propagateIfInstanceOf(cause, IOException.class); throw Throwables.propagate(cause); } Events.bus.post(new Events.RecordCount(pidDatabase.countRecordingEntries())); return Optional.fromNullable(optWriter[0]); } public void stopRecording(final int pid, final Function<Writer, ?> cleanup) { pidDatabase.stopRecording(pid, new Function<Writer, Void>() { @Override public Void apply(final Writer writer) { try { cleanup.apply(writer); renderer.writeFooter(writer); } catch (final IOException e) { // Ignore. } return null; } }); Events.bus.post(new Events.RecordCount(pidDatabase.countRecordingEntries())); } public Optional<Writer> splitLogAndGetWriter(final int pid) { final Optional<PidEntry> optEntry = pidDatabase.getEntry(pid); if (optEntry.isPresent()) { final PidEntry entry = optEntry.get(); final long splitSize = preferences.getSplitSize(); if (splitSize >= 0 && entry.path.isPresent() && entry.path.get().length() >= splitSize) { try { renderer.writeFooter(entry.writer.get()); } catch (final IOException e) { // Ignore. } final PidEntry splitEntry = pidDatabase.splitEntry(pid, logFiles); try { renderer.writeHeader(splitEntry.writer.get(), splitEntry.pid, splitEntry.processName, new Date()); } catch (final IOException e) { // Ignore. } return splitEntry.writer; } else { return entry.writer; } } else { return Optional.absent(); } } public void flushWriter(final String filename) { final Optional<PidEntry> entry = pidDatabase.findEntry(filename); if (!entry.isPresent()) { return; } final Optional<Writer> writer = entry.get().writer; if (!entry.isPresent()) { return; } try { writer.get().flush(); } catch (final IOException e) { // Flush failure, but ignore. } } public final void startRecordingExistingProcesses() { final Date now = new Date(); final Pattern filter = preferences.getFilter(); for (final HashMap<String, String> entry : pidDatabase.runningProcesses()) { final String name = entry.get("name"); if (filter.matcher(name).find() && !entry.containsKey("recording")) { final int pid = Integer.parseInt(entry.get("pid")); try { startRecording(pid, Optional.of(name), now); } catch (final IOException e) { e.printStackTrace(); } } } } /** * Obtains the array of concrete process IDs that should record this log entry. * * @param entry The entry to be recorded. * @return The array of process IDs to record this entry. If this array is empty, there are two possible return * values: * <ul> * <li>{@link #EMPTY_PID_ARRAY} no processes should record this entry at all</li> * <li>{@link #EMPTY_PID_ARRAY_LIVE_ALLOWED} no processes need to record this entry, but this entry may be used in * live logging.</li> * </ul> */ public int[] getFilteredPidsForLog(final LogEntry entry) { entry.populateProcessName(pidDatabase); final String source = entry.getProcessName(); final Set<String> targets = pidDatabase.listRecordingProcessNames(); final HashSet<String> filtered = preferences.getLogFilter().filter(entry, source, targets); if (filtered.isEmpty()) { return EMPTY_PID_ARRAY; } targets.remove(LogEntryFilter.LIVE_SOURCE); if (filtered.isEmpty()) { return EMPTY_PID_ARRAY_LIVE_ALLOWED; } final int[] pids = new int[filtered.size()]; int i = 0; for (final String target : filtered) { final int targetPid; if (target.equals(source)) { targetPid = entry.pid(); } else { targetPid = pidDatabase.findPidForExactProcessName(target); } pids[i] = targetPid; ++i; } return pids; } }