440 lines
12 KiB
Java
440 lines
12 KiB
Java
package jp.juggler.subwaytooter;
|
|
|
|
import android.annotation.SuppressLint;
|
|
import android.content.ContentValues;
|
|
import android.content.Context;
|
|
import android.content.SharedPreferences;
|
|
import android.database.Cursor;
|
|
import android.database.sqlite.SQLiteDatabase;
|
|
import android.provider.BaseColumns;
|
|
import android.support.annotation.NonNull;
|
|
import android.util.JsonReader;
|
|
import android.util.JsonToken;
|
|
import android.util.JsonWriter;
|
|
|
|
import org.json.JSONException;
|
|
import org.json.JSONObject;
|
|
|
|
import java.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.Iterator;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
|
|
import jp.juggler.subwaytooter.table.AcctColor;
|
|
import jp.juggler.subwaytooter.table.MutedApp;
|
|
import jp.juggler.subwaytooter.table.MutedWord;
|
|
import jp.juggler.subwaytooter.table.SavedAccount;
|
|
import jp.juggler.subwaytooter.util.LogCategory;
|
|
|
|
public class AppDataExporter {
|
|
|
|
static final LogCategory log = new LogCategory( "AppDataExporter" );
|
|
|
|
private static void writeJSONObject( @NonNull JsonWriter writer, @NonNull JSONObject src ) throws IOException, JSONException{
|
|
writer.beginObject();
|
|
for( Iterator< String > it = src.keys() ; it.hasNext() ; ){
|
|
String k = it.next();
|
|
if( src.isNull( k ) ){
|
|
writer.name( k );
|
|
writer.nullValue();
|
|
}else{
|
|
Object o = src.get( k );
|
|
|
|
if( o instanceof String ){
|
|
writer.name( k );
|
|
writer.value( (String) o );
|
|
|
|
}else if( o instanceof Boolean ){
|
|
writer.name( k );
|
|
writer.value( (Boolean) o );
|
|
|
|
}else if( o instanceof Number ){
|
|
writer.name( k );
|
|
writer.value( (Number) o );
|
|
|
|
}else{
|
|
throw new RuntimeException( String.format( Locale.JAPAN, "bad data type: JSONObject key =%s", k ) );
|
|
}
|
|
}
|
|
}
|
|
writer.endObject();
|
|
}
|
|
|
|
private static JSONObject readJsonObject( JsonReader reader ) throws IOException, JSONException{
|
|
JSONObject dst = new JSONObject();
|
|
|
|
reader.beginObject();
|
|
while( reader.hasNext() ){
|
|
String name = reader.nextName();
|
|
JsonToken token = reader.peek();
|
|
switch( token ){
|
|
|
|
case NULL:
|
|
reader.nextNull();
|
|
break;
|
|
|
|
case STRING:
|
|
dst.put( name, reader.nextString() );
|
|
break;
|
|
|
|
case BOOLEAN:
|
|
dst.put( name, reader.nextBoolean() );
|
|
break;
|
|
|
|
case NUMBER:
|
|
dst.put( name, reader.nextDouble() );
|
|
break;
|
|
|
|
default:
|
|
throw new RuntimeException( String.format( Locale.JAPAN, "bad data type: %s key =%s", token, name ) );
|
|
}
|
|
}
|
|
reader.endObject();
|
|
|
|
return dst;
|
|
}
|
|
|
|
private static void writeFromTable( @NonNull JsonWriter writer, String json_key, String table ) throws IOException{
|
|
|
|
writer.name( json_key );
|
|
writer.beginArray();
|
|
|
|
Cursor cursor = App1.getDB().query( table, null, null, null, null, null, null );
|
|
try{
|
|
ArrayList< String > names = new ArrayList<>();
|
|
int column_count = cursor.getColumnCount();
|
|
for( int i = 0 ; i < column_count ; ++ i ){
|
|
names.add( cursor.getColumnName( i ) );
|
|
}
|
|
while( cursor.moveToNext() ){
|
|
writer.beginObject();
|
|
|
|
for( int i = 0 ; i < column_count ; ++ i ){
|
|
switch( cursor.getType( i ) ){
|
|
case Cursor.FIELD_TYPE_NULL:
|
|
writer.name( names.get( i ) );
|
|
writer.nullValue();
|
|
break;
|
|
case Cursor.FIELD_TYPE_INTEGER:
|
|
writer.name( names.get( i ) );
|
|
writer.value( cursor.getLong( i ) );
|
|
break;
|
|
case Cursor.FIELD_TYPE_STRING:
|
|
writer.name( names.get( i ) );
|
|
writer.value( cursor.getString( i ) );
|
|
break;
|
|
|
|
case Cursor.FIELD_TYPE_FLOAT:
|
|
Double d = cursor.getDouble( i );
|
|
if( Double.isNaN( d ) || Double.isInfinite( d ) ){
|
|
log.w( "column %s is nan or infinite value.", names.get( i ) );
|
|
}else{
|
|
writer.name( names.get( i ) );
|
|
writer.value( d );
|
|
}
|
|
break;
|
|
case Cursor.FIELD_TYPE_BLOB:
|
|
log.w( "column %s is blob.", names.get( i ) );
|
|
break;
|
|
}
|
|
}
|
|
|
|
writer.endObject();
|
|
}
|
|
}finally{
|
|
cursor.close();
|
|
}
|
|
writer.endArray();
|
|
}
|
|
|
|
private static void importTable( JsonReader reader, String table, HashMap< Long, Long > id_map ) throws IOException{
|
|
SQLiteDatabase db = App1.getDB();
|
|
if( table.equals( SavedAccount.table ) ){
|
|
SavedAccount.onDBDelete( db );
|
|
SavedAccount.onDBCreate( db );
|
|
}
|
|
|
|
db.execSQL( "BEGIN TRANSACTION" );
|
|
try{
|
|
db.execSQL( "delete from " + table );
|
|
|
|
ContentValues cv = new ContentValues();
|
|
|
|
reader.beginArray();
|
|
while( reader.hasNext() ){
|
|
|
|
long old_id = - 1L;
|
|
cv.clear();
|
|
|
|
reader.beginObject();
|
|
while( reader.hasNext() ){
|
|
String name = reader.nextName();
|
|
|
|
if( BaseColumns._ID.equals( name ) ){
|
|
old_id = reader.nextLong();
|
|
continue;
|
|
}
|
|
|
|
// 一時的に存在したが現在のDBスキーマにはない項目は読み飛ばす
|
|
if( SavedAccount.table.equals( table ) &&
|
|
( "nickname".equals( name ) || "color".equals( name ) )
|
|
){
|
|
reader.skipValue();
|
|
continue;
|
|
}
|
|
|
|
JsonToken token = reader.peek();
|
|
switch( token ){
|
|
case NULL:
|
|
reader.skipValue();
|
|
cv.putNull( name );
|
|
break;
|
|
|
|
case BOOLEAN:
|
|
cv.put( name, reader.nextBoolean() ? 1 : 0 );
|
|
break;
|
|
|
|
case NUMBER:
|
|
cv.put( name, reader.nextLong() );
|
|
break;
|
|
|
|
case STRING:
|
|
cv.put( name, reader.nextString() );
|
|
break;
|
|
|
|
default:
|
|
reader.skipValue();
|
|
// 無視する
|
|
break;
|
|
}
|
|
}
|
|
reader.endObject();
|
|
long new_id = db.insertWithOnConflict( table, null, cv, SQLiteDatabase.CONFLICT_REPLACE );
|
|
if( new_id == - 1L ){
|
|
throw new RuntimeException( "importTable: invalid row_id" );
|
|
}
|
|
if( id_map != null ){
|
|
id_map.put( old_id, new_id );
|
|
}
|
|
}
|
|
reader.endArray();
|
|
db.execSQL( "COMMIT TRANSACTION" );
|
|
}catch( Throwable ex ){
|
|
ex.printStackTrace();
|
|
log.e( ex, "importTable failed." );
|
|
try{
|
|
db.execSQL( "ROLLBACK TRANSACTION" );
|
|
}catch( Throwable ignored ){
|
|
}
|
|
throw ex;
|
|
}
|
|
}
|
|
|
|
private static void writePref( JsonWriter writer, SharedPreferences pref ) throws IOException{
|
|
writer.beginObject();
|
|
for( Map.Entry< String, ? > entry : pref.getAll().entrySet() ){
|
|
String k = entry.getKey();
|
|
Object v = entry.getValue();
|
|
writer.name( k );
|
|
if( v == null ){
|
|
writer.nullValue();
|
|
}else if( v instanceof String ){
|
|
writer.value( (String) v );
|
|
}else if( v instanceof Boolean ){
|
|
writer.value( (Boolean) v );
|
|
}else if( v instanceof Number ){
|
|
writer.value( (Number) v );
|
|
}else{
|
|
throw new RuntimeException( String.format( Locale.JAPAN, "writePref. bad data type: Preference key =%s", k ) );
|
|
}
|
|
}
|
|
writer.endObject();
|
|
}
|
|
|
|
private static void importPref( JsonReader reader, SharedPreferences pref ) throws IOException{
|
|
SharedPreferences.Editor e = pref.edit();
|
|
reader.beginObject();
|
|
while( reader.hasNext() ){
|
|
String k = reader.nextName();
|
|
if( k == null ){
|
|
throw new RuntimeException( "importPref: name is null" );
|
|
}
|
|
JsonToken token = reader.peek();
|
|
if( token == JsonToken.NULL ){
|
|
reader.nextNull();
|
|
e.remove( k );
|
|
continue;
|
|
}
|
|
switch( k ){
|
|
// boolean
|
|
case Pref.KEY_DONT_CONFIRM_BEFORE_CLOSE_COLUMN:
|
|
case Pref.KEY_PRIOR_LOCAL_URL:
|
|
case Pref.KEY_DISABLE_FAST_SCROLLER:
|
|
case Pref.KEY_SIMPLE_LIST:
|
|
case Pref.KEY_NOTIFICATION_SOUND:
|
|
case Pref.KEY_NOTIFICATION_VIBRATION:
|
|
case Pref.KEY_NOTIFICATION_LED:
|
|
case Pref.KEY_EXIT_APP_WHEN_CLOSE_PROTECTED_COLUMN:
|
|
case Pref.KEY_SHOW_FOLLOW_BUTTON_IN_BUTTON_BAR:
|
|
case Pref.KEY_DONT_ROUND:
|
|
case Pref.KEY_DONT_USE_STREAMING:
|
|
case Pref.KEY_DONT_REFRESH_ON_RESUME:
|
|
case Pref.KEY_DONT_SCREEN_OFF:
|
|
case Pref.KEY_DISABLE_TABLET_MODE:
|
|
case Pref.KEY_DONT_CROP_MEDIA_THUMBNAIL:
|
|
boolean bv = reader.nextBoolean();
|
|
e.putBoolean( k, bv );
|
|
break;
|
|
|
|
// int
|
|
case Pref.KEY_BACK_BUTTON_ACTION:
|
|
case Pref.KEY_UI_THEME:
|
|
case Pref.KEY_RESIZE_IMAGE:
|
|
case Pref.KEY_REFRESH_AFTER_TOOT:
|
|
case Pref.KEY_FOOTER_BUTTON_BG_COLOR:
|
|
case Pref.KEY_FOOTER_BUTTON_FG_COLOR:
|
|
case Pref.KEY_FOOTER_TAB_BG_COLOR:
|
|
case Pref.KEY_FOOTER_TAB_DIVIDER_COLOR:
|
|
int iv = reader.nextInt();
|
|
e.putInt( k, iv );
|
|
break;
|
|
|
|
// string
|
|
case Pref.KEY_COLUMN_WIDTH:
|
|
case Pref.KEY_MEDIA_THUMB_HEIGHT:
|
|
String sv = reader.nextString();
|
|
e.putString( k, sv );
|
|
break;
|
|
|
|
// force reset
|
|
default:
|
|
case Pref.KEY_TIMELINE_FONT:
|
|
reader.skipValue();
|
|
e.remove( k );
|
|
break;
|
|
|
|
// just ignore
|
|
case Pref.KEY_DEVICE_TOKEN:
|
|
case Pref.KEY_INSTALL_ID:
|
|
reader.skipValue();
|
|
e.remove( k );
|
|
break;
|
|
}
|
|
}
|
|
reader.endObject();
|
|
e.apply();
|
|
}
|
|
|
|
private static void writeColumn( AppState app_state, JsonWriter writer ) throws IOException, JSONException{
|
|
writer.beginArray();
|
|
for( Column column : app_state.column_list ){
|
|
JSONObject dst = new JSONObject();
|
|
column.encodeJSON( dst, 0 );
|
|
writeJSONObject( writer, dst );
|
|
}
|
|
writer.endArray();
|
|
}
|
|
|
|
private static @NonNull
|
|
ArrayList< Column > readColumn( AppState app_state, JsonReader reader, HashMap< Long, Long > id_map ) throws IOException, JSONException{
|
|
ArrayList< Column > result = new ArrayList<>();
|
|
reader.beginArray();
|
|
while( reader.hasNext() ){
|
|
JSONObject item = readJsonObject( reader );
|
|
long old_id = item.optLong( Column.KEY_ACCOUNT_ROW_ID, - 1L );
|
|
Long new_id = id_map.get( old_id );
|
|
if( new_id == null ){
|
|
throw new RuntimeException( "readColumn: can't convert account id" );
|
|
}
|
|
item.put( Column.KEY_ACCOUNT_ROW_ID, (long) new_id );
|
|
try{
|
|
result.add( new Column( app_state, item ) );
|
|
}catch( Throwable ex ){
|
|
ex.printStackTrace();
|
|
log.e( ex, "column load failed." );
|
|
throw ex;
|
|
}
|
|
}
|
|
reader.endArray();
|
|
return result;
|
|
}
|
|
|
|
private static final String KEY_PREF = "pref";
|
|
private static final String KEY_ACCOUNT = "account";
|
|
private static final String KEY_COLUMN = "column";
|
|
private static final String KEY_ACCT_COLOR = "acct_color";
|
|
private static final String KEY_MUTED_APP = "muted_app";
|
|
private static final String KEY_MUTED_WORD = "muted_word";
|
|
|
|
static void encodeAppData( Context context, JsonWriter writer )
|
|
throws IOException, JSONException{
|
|
writer.setIndent( " " );
|
|
writer.beginObject();
|
|
|
|
AppState app_state = App1.getAppState( context );
|
|
//////////////////////////////////////
|
|
{
|
|
writer.name( KEY_PREF );
|
|
writePref( writer, app_state.pref );
|
|
}
|
|
//////////////////////////////////////
|
|
writeFromTable( writer, KEY_ACCOUNT, SavedAccount.table );
|
|
writeFromTable( writer, KEY_ACCT_COLOR, AcctColor.table );
|
|
writeFromTable( writer, KEY_MUTED_APP, MutedApp.table );
|
|
writeFromTable( writer, KEY_MUTED_WORD, MutedWord.table );
|
|
|
|
//////////////////////////////////////
|
|
{
|
|
writer.name( KEY_COLUMN );
|
|
writeColumn( app_state, writer );
|
|
|
|
}
|
|
|
|
writer.endObject();
|
|
}
|
|
|
|
static ArrayList< Column > decodeAppData( Context context, JsonReader reader )
|
|
throws IOException, JSONException{
|
|
|
|
ArrayList< Column > result = null;
|
|
|
|
AppState app_state = App1.getAppState( context );
|
|
reader.beginObject();
|
|
|
|
@SuppressLint("UseSparseArrays")
|
|
HashMap< Long, Long > account_id_map = new HashMap<>();
|
|
|
|
while( reader.hasNext() ){
|
|
String name = reader.nextName();
|
|
|
|
if( KEY_PREF.equals( name ) ){
|
|
importPref( reader, app_state.pref );
|
|
|
|
}else if( KEY_ACCOUNT.equals( name ) ){
|
|
importTable( reader, SavedAccount.table, account_id_map );
|
|
|
|
}else if( KEY_ACCT_COLOR.equals( name ) ){
|
|
importTable( reader, AcctColor.table, null );
|
|
AcctColor.clearMemoryCache();
|
|
}else if( KEY_MUTED_APP.equals( name ) ){
|
|
importTable( reader, MutedApp.table, null );
|
|
|
|
}else if( KEY_MUTED_WORD.equals( name ) ){
|
|
importTable( reader, MutedWord.table, null );
|
|
|
|
}else if( KEY_COLUMN.equals( name ) ){
|
|
result = readColumn( app_state, reader, account_id_map );
|
|
}
|
|
}
|
|
|
|
if( result == null ){
|
|
throw new RuntimeException( "import data does not includes column list!" );
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
}
|