package com.nolanlawson.logcat.helper; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import android.content.Context; import android.os.Environment; import android.widget.Toast; import com.nolanlawson.logcat.R; import com.nolanlawson.logcat.data.SavedLog; import com.nolanlawson.logcat.util.UtilLogger; public class SaveLogHelper { private static final int BUFFER = 0x1000; // 4K public static final String TEMP_DEVICE_INFO_FILENAME = "device_info.txt"; public static final String TEMP_LOG_FILENAME = "logcat.txt"; public static final String TEMP_ZIP_FILENAME = "logcat_and_device_info.zip"; private static final String LEGACY_SAVED_LOGS_DIR = "catlog_saved_logs"; private static final String CATLOG_DIR = "catlog"; private static final String SAVED_LOGS_DIR = "saved_logs"; private static final String TMP_DIR = "tmp"; private static UtilLogger log = new UtilLogger(SaveLogHelper.class); public static File saveTemporaryFile(Context context, String filename, CharSequence text, List<CharSequence> lines) { PrintStream out = null; try { File tempFile = new File(getTempDirectory(), filename); // specifying BUFFER gets rid of an annoying warning message out = new PrintStream(new BufferedOutputStream(new FileOutputStream(tempFile, false), BUFFER)); if (text != null) { // one big string out.print(text); } else { // multiple lines separated by newline for (CharSequence line : lines) { out.println(line); } } log.d("Saved temp file: %s", tempFile); return tempFile; } catch (FileNotFoundException ex) { log.e(ex,"unexpected exception"); return null; } finally { if (out != null) { out.close(); } } } public static boolean checkSdCard(Context context) { boolean result = SaveLogHelper.checkIfSdCardExists(); if (!result) { Toast.makeText(context, R.string.sd_card_not_found, Toast.LENGTH_LONG).show(); } return result; } public static boolean checkIfSdCardExists() { File sdcardDir = Environment.getExternalStorageDirectory(); return sdcardDir != null && sdcardDir.listFiles() != null; } public static File getFile(String filename) { File catlogDir = getSavedLogsDirectory(); File file = new File(catlogDir, filename); return file; } public static void deleteLogIfExists(String filename) { File catlogDir = getSavedLogsDirectory(); File file = new File(catlogDir, filename); if (file.exists()) { file.delete(); } } public static Date getLastModifiedDate(String filename) { File catlogDir = getSavedLogsDirectory(); File file = new File(catlogDir, filename); if (file.exists()) { return new Date(file.lastModified()); } else { // shouldn't happen log.e("file last modified date not found: %s", filename); return new Date(); } } /** * Get all the log filenames, order by last modified descending * @return */ public static List<String> getLogFilenames() { File catlogDir = getSavedLogsDirectory(); File[] filesArray = catlogDir.listFiles(); if (filesArray == null) { return Collections.emptyList(); } List<File> files = new ArrayList<File>(Arrays.asList(filesArray)); Collections.sort(files, new Comparator<File>(){ @Override public int compare(File object1, File object2) { return new Long(object2.lastModified()).compareTo(object1.lastModified()); }}); List<String> result = new ArrayList<String>(); for (File file : files) { result.add(file.getName()); } return result; } public static SavedLog openLog(String filename, int maxLines) { File catlogDir = getSavedLogsDirectory(); File logFile = new File(catlogDir, filename); LinkedList<String> logLines = new LinkedList<String>(); boolean truncated = false; BufferedReader bufferedReader = null; try { bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(logFile)), BUFFER); while (bufferedReader.ready()) { logLines.add(bufferedReader.readLine()); if (logLines.size() > maxLines) { logLines.removeFirst(); truncated = true; } } } catch (IOException ex) { log.e(ex, "couldn't read file"); } finally { if (bufferedReader != null) { try { bufferedReader.close(); } catch (IOException e) { log.e(e, "couldn't close buffered reader"); } } } SavedLog result = new SavedLog(); result.setLogLines(logLines); result.setTruncated(truncated); return result; } public static synchronized boolean saveLog(CharSequence logString, String filename) { return saveLog(null, logString, filename); } public static synchronized boolean saveLog(List<CharSequence> logLines, String filename) { return saveLog(logLines, null, filename); } private static boolean saveLog(List<CharSequence> logLines, CharSequence logString, String filename) { File catlogDir = getSavedLogsDirectory(); File newFile = new File(catlogDir, filename); try { if (!newFile.exists()) { newFile.createNewFile(); } } catch (IOException ex) { log.e(ex, "couldn't create new file"); return false; } PrintStream out = null; try { // specifying BUFFER gets rid of an annoying warning message out = new PrintStream(new BufferedOutputStream(new FileOutputStream(newFile, true), BUFFER)); // save a log as either a list of strings or as a charsequence if (logLines != null) { for (CharSequence line : logLines) { out.println(line); } } else if (logString != null) { out.print(logString); } } catch (FileNotFoundException ex) { log.e(ex,"unexpected exception"); return false; } finally { if (out != null) { out.close(); } } return true; } public static File getTempDirectory() { File catlogDir = getCatlogDirectory(); File tmpDir = new File(catlogDir, TMP_DIR); if (!tmpDir.exists()) { tmpDir.mkdir(); } return tmpDir; } private static File getSavedLogsDirectory() { File catlogDir = getCatlogDirectory(); File savedLogsDir = new File(catlogDir, SAVED_LOGS_DIR); if (!savedLogsDir.exists()) { savedLogsDir.mkdir(); } return savedLogsDir; } private static File getCatlogDirectory() { File sdcardDir = Environment.getExternalStorageDirectory(); File catlogDir = new File(sdcardDir, CATLOG_DIR); if (!catlogDir.exists()) { catlogDir.mkdir(); } return catlogDir; } /** * I used to save logs to /sdcard/catlog_saved_logs. Now it's /sdcard/catlog/saved_logs. Move any files that * need to be moved to the new directory. * * @param sdcardDir * @param savedLogsDir */ public static synchronized void moveLogsFromLegacyDirIfNecessary() { File sdcardDir = Environment.getExternalStorageDirectory(); File legacyDir = new File(sdcardDir, LEGACY_SAVED_LOGS_DIR); if (legacyDir.exists() && legacyDir.isDirectory()) { File savedLogsDir = getSavedLogsDirectory(); for (File file : legacyDir.listFiles()) { file.renameTo(new File(savedLogsDir, file.getName())); } legacyDir.delete(); } } public static boolean legacySavedLogsDirExists() { File sdcardDir = Environment.getExternalStorageDirectory(); File legacyDir = new File(sdcardDir, LEGACY_SAVED_LOGS_DIR); return legacyDir.exists() && legacyDir.isDirectory(); } public static File saveTemporaryZipFile(String filename, List<File> files) { try { return saveTemporaryZipFileAndThrow(filename, files); } catch (IOException e) { log.e(e, "unexpected error"); } return null; } private static File saveTemporaryZipFileAndThrow(String filename, List<File> files) throws IOException { File zipFile = new File(getTempDirectory(), filename); ZipOutputStream output = null; try { output = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile), BUFFER)); for (File file : files) { FileInputStream fi = new FileInputStream(file); BufferedInputStream input = null; try { input = new BufferedInputStream(fi, BUFFER); ZipEntry entry = new ZipEntry(file.getName()); output.putNextEntry(entry); copy(input, output); } finally { if (input != null) { input.close(); } } } } finally { if (output != null) { output.close(); } } return zipFile; } /** * Copies all bytes from the input stream to the output stream. Does not * close or flush either stream. * * Taken from Google Guava ByteStreams.java * * @param from * the input stream to read from * @param to * the output stream to write to * @return the number of bytes copied * @throws IOException * if an I/O error occurs */ private static long copy(InputStream from, OutputStream to) throws IOException { byte[] buf = new byte[BUFFER]; long total = 0; while (true) { int r = from.read(buf); if (r == -1) { break; } to.write(buf, 0, r); total += r; } return total; } }