/**
* Copyright (C) 2015 Fernando Cejas Open Source Project
* <p/>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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.fernandocejas.android10.sample.data.cache;
import android.content.Context;
import com.fernandocejas.android10.sample.data.cache.serializer.JsonSerializer;
import com.fernandocejas.android10.sample.data.entity.UserEntity;
import com.fernandocejas.android10.sample.data.exception.UserNotFoundException;
import com.fernandocejas.android10.sample.data.executor.JobExecutor;
import com.fernandocejas.android10.sample.data.executor.ThreadExecutor;
import java.io.File;
import rx.Observable;
import rx.Subscriber;
/**
* {@link UserCache} implementation.
*/
public class UserCacheImpl implements UserCache {
private static final String SETTINGS_FILE_NAME = "com.fernandocejas.android10.SETTINGS";
private static final String SETTINGS_KEY_LAST_CACHE_UPDATE = "last_cache_update";
private static final String DEFAULT_FILE_NAME = "user_";
private static final long EXPIRATION_TIME = 60 * 10 * 1000;
private final Context context;
private final File cacheDir;
private JsonSerializer serializer;
private FileManager fileManager;
private ThreadExecutor threadExecutor;
/**
* Constructor of the class {@link UserCacheImpl}.
* <p/>
* <p/>
* UserCacheSerializer {@link JsonSerializer} for object serialization.
* {@link FileManager} for saving serialized objects to the file system.
*/
public UserCacheImpl(Context applicationContext) {
this(applicationContext, new JsonSerializer(), new FileManager(), new JobExecutor());
}
/**
* Constructor of the class {@link UserCacheImpl}.
*
* @param context A
* @param userCacheSerializer {@link JsonSerializer} for object serialization.
* @param fileManager {@link FileManager} for saving serialized objects to the file system.
*/
public UserCacheImpl(Context context, JsonSerializer userCacheSerializer,
FileManager fileManager, ThreadExecutor executor) {
if (context == null || userCacheSerializer == null || fileManager == null || executor == null) {
throw new IllegalArgumentException("Invalid null parameter");
}
this.context = context.getApplicationContext();
this.cacheDir = this.context.getCacheDir();
this.serializer = userCacheSerializer;
this.fileManager = fileManager;
this.threadExecutor = executor;
}
public void setSerializer(JsonSerializer serializer) {
this.serializer = serializer;
}
public void setFileManager(FileManager fileManager) {
this.fileManager = fileManager;
}
public void setThreadExecutor(ThreadExecutor threadExecutor) {
this.threadExecutor = threadExecutor;
}
@Override
public synchronized Observable<UserEntity> get(final int userId) {
return Observable.create(new Observable.OnSubscribe<UserEntity>() {
@Override
public void call(Subscriber<? super UserEntity> subscriber) {
File userEntityFile = UserCacheImpl.this.buildFile(userId);
String fileContent = UserCacheImpl.this.fileManager.readFileContent(userEntityFile);
UserEntity userEntity = UserCacheImpl.this.serializer.deserialize(fileContent);
if (userEntity != null) {
subscriber.onNext(userEntity);
subscriber.onCompleted();
} else {
subscriber.onError(new UserNotFoundException());
}
}
});
}
@Override
public synchronized void put(UserEntity userEntity) {
if (userEntity != null) {
File userEntitiyFile = this.buildFile(userEntity.getUserId());
if (!isCached(userEntity.getUserId())) {
String jsonString = this.serializer.serialize(userEntity);
this.executeAsynchronously(new CacheWriter(this.fileManager, userEntitiyFile,
jsonString));
setLastCacheUpdateTimeMillis();
}
}
}
@Override
public boolean isCached(int userId) {
File userEntitiyFile = this.buildFile(userId);
return this.fileManager.exists(userEntitiyFile);
}
@Override
public boolean isExpired() {
long currentTime = System.currentTimeMillis();
long lastUpdateTime = this.getLastCacheUpdateTimeMillis();
boolean expired = ((currentTime - lastUpdateTime) > EXPIRATION_TIME);
if (expired) {
this.evictAll();
}
return expired;
}
@Override
public synchronized void evictAll() {
this.executeAsynchronously(new CacheEvictor(this.fileManager, this.cacheDir));
}
/**
* Build a file, used to be inserted in the disk cache.
*
* @param userId The id user to build the file.
* @return A valid file.
*/
private File buildFile(int userId) {
StringBuilder fileNameBuilder = new StringBuilder();
fileNameBuilder.append(this.cacheDir.getPath());
fileNameBuilder.append(File.separator);
fileNameBuilder.append(DEFAULT_FILE_NAME);
fileNameBuilder.append(userId);
return new File(fileNameBuilder.toString());
}
/**
* Set in millis, the last time the cache was accessed.
*/
private void setLastCacheUpdateTimeMillis() {
long currentMillis = System.currentTimeMillis();
this.fileManager.writeToPreferences(this.context, SETTINGS_FILE_NAME,
SETTINGS_KEY_LAST_CACHE_UPDATE, currentMillis);
}
/**
* Get in millis, the last time the cache was accessed.
*/
private long getLastCacheUpdateTimeMillis() {
return this.fileManager.getFromPreferences(this.context, SETTINGS_FILE_NAME,
SETTINGS_KEY_LAST_CACHE_UPDATE);
}
/**
* Executes a {@link Runnable} in another Thread.
*
* @param runnable {@link Runnable} to execute
*/
private void executeAsynchronously(Runnable runnable) {
this.threadExecutor.execute(runnable);
}
/**
* {@link Runnable} class for writing to disk.
*/
private static class CacheWriter implements Runnable {
private final FileManager fileManager;
private final File fileToWrite;
private final String fileContent;
CacheWriter(FileManager fileManager, File fileToWrite, String fileContent) {
this.fileManager = fileManager;
this.fileToWrite = fileToWrite;
this.fileContent = fileContent;
}
@Override
public void run() {
this.fileManager.writeToFile(fileToWrite, fileContent);
}
}
/**
* {@link Runnable} class for evicting all the cached files
*/
private static class CacheEvictor implements Runnable {
private final FileManager fileManager;
private final File cacheDir;
CacheEvictor(FileManager fileManager, File cacheDir) {
this.fileManager = fileManager;
this.cacheDir = cacheDir;
}
@Override
public void run() {
this.fileManager.clearDirectory(this.cacheDir);
}
}
}