/*
* Overchan Android (Meta Imageboard Client)
* Copyright (C) 2014-2016 miku-nyan <https://github.com/miku-nyan>
*
* 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 nya.miku.wishmaster.cache;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.lang3.tuple.Pair;
import nya.miku.wishmaster.api.models.AttachmentModel;
import nya.miku.wishmaster.api.models.BadgeIconModel;
import nya.miku.wishmaster.api.models.BoardModel;
import nya.miku.wishmaster.api.models.DeletePostModel;
import nya.miku.wishmaster.api.models.PostModel;
import nya.miku.wishmaster.api.models.SendPostModel;
import nya.miku.wishmaster.api.models.SimpleBoardModel;
import nya.miku.wishmaster.api.models.ThreadModel;
import nya.miku.wishmaster.api.models.UrlPageModel;
import nya.miku.wishmaster.common.Async;
import nya.miku.wishmaster.common.IOUtils;
import nya.miku.wishmaster.common.Logger;
import nya.miku.wishmaster.common.MainApplication;
import nya.miku.wishmaster.lib.KryoOutputHC;
import nya.miku.wishmaster.ui.tabs.TabModel;
import nya.miku.wishmaster.ui.tabs.TabsIdStack;
import nya.miku.wishmaster.ui.tabs.TabsState;
import android.os.Build;
import android.support.v4.util.AtomicFile;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.esotericsoftware.kryo.serializers.TaggedFieldSerializer;
/**
* Сериализация объектов (на основе kryo)
* @author miku-nyan
*
*/
public class Serializer {
private static final String TAG = "Serializer";
private final FileCache fileCache;
private final AtomicFile tabsStateFile;
private final Kryo kryo;
private final Object kryoLock = new Object();
/**
* Конструктор
* @param fileCache объект файлового кэша
*/
public Serializer(FileCache fileCache) {
this.fileCache = fileCache;
this.tabsStateFile = new AtomicFile(new File(fileCache.getFilesDirectory(), FileCache.TABS_FILENAME));
this.kryo = new Kryo();
this.kryo.setReferences(false);
this.kryo.setDefaultSerializer(TaggedFieldSerializer.class);
this.kryo.register(TabsState.class, 0);
this.kryo.register(TabModel.class, 1);
this.kryo.register(TabsIdStack.class, 2);
this.kryo.register(SerializablePage.class, 3);
this.kryo.register(SerializableBoardsList.class, 4);
this.kryo.register(AttachmentModel.class, 5);
this.kryo.register(BadgeIconModel.class, 6);
this.kryo.register(BoardModel.class, 7);
this.kryo.register(DeletePostModel.class, 8);
this.kryo.register(PostModel.class, 9);
this.kryo.register(SendPostModel.class, 10);
this.kryo.register(SimpleBoardModel.class, 11);
this.kryo.register(ThreadModel.class, 12);
this.kryo.register(UrlPageModel.class, 13);
this.kryo.register(AttachmentModel[].class, 14);
this.kryo.register(BadgeIconModel[].class, 15);
this.kryo.register(BoardModel[].class, 16);
this.kryo.register(DeletePostModel[].class, 17);
this.kryo.register(PostModel[].class, 18);
this.kryo.register(SendPostModel[].class, 19);
this.kryo.register(SimpleBoardModel[].class, 20);
this.kryo.register(ThreadModel[].class, 21);
this.kryo.register(UrlPageModel[].class, 22);
this.kryo.register(java.util.ArrayList.class, 23);
this.kryo.register(java.util.LinkedList.class, 24);
this.kryo.register(java.io.File.class, new FileSerializer(), 25);
this.kryo.register(java.io.File[].class, 26);
}
private void serialize(String filename, Object obj) {
synchronized (kryoLock) {
File file = fileCache.create(filename);
Output output = null;
try {
output = createOutput(new FileOutputStream(file));
kryo.writeObject(output, obj);
} catch (Exception e) {
Logger.e(TAG, e);
} catch (OutOfMemoryError oom) {
MainApplication.freeMemory();
Logger.e(TAG, oom);
} finally {
IOUtils.closeQuietly(output);
}
fileCache.put(file);
}
}
private void serializeAsync(final String filename, final Object obj) {
Async.runAsync(new Runnable() {
@Override
public void run() {
serialize(filename, obj);
}
});
}
private <T> T deserialize(File file, Class<T> type) {
if (file == null || !file.exists()) {
return null;
}
synchronized (kryoLock) {
Input input = null;
try {
input = new Input(new FileInputStream(file));
return kryo.readObject(input, type);
} catch (Exception e) {
Logger.e(TAG, e);
} catch (OutOfMemoryError oom) {
MainApplication.freeMemory();
Logger.e(TAG, oom);
} finally {
IOUtils.closeQuietly(input);
}
}
return null;
}
public <T> T deserialize(String fileName, Class<T> type) {
return deserialize(fileCache.get(fileName), type);
}
public void serializePage(String hash, SerializablePage page) {
serializeAsync(FileCache.PREFIX_PAGES + hash, page);
}
public SerializablePage deserializePage(String hash) {
try {
return deserialize(FileCache.PREFIX_PAGES + hash, SerializablePage.class);
} catch (Exception e) {
Logger.e(TAG, e);
return null;
}
}
public void serializeBoardsList(String hash, SerializableBoardsList boardsList) {
serializeAsync(FileCache.PREFIX_BOARDS + hash, boardsList);
}
public SerializableBoardsList deserializeBoardsList(String hash) {
try {
return deserialize(FileCache.PREFIX_BOARDS + hash, SerializableBoardsList.class);
} catch (Exception e) {
Logger.e(TAG, e);
return null;
}
}
public void serializeDraft(String hash, SendPostModel draft) {
serializeAsync(FileCache.PREFIX_DRAFTS + hash, draft);
}
public SendPostModel deserializeDraft(String hash) {
try {
return deserialize(FileCache.PREFIX_DRAFTS + hash, SendPostModel.class);
} catch (Exception e) {
Logger.e(TAG, e);
return null;
}
}
public void removeDraft(String hash) {
File file = fileCache.get(FileCache.PREFIX_DRAFTS + hash);
if (file != null) {
fileCache.delete(file);
}
}
public void serializeTabsState(final TabsState state) {
Async.runAsync(new Runnable() {
@Override
public void run() {
synchronized (kryoLock) {
FileOutputStream fileStream = null;
try {
fileStream = tabsStateFile.startWrite();
Output output = createOutput(fileStream);
kryo.writeObject(output, state);
output.flush();
tabsStateFile.finishWrite(fileStream);
} catch (Exception|OutOfMemoryError e) {
if (e instanceof OutOfMemoryError) MainApplication.freeMemory();
Logger.e(TAG, e);
tabsStateFile.failWrite(fileStream);
}
}
}
});
}
public TabsState deserializeTabsState() {
synchronized (kryoLock) {
Input input = null;
try {
input = new Input(tabsStateFile.openRead());
TabsState obj = kryo.readObject(input, TabsState.class);
if (obj != null && obj.tabsArray != null && obj.tabsIdStack != null) return obj;
} catch (Exception e) {
Logger.e(TAG, e);
} catch (OutOfMemoryError e) {
MainApplication.freeMemory();
Logger.e(TAG, e);
} finally {
IOUtils.closeQuietly(input);
}
}
return TabsState.obtainDefault();
}
public void savePage(OutputStream out, String title, UrlPageModel pageModel, SerializablePage page) {
synchronized (kryoLock) {
Output output = null;
try {
output = createOutput(out);
output.writeString(title);
kryo.writeObject(output, pageModel);
kryo.writeObject(output, page);
} finally {
IOUtils.closeQuietly(output);
}
}
}
public Pair<String, UrlPageModel> loadPageInfo(InputStream in) {
synchronized (kryoLock) {
Input input = null;
try {
input = new Input(in);
String title = input.readString();
UrlPageModel pageModel = kryo.readObject(input, UrlPageModel.class);
return Pair.of(title, pageModel);
} finally {
IOUtils.closeQuietly(input);
}
}
}
public SerializablePage loadPage(InputStream in) {
synchronized (kryoLock) {
Input input = null;
try {
input = new Input(in);
input.readString();
kryo.readObject(input, UrlPageModel.class);
return kryo.readObject(input, SerializablePage.class);
} finally {
IOUtils.closeQuietly(input);
}
}
}
private class FileSerializer extends com.esotericsoftware.kryo.Serializer<java.io.File> {
@Override
public void write (Kryo kryo, Output output, File object) {
output.writeString(object.getPath());
}
@Override
public File read (Kryo kryo, Input input, Class<File> type) {
return new File(input.readString());
}
}
private static Output createOutput(OutputStream stream) {
return isHoneycomb() ? new KryoOutputHC(stream) : new Output(stream);
}
private static boolean isHoneycomb() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && Build.VERSION.SDK_INT <= Build.VERSION_CODES.HONEYCOMB_MR2;
}
}