package free.yhc.netmbuddy.share; import android.database.SQLException; import android.support.annotation.NonNull; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.zip.ZipInputStream; import free.yhc.abaselib.AppEnv; import free.yhc.baselib.Logger; import free.yhc.baselib.async.HelperHandler; import free.yhc.baselib.async.Task; import free.yhc.baselib.exception.UnsupportedFormatException; import free.yhc.abaselib.util.AUtil; import free.yhc.baselib.util.FileUtil; import free.yhc.netmbuddy.Err; import free.yhc.netmbuddy.R; import free.yhc.netmbuddy.db.ColPlaylist; import free.yhc.netmbuddy.db.ColVideo; import free.yhc.netmbuddy.utils.JsonUtil; import free.yhc.netmbuddy.utils.UxUtil; public class ImportJob extends Task<ImportJob.Result> { private static final boolean DBG = Logger.DBG_DEFAULT; private static final Logger P = Logger.create(ImportJob.class, Logger.LOGLV_DEFAULT); private final AtomicReference<Task<Result>> mTask = new AtomicReference<>(null); private final ZipInputStream mZis; /////////////////////////////////////////////////////////////////////////// // // Upgrade old-JSON-format to latest one // /////////////////////////////////////////////////////////////////////////// public static class Result { public String message = ""; // # of successfully imported public AtomicInteger success = new AtomicInteger(0); // # of fails to import. public AtomicInteger fail = new AtomicInteger(0); } @NonNull private static JSONObject upgradeTo2(@NonNull JSONObject jo) throws JSONException, UnsupportedFormatException { // See DataModel.Video.sDBProjection final String[][] video_v1Tov2 = { { "ytid", ColVideo.VIDEOID.getName() }, { "volume", ColVideo.VOLUME.getName() }, { "bookmarks", ColVideo.BOOKMARKS.getName() }, }; // See DataModel.Playlist.sDBProjection final String[][] pl_v1Tov2 = { { "title", ColPlaylist.TITLE.getName() }, { "thumbnail_ytvid", ColPlaylist.THUMBNAIL_YTVID.getName() }, }; JSONObject pl = jo.getJSONObject(DataModel.Root.FPLAYLIST); JSONArray varr = pl.getJSONArray(DataModel.Playlist.FVIDEOS); // upgrade to v2 format for videos for (int i = 0; i < varr.length(); i++) { JSONObject o = varr.getJSONObject(i); // replace keys. for (String[] m : video_v1Tov2) if (!JsonUtil.jReplaceKey(o, m[0], m[1])) // something wrong during replacing key. JSON format is unrecognizable! throw new UnsupportedFormatException(); } // upgrade to v2 format for playlists for (String[] m : pl_v1Tov2) { if (!JsonUtil.jReplaceKey(pl, m[0], m[1])) // something wrong during replacing key. JSON format is unrecognizable! throw new UnsupportedFormatException(); } return jo; } @NonNull private static JSONObject upgrade(@NonNull JSONObject jo) throws JSONException, UnsupportedFormatException { JSONObject jometa = jo.getJSONObject(DataModel.Root.FMETA); int ov = jometa.getInt(DataModel.Meta.FVERSION); // object version while (ov < DataModel.DATAMODEL_VERSION) { switch (ov) { case 1: jo = upgradeTo2(jo); break; } ov++; } return jo; } /////////////////////////////////////////////////////////////////////////// // // // /////////////////////////////////////////////////////////////////////////// private CharSequence getReportText(boolean cancelled, Result r) { int success; int fail; if (null != r) { success = r.success.get(); fail = r.fail.get(); } else { if (DBG) P.e("Unexpected Error (returned result is null!)\n" + " recovered"); return "<null> data"; } CharSequence text = " [ " + AUtil.getResString(R.string.app_name) + " ]\n" + AUtil.getResString(R.string.import_) + " : " + AUtil.getResString(cancelled? R.string.cancelled: R.string.done) + "\n" + r.message; return text + "\n" + " " + AUtil.getResString(R.string.done) + " : " + success + "\n" + " " + AUtil.getResString(R.string.error) + " : " + fail; } /** */ private Result doExecute() throws IOException, JSONException, UnsupportedFormatException, SQLException { JSONObject rootJo; try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { FileUtil.unzip(baos, mZis); rootJo = new JSONObject(baos.toString("UTF-8")); } // --------------------------------------------- // Upgrade to current data format // --------------------------------------------- rootJo = upgrade(rootJo); DataModel.Root dr = new DataModel.Root(); dr.set(rootJo); if (!dr.verify()) throw new UnsupportedFormatException(); // --------------------------------------------- // Handling Meta data // --------------------------------------------- Task<Result> t; switch (dr.meta.type) { case PLAYLIST: t = ImportPlaylistTask.create(dr.pl); break; default: throw new UnsupportedFormatException(); } try { t.addEventListener(HelperHandler.get(), new Task.EventListener<Task, Result>(){ public void onProgressInit(@NonNull Task task, long maxProgress) { ImportJob.this.publishProgressInit(maxProgress); } public void onProgress(@NonNull Task task, long progress) { ImportJob.this.publishProgress(progress); } }); /* Order of below two lines are important. * 'onEarlyCancel' and 'isCancel' guarantee that * "below 'isCancel' is NOT executed after 'onEarlyCancel'". */ mTask.set(t); if (isCancel()) throw new InterruptedException(); return t.startSync(); } catch (SQLException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } } /////////////////////////////////////////////////////////////////////////// // // // /////////////////////////////////////////////////////////////////////////// @Override protected void onEarlyCancel(boolean started, Object param) { super.onEarlyCancel(started, param); Task<Result> t = mTask.get(); if (null != t) t.cancel(); } /////////////////////////////////////////////////////////////////////////// // // // /////////////////////////////////////////////////////////////////////////// protected ImportJob(@NonNull ZipInputStream zis) { mZis = zis; } /////////////////////////////////////////////////////////////////////////// // // // /////////////////////////////////////////////////////////////////////////// @NonNull public static ImportJob create(@NonNull ZipInputStream zis) { return new ImportJob(zis); } @Override public Result doAsync() { CharSequence text; try { Result r = doExecute(); text = getReportText(isCancel(), r); } catch (SQLException e) { text = AUtil.getResText(Err.DB_UNKNOWN.getMessage()); } catch (JSONException | UnsupportedFormatException e) { text = AUtil.getResText(Err.INVALID_DATA.getMessage()); } catch (IOException e) { text = AUtil.getResText(Err.IO_FILE.getMessage()); } catch (Exception e) { if (DBG) P.w(P.stackTrace(e)); text = AUtil.getResText(Err.UNKNOWN.getMessage()); } final CharSequence text_ = text; AppEnv.getUiHandler().post(new Runnable() { @Override public void run() { UxUtil.showTextToast(text_); } }); return null; } }