com.amazonaws.mobileconnectors.amazonmobileanalytics.internal.delivery.FileEventStore Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aws-android-sdk-mobileanalytics Show documentation
Show all versions of aws-android-sdk-mobileanalytics Show documentation
Amazon Web Services SDK for Android
/*
* Copyright 2010-2018 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.amazonmobileanalytics.internal.delivery;
import android.util.Log;
import com.amazonaws.mobileconnectors.amazonmobileanalytics.internal.core.AnalyticsContext;
import com.amazonaws.mobileconnectors.amazonmobileanalytics.internal.core.system.FileManager;
import com.amazonaws.util.StringUtils;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.concurrent.locks.ReentrantLock;
public class FileEventStore implements EventStore {
private static final String TAG = "FileEventStore";
static final String EVENTS_DIRECTORY = "events";
static final String EVENT_FILE_NAME = "eventsFile";
static final String KEY_MAX_STORAGE_SIZE = "maxStorageSize";
static final double ERROR_LENGTH_THRESHOLD_PERCENTAGE = 1.1;
private final ReentrantLock accessLock = new ReentrantLock(true);
static long MAX_STORAGE_SIZE = 1024 * 1024 * 5L;
private final AnalyticsContext context;
private File eventsFile;
public static FileEventStore newInstance(final AnalyticsContext context) {
return new FileEventStore(context);
}
public FileEventStore(final AnalyticsContext context) {
this.context = context;
tryCreateEventsFile();
}
@Override
public boolean put(final String event) throws EventStoreException {
boolean success = false;
BufferedWriter writer = null;
accessLock.lock();
try {
writer = tryInitializeWriter();
if (writer != null) {
final long maxStorageSize = context.getConfiguration().optLong(
KEY_MAX_STORAGE_SIZE, MAX_STORAGE_SIZE);
if (eventsFile.length() + event.length() <= maxStorageSize) {
writer.write(event);
writer.newLine();
writer.flush();
success = true;
}
}
} catch (IOException e) {
Log.e(TAG, "Failed to persist the event", e);
} finally {
tryCloseWriter(writer);
accessLock.unlock();
}
return success;
}
private boolean tryCreateEventsFile() {
if (eventsFile != null && eventsFile.exists()) {
return true;
}
synchronized (this) {
if (eventsFile != null && eventsFile.exists()) {
return true;
}
try {
FileManager fileManager = context.getSystem().getFileManager();
File eventsDir = fileManager.createDirectory(EVENTS_DIRECTORY);
eventsFile = fileManager.createFile(new File(eventsDir, EVENT_FILE_NAME));
return true;
} catch (IOException e) {
Log.e(TAG, "Unable to open events file");
Log.e(TAG, "An error occurred while attempting to create/open the events file", e);
}
return false;
}
}
private BufferedWriter tryInitializeWriter() throws EventStoreException {
BufferedWriter writer = null;
try {
if (tryCreateEventsFile()) {
OutputStream stream = context.getSystem().getFileManager()
.newOutputStream(eventsFile, true);
writer = new BufferedWriter(new OutputStreamWriter(stream, StringUtils.UTF8));
} else {
throw new EventStoreException("Unable to create eventsFile");
}
} catch (EventStoreException e) {
throw e;
} catch (FileNotFoundException e) {
Log.e(TAG, "Events file not found to persist event to", e);
throw new EventStoreException("Unable to open events file writer", e);
} catch (Exception e) {
Log.e(TAG, "Unexpected exception", e);
throw new EventStoreException("Unexpected error while creating eventsFile writer", e);
}
return writer;
}
private void tryCloseWriter(final Writer writer) throws EventStoreException {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
Log.e(TAG, "Unable to close writer for events file", e);
}
}
}
private File deleteReadEvents(final int lineNumber) {
// Write all events after line number to a temporary file
FileManager fileManager = context.getSystem().getFileManager();
File eventsDir = fileManager.createDirectory(EVENTS_DIRECTORY);
File tempEventsFile = null;
try {
File tempFile = new File(eventsDir, EVENT_FILE_NAME + ".tmp");
if (tempFile.exists()) {
if (!tempFile.delete()) {
Log.e(TAG, "Failed to delete previous temp file");
}
}
tempEventsFile = fileManager.createFile(tempFile);
} catch (IOException e) {
Log.e(TAG,
"An error occurred while attempting to create/open the temporary events file",
e);
return eventsFile;
}
boolean errorsOccurred = false;
if (tempEventsFile != null && eventsFile.exists() && tempEventsFile.exists()) {
BufferedReader reader = null;
PrintWriter writer = null;
try {
reader = new BufferedReader(new FileReader(eventsFile));
writer = new PrintWriter(new FileWriter(tempEventsFile, true));
String line = null;
int currentLineNumber = 0;
while ((line = reader.readLine()) != null) {
currentLineNumber++;
if (currentLineNumber > lineNumber) {
writer.println(line);
writer.flush();
}
}
} catch (FileNotFoundException e) {
errorsOccurred = true;
Log.e(TAG, "The events file count not be found", e);
} catch (IOException e) {
Log.e(TAG, "An error occurred while attempting to delete the read events", e);
errorsOccurred = true;
} finally {
if (writer != null) {
writer.close();
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
}
}
if (!errorsOccurred) {
if (eventsFile.delete()) {
if (!tempEventsFile.renameTo(eventsFile)) {
Log.e(TAG, "Failed to rename temp file to events file");
}
try {
eventsFile = fileManager
.createFile(new File(eventsDir, EVENT_FILE_NAME));
} catch (IOException e) {
Log.e(TAG,
"An error occurred while attempting to create the eventsFile",
e);
}
} else {
Log.e(TAG, "Failed to delete previous events file");
}
}
}
File tempFile = new File(eventsDir, EVENT_FILE_NAME + ".tmp");
if (tempFile.exists()) {
if (!tempFile.delete()) {
Log.e(TAG, "Failed to delete temp file");
}
}
}
return eventsFile;
}
@Override
public EventIterator iterator() {
return new EventIterator() {
int linesRead = 0;
String nextBuffer = null;
BufferedReader reader = null;
boolean isEndOfFile = false;
private boolean tryOpenReader() {
if (reader != null) {
return true;
}
if (!isEndOfFile) {
InputStreamReader streamReader = null;
try {
streamReader = new InputStreamReader(context.getSystem().getFileManager()
.newInputStream(eventsFile), StringUtils.UTF8);
} catch (FileNotFoundException e) {
Log.e(TAG, "Could not open the events file", e);
}
if (streamReader != null) {
reader = new BufferedReader(streamReader);
return true;
}
}
return false;
}
private void tryCloseReader() {
if (reader != null) {
try {
reader.close();
} catch (IOException ioe) {
Log.e(TAG, "Unable to close reader for events file", ioe);
} finally {
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;
} 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;
} finally {
accessLock.unlock();
}
}
@Override
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");
}
@Override
public void removeReadEvents() {
accessLock.lock();
try {
deleteReadEvents(linesRead);
resetReader();
} finally {
accessLock.unlock();
}
}
private void resetReader() {
tryCloseReader();
linesRead = 0;
nextBuffer = null;
}
};
}
}