/* * Copyright 2010-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package com.amazonaws.mobileconnectors.kinesis.kinesisrecorder; import android.util.Log; import com.amazonaws.AmazonClientException; import com.amazonaws.util.StringUtils; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.util.concurrent.locks.ReentrantLock; /** * The FileRecordStore is responsible for recording Kinesis PutRecordRequests to * the Android disk. Currently it stores each request as a JSON object * representing it's properties. One request per line. */ class FileRecordStore { private static final String TAG = "FileRecordStore"; private final ReentrantLock accessLock = new ReentrantLock(true); /** The file the requests are stored in **/ private File recordFile; /** The FileManager used for interacting with the FS **/ private final FileManager fileManager; private final String recordFileName; private final long maxStorageSize; /** * Creates the FileRecordStore * * @param recorderDirectory The directory (which the FileRecordStore is only * used for the KinesisRecorder) to use to store requests in * @param recordFileName Name of the record file * @param maxStorageSize Maximum storage size in bytes */ public FileRecordStore(File workDirectory, String recordFileName, long maxStorageSize) { this.fileManager = new FileManager(workDirectory); this.recordFileName = recordFileName; this.maxStorageSize = maxStorageSize; try { tryCreateRecordsFile(); } catch (IOException ioe) { throw new AmazonClientException("Failed to create fire store", ioe); } } public boolean put(final String record) throws IOException { boolean success = false; BufferedWriter writer = null; accessLock.lock(); try { writer = tryInitializeWriter(); if (recordFile.length() + record.getBytes(StringUtils.UTF8).length <= maxStorageSize) { writer.write(record); writer.newLine(); writer.flush(); success = true; } } finally { if (writer != null) { writer.close(); } accessLock.unlock(); } return success; } public long getFileSize() { return recordFile == null ? 0 : recordFile.length(); } private void tryCreateRecordsFile() throws IOException { if (recordFile != null && recordFile.exists()) { return; } synchronized (this) { if (recordFile != null && recordFile.exists()) { return; } File recordDir = fileManager.createDirectory( Constants.RECORDS_DIRECTORY); recordFile = fileManager.createFile(new File( recordDir, recordFileName)); } } private BufferedWriter tryInitializeWriter() throws IOException { BufferedWriter writer = null; tryCreateRecordsFile(); OutputStream stream = fileManager.newOutputStream(recordFile, true); writer = new BufferedWriter(new OutputStreamWriter(stream, StringUtils.UTF8)); return writer; } private File deleteAllRecords() throws IOException { File recordsDir = fileManager.createDirectory( Constants.RECORDS_DIRECTORY); recordFile.delete(); recordFile = fileManager.createFile(new File( recordsDir, recordFileName)); return recordFile; } private File deleteReadRecords(final int lineNumber) throws IOException { // Write all records after line number to a temporary file File recordsDir = fileManager.createDirectory( Constants.RECORDS_DIRECTORY); File tempRecordsFile = null; File tempFile = new File( recordsDir, recordFileName + ".tmp"); if (tempFile.exists()) { if (!tempFile.delete()) { throw new IOException("Failed to delete previous temp file"); } } tempRecordsFile = fileManager.createFile(tempFile); if (tempRecordsFile != null && recordFile.exists() && tempRecordsFile.exists()) { BufferedReader reader = null; PrintWriter writer = null; try { reader = new BufferedReader(new InputStreamReader(new FileInputStream(recordFile), StringUtils.UTF8)); writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream( tempRecordsFile, true), StringUtils.UTF8)); String line = null; int currentLineNumber = 0; while ((line = reader.readLine()) != null) { currentLineNumber++; if (currentLineNumber > lineNumber) { writer.println(line); writer.flush(); } } } finally { if (writer != null) { writer.close(); } if (reader != null) { try { reader.close(); } catch (IOException e) { } } if (!recordFile.delete() || !tempRecordsFile.renameTo(recordFile)) { throw new IOException( "Failed to delete read records and persist unread records"); } } if (tempFile.exists()) { if (!tempFile.delete()) { Log.e(TAG, "Failed to delete temp file"); } } } return recordFile; } public RecordIterator iterator() { return new RecordIterator(); } public class RecordIterator implements java.util.Iterator<String> { int linesRead = 0; String nextBuffer = null; BufferedReader reader = null; boolean isEndOfFile = false; private boolean tryOpenReader() throws FileNotFoundException { if (reader != null) { return true; } if (!isEndOfFile) { InputStreamReader streamReader = null; streamReader = new InputStreamReader(fileManager.newInputStream(recordFile), StringUtils.UTF8); if (streamReader != null) { reader = new BufferedReader(streamReader); return true; } } return false; } private void tryCloseReader() throws IOException { if (reader != null) { reader.close(); reader = null; } } @Override public boolean hasNext() { boolean hasNext = false; accessLock.lock(); try { // If there is something already buffered then there is a // next if (nextBuffer != null) { hasNext = true; } else { if (!tryOpenReader()) { return hasNext; } // Nothing was previously buffered so try to read one // more line boolean found = false; while (!found) { try { nextBuffer = reader.readLine(); found = true; } catch (IOException e) { nextBuffer = null; found = true; } } if (nextBuffer != null) { // There was at least one more line so there is a // next hasNext = true; } else { // The next line was null so it should be the end of // the file. Try to close the reader isEndOfFile = true; tryCloseReader(); } } return hasNext; } catch (FileNotFoundException fnfe) { throw new AmazonClientException("Cannot find records file", fnfe); } catch (IOException ioe) { throw new AmazonClientException("IO Error", ioe); } finally { accessLock.unlock(); } } @Override public String next() { String next = null; accessLock.lock(); try { if (nextBuffer != null) { next = nextBuffer; linesRead++; nextBuffer = null; } else { if (!tryOpenReader()) { return next; } boolean found = false; while (!found) { try { next = reader.readLine(); found = true; } catch (IOException e) { next = null; found = true; } } if (next != null) { linesRead++; } else { isEndOfFile = true; tryCloseReader(); } } return next; } catch (FileNotFoundException e) { throw new AmazonClientException("Cannot find records file", e); } catch (IOException ioe) { throw new AmazonClientException("IO Error", ioe); } finally { accessLock.unlock(); } } public String peek() { accessLock.lock(); try { hasNext(); return nextBuffer; } finally { accessLock.unlock(); } } @Override public void remove() { throw new UnsupportedOperationException( "The remove() operation is not supported for this iterator"); } public void removeReadRecords() throws IOException { accessLock.lock(); try { deleteReadRecords(linesRead); resetReader(); } finally { accessLock.unlock(); } } public void removeAllRecords() throws IOException { accessLock.lock(); try { deleteAllRecords(); resetReader(); } finally { accessLock.unlock(); } } private void resetReader() throws IOException { tryCloseReader(); linesRead = 0; nextBuffer = null; isEndOfFile = false; } public void close() throws IOException { tryCloseReader(); } } }