package com.erakk.lnreader.task;
import android.os.AsyncTask;
import android.util.Log;
import com.erakk.lnreader.LNReaderApplication;
import com.erakk.lnreader.R;
import com.erakk.lnreader.callback.CallbackEventData;
import com.erakk.lnreader.callback.ICallbackEventData;
import com.erakk.lnreader.callback.ICallbackNotifier;
import com.erakk.lnreader.callback.IExtendedCallbackNotifier;
import com.erakk.lnreader.dao.NovelsDao;
import com.erakk.lnreader.helper.Util;
import com.erakk.lnreader.model.ImageModel;
import com.erakk.lnreader.model.NovelContentModel;
import com.erakk.lnreader.model.PageModel;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
public class RelinkImagesTask extends AsyncTask<Void, ICallbackEventData, Void> implements ICallbackNotifier {
private static final String TAG = RelinkImagesTask.class.toString();
private final String rootPath;
private IExtendedCallbackNotifier<AsyncTaskResult<?>> callback;
private String source;
private final boolean hasError = false;
private int updated;
public static RelinkImagesTask instance;
public static RelinkImagesTask getInstance() {
return instance;
}
public static RelinkImagesTask getInstance(String rootPath, IExtendedCallbackNotifier<AsyncTaskResult<?>> callback, String source) {
if (instance == null || instance.getStatus() == Status.FINISHED) {
instance = new RelinkImagesTask(rootPath, callback, source);
} else {
instance.setCallback(callback, source);
}
return instance;
}
public void setCallback(IExtendedCallbackNotifier<AsyncTaskResult<?>> callback, String source) {
this.callback = callback;
this.source = source;
}
private RelinkImagesTask(String rootPath, IExtendedCallbackNotifier<AsyncTaskResult<?>> callback, String source) {
this.rootPath = rootPath;
this.callback = callback;
this.source = source;
}
@Override
public void onProgressCallback(ICallbackEventData message) {
publishProgress(message);
}
@Override
protected Void doInBackground(Void... params) {
BufferedWriter writer = null;
try {
// create a temporary file
String timeLog = new SimpleDateFormat("yyyyMMdd_HHmmss").format(Calendar.getInstance().getTime());
File logFile = new File(rootPath + "/RelinkImageTask_" + timeLog + ".log");
// This will output the full path where the file will be written to...
Log.d(TAG, "Writing to: " + logFile.getCanonicalPath());
writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(logFile), "UTF-8"));
writer.write("INF1. Using new image base path: " + rootPath);
writer.newLine();
writer.flush();
processImageInContents(writer);
processBigImage(writer);
writer.write("-=EOL=-");
} catch (Exception e) {
Log.e(TAG, "Failed to write log file.", e);
} finally {
try {
// Close the writer regardless of what happens...
if (writer != null)
writer.close();
} catch (Exception e) {
Log.e(TAG, "Failed when closing writer.", e);
}
}
return null;
}
private void writeLogFile(BufferedWriter writer, String key, RelinkImageData data) throws IOException {
writer.write("Context: " + key);
writer.newLine();
writer.write("Type: " + data.Type);
writer.newLine();
writer.write("Original Name: " + data.OriginalName);
writer.newLine();
writer.write("Tested File Paths: ");
writer.newLine();
for (String image : data.AlternateNames) {
writer.write("\t" + image);
writer.newLine();
}
writer.flush();
}
private void processBigImage(BufferedWriter writer) throws IOException {
writer.write("BIG1. Start processing big images");
ArrayList<ImageModel> images = NovelsDao.getInstance().getAllImages();
int count = 1;
int notReplacedCount = 0, replacedCount = 0, notFoundCount = 0;
for (ImageModel image : images) {
if (this.isCancelled()) {
String message = LNReaderApplication.getInstance().getApplicationContext().getResources().getString(R.string.relink_task_cancelled);
publishProgress(new CallbackEventData(message, source));
return;
}
String message = LNReaderApplication.getInstance().getApplicationContext().getResources().getString(R.string.relink_task_progress2, image.getName(), count, images.size());
publishProgress(new CallbackEventData(message, source));
String oldPath = image.getPath();
// skip if file exists
if (new File(oldPath).exists()) {
Log.d(TAG, "Skipping: " + oldPath);
++notReplacedCount;
continue;
}
String newPath = oldPath.replaceAll("[\\w/\\./!$%^&*()_+|~\\={}\\[\\]:\";'<>?,-]+/project/images/", rootPath + "/project/images/");
Log.i(TAG, "Trying to update big image: " + oldPath + " => " + newPath);
if (new File(newPath).exists()) {
Log.i(TAG, "Updated: " + oldPath + " => " + newPath);
image.setPath(newPath);
NovelsDao.getInstance().insertImage(image);
++updated;
++replacedCount;
} else {
Log.w(TAG, "File doesn't exists: " + newPath);
writeLogFile(writer, image.getName(), new RelinkImageData(oldPath, newPath, ImageType.Big));
++notFoundCount;
}
}
writer.write("BIG2. Big Image Summary: ");
writer.newLine();
writer.write("Total: " + images.size());
writer.newLine();
writer.write("Not replaced: " + notReplacedCount);
writer.newLine();
writer.write("Updated: " + replacedCount);
writer.newLine();
writer.write("Not Found: " + notFoundCount);
writer.newLine();
writer.newLine();
writer.flush();
}
private void processImageInContents(BufferedWriter writer) throws IOException {
writer.write("CTX1. Start processing images in contents");
writer.newLine();
// get all contents
ArrayList<PageModel> pages = NovelsDao.getInstance().getAllContentPageModel();
updated = 0;
int count = 1;
int notReplacedCount = 0, replacedCount = 0, notFoundCount = 0, totalImages = 0;
for (PageModel page : pages) {
if (this.isCancelled()) {
String message = LNReaderApplication.getInstance().getApplicationContext().getResources().getString(R.string.relink_task_cancelled);
publishProgress(new CallbackEventData(message, source));
return;
}
String message = LNReaderApplication.getInstance().getApplicationContext().getResources().getString(R.string.relink_task_progress, page.getPage(), count, pages.size());
publishProgress(new CallbackEventData(message, source));
try {
// get the contents
NovelContentModel content = NovelsDao.getInstance().getNovelContent(page, false, this);
boolean isModified = false;
if (content != null) {
// replace the rootpath based on /project/
// for now just replace the thumbs
// file:///mnt/sdcard/test/project/images/thumb/c/c7/Accel_World_v01_262.jpg/84px-Accel_World_v01_262.jpg
// file:///sdcard-ext/.bakareaderex/project/images/thumb/c/c7/Accel_World_v01_262.jpg/84px-Accel_World_v01_262.jpg
// file:///sdcard-ext/.bakareaderex/project/thumb.php?f=Kino_no_Tabi_v2_000b-back.jpg&width=120
Document doc = Jsoup.parse(content.getContent());
Elements imageElements = doc.select("img");
for (Element image : imageElements) {
++totalImages;
String imgUrl = image.attr("src");
boolean isOldFormat = imgUrl.contains("/project/images/thumb/");
boolean isNewFormat = imgUrl.contains("/project/thumb.php?f=");
if (imgUrl.startsWith("file:///") && (isOldFormat || isNewFormat)) {
String mntImgUrl = imgUrl.replace("file:///", "");
Log.d(TAG, "Found image in Content : " + imgUrl);
if (!new File(mntImgUrl).exists()) {
Log.d(TAG, "Old image doesn't exists/moved: " + mntImgUrl);
boolean isFound = false;
List<String> newUrls = new ArrayList<String>();
if (isOldFormat)
isFound = processImagePath(imgUrl, image, "/project/images/thumb/", newUrls);
else if (isNewFormat) {
isFound = processImagePath(imgUrl, image, "/project/thumb.php?f=", newUrls);
}
if (isFound) {
++updated;
++replacedCount;
isModified = true;
} else {
Log.w(TAG, "Image not found for " + imgUrl);
++notFoundCount;
writeLogFile(writer, content.getPage(), new RelinkImageData(imgUrl, newUrls, ImageType.Content));
}
String alt = image.attr("alt");
if (Util.isStringNullOrEmpty(alt)) {
image.attr("alt", image.attr("src"));
}
} else {
Log.d(TAG, "Old Image Path exists: " + mntImgUrl);
++notReplacedCount;
}
}
}
if (isModified) {
content.setContent(doc.html());
NovelsDao.getInstance().updateNovelContent(content, true);
}
}
} catch (Exception e) {
message = LNReaderApplication.getInstance().getApplicationContext().getResources().getString(R.string.relink_task_error, page.getPage());
Log.e(TAG, message, e);
publishProgress(new CallbackEventData(message, source));
}
++count;
}
writer.write("CTX2. Content Image Summary: ");
writer.newLine();
writer.write("Total Contents: " + pages.size());
writer.newLine();
writer.write("Total Images in content: " + totalImages);
writer.newLine();
writer.write("Not replaced: " + notReplacedCount);
writer.newLine();
writer.write("Updated: " + replacedCount);
writer.newLine();
writer.write("Not Found: " + notFoundCount);
writer.newLine();
writer.newLine();
writer.flush();
}
private boolean processImagePath(String imgUrl, Element image, String pattern, List<String> newUrls) throws Exception {
String regex = "file:///[\\w/\\./!$%^&*()_+|~\\={}\\[\\]:\";'<>?,-]+" + pattern;
String newUrl = imgUrl.replaceAll(regex, "file:///" + rootPath + pattern);
while (newUrl.startsWith("file:////")) {
newUrl = newUrl.replace("file:////", "file:///");
}
newUrls.add(newUrl);
// check with encoded / decoded filename
String oriFilename = newUrl.substring(newUrl.lastIndexOf(pattern) + pattern.length());
if (oriFilename.contains("%")) {
String decodedFilename = java.net.URLDecoder.decode(oriFilename, "UTF-8");
decodedFilename = newUrl.replace(oriFilename, decodedFilename);
if (Util.isInList(decodedFilename, newUrls) == -1)
newUrls.add(decodedFilename);
String decodedFilenamePlus = decodedFilename.replace("+", "_");
if (Util.isInList(decodedFilenamePlus, newUrls) == -1)
newUrls.add(decodedFilenamePlus);
}
String encodedFilename = java.net.URLEncoder.encode(oriFilename, "UTF-8");
encodedFilename = newUrl.replace(oriFilename, encodedFilename).replace("%2F", "/");
if (Util.isInList(encodedFilename, newUrls) == -1)
newUrls.add(encodedFilename);
String encodedFilenamePlus = encodedFilename.replace("%2B", "_");
if (Util.isInList(encodedFilenamePlus, newUrls) == -1)
newUrls.add(encodedFilenamePlus);
for (String url : newUrls) {
String mntNewUrl = url.replace("file:///", "");
Log.d(TAG, "Trying to replace with " + mntNewUrl);
if (new File(mntNewUrl).exists()) {
Log.i(TAG, "Replace image: " + imgUrl + " ==> " + url);
image.attr("src", url);
return true;
}
}
return false;
}
@Override
protected void onProgressUpdate(ICallbackEventData... values) {
Log.d(TAG, values[0].getMessage());
if (callback != null)
callback.onProgressCallback(new CallbackEventData(values[0].getMessage(), source));
}
@Override
protected void onPostExecute(Void result) {
if (!hasError) {
String message = LNReaderApplication.getInstance().getApplicationContext().getResources().getString(R.string.relink_task_complete, rootPath, updated);
Log.i(TAG, message);
if (callback != null)
callback.onCompleteCallback(new CallbackEventData(message, source), null);
}
}
public class RelinkImageData {
public String OriginalName = "";
public ArrayList<String> AlternateNames = new ArrayList<String>();
public ImageType Type = ImageType.Big;
public RelinkImageData(String original, String altName, ImageType type) {
this.OriginalName = original;
this.AlternateNames.add(altName);
this.Type = type;
}
public RelinkImageData(String original, List<String> altNames, ImageType type) {
this.OriginalName = original;
for (String a : altNames) {
AlternateNames.add(a);
}
this.Type = type;
}
}
public enum ImageType {
Big, Content
}
}