/*
* This source is part of the
* _____ ___ ____
* __ / / _ \/ _ | / __/___ _______ _
* / // / , _/ __ |/ _/_/ _ \/ __/ _ `/
* \___/_/|_/_/ |_/_/ (_)___/_/ \_, /
* /___/
* repository.
*
* Copyright (C) 2014-2017 Carmen Alvarez (c@rmen.ca)
*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 ca.rmen.android.networkmonitor.app.dbops.backend.imp0rt;
import android.content.ContentProviderOperation;
import android.content.ContentProviderOperation.Builder;
import android.content.Context;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.os.RemoteException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import ca.rmen.android.networkmonitor.Constants;
import ca.rmen.android.networkmonitor.R;
import ca.rmen.android.networkmonitor.app.dbops.ProgressListener;
import ca.rmen.android.networkmonitor.app.dbops.backend.DBOperation;
import ca.rmen.android.networkmonitor.app.dbops.ui.Share;
import ca.rmen.android.networkmonitor.provider.NetMonColumns;
import ca.rmen.android.networkmonitor.provider.NetMonProvider;
import ca.rmen.android.networkmonitor.util.IoUtil;
import ca.rmen.android.networkmonitor.util.Log;
/**
* Replace the contents of the current database with the contents of another database.
* This is based on DBImport from the scrum chatter project.
*/
public class DBImport implements DBOperation {
private static final String TAG = Constants.TAG + DBImport.class.getSimpleName();
private final Context mContext;
private final Uri mUri;
private final AtomicBoolean mIsCanceled = new AtomicBoolean(false);
public DBImport(Context context, Uri uri) {
mContext = context;
mUri = uri;
}
/**
* Replace the database of our app with the contents of the database found at the given uri.
*/
@Override
public void execute(ProgressListener listener) {
try {
if (mUri.getScheme().equals("file")) {
File db = new File(mUri.getEncodedPath());
importDB(db, listener);
} else {
InputStream is = mContext.getContentResolver().openInputStream(mUri);
File tempDb = new File(mContext.getCacheDir(), "temp" + System.currentTimeMillis() + ".db");
FileOutputStream os = new FileOutputStream(tempDb);
if (IoUtil.copy(is, os) > 0) {
importDB(tempDb, listener);
//noinspection ResultOfMethodCallIgnored
tempDb.delete();
}
}
} catch (RemoteException | OperationApplicationException | SQLException | IOException e) {
Log.w(TAG, "Error importing the db: " + e.getMessage(), e);
}
}
@Override
public void cancel() {
mIsCanceled.set(true);
}
/**
* In a single database transaction, delete all the cells from the current database, read the data from the given importDb file, create a batch of
* corresponding insert operations, and execute the inserts.
*/
private void importDB(File importDb, ProgressListener listener) throws RemoteException, OperationApplicationException {
Log.v(TAG, "importDB from " + importDb);
SQLiteDatabase dbImport = SQLiteDatabase.openDatabase(importDb.getAbsolutePath(), null, SQLiteDatabase.OPEN_READONLY);
ArrayList<ContentProviderOperation> operations = new ArrayList<>();
operations.add(ContentProviderOperation.newDelete(NetMonColumns.CONTENT_URI).build());
Uri insertUri = new Uri.Builder().authority(NetMonProvider.AUTHORITY).appendPath(NetMonColumns.TABLE_NAME)
.appendQueryParameter(NetMonProvider.QUERY_PARAMETER_NOTIFY, "false").build();
buildInsertOperations(dbImport, insertUri, operations, listener);
dbImport.close();
}
/**
* Read all cells from the given table from the dbImport database, and add corresponding insert operations to the operations parameter.
*
* @throws OperationApplicationException
* @throws RemoteException
*/
private void buildInsertOperations(SQLiteDatabase dbImport, Uri uri, ArrayList<ContentProviderOperation> operations, ProgressListener listener)
throws RemoteException, OperationApplicationException {
Log.v(TAG, "buildInsertOperations: uri = " + uri);
try {
Cursor c = dbImport.query(false, NetMonColumns.TABLE_NAME, null, null, null, null, null, null, null);
if (c != null) {
try {
int count = c.getCount();
if (c.moveToFirst()) {
int columnCount = c.getColumnCount();
do {
Builder builder = ContentProviderOperation.newInsert(uri);
for (int i = 0; i < columnCount; i++) {
String columnName = c.getColumnName(i);
Object value = c.getString(i);
builder.withValue(columnName, value);
}
operations.add(builder.build());
if (operations.size() >= 100) {
mContext.getContentResolver().applyBatch(NetMonProvider.AUTHORITY, operations);
operations.clear();
}
if (listener != null) listener.onProgress(c.getPosition(), count);
} while (c.moveToNext() && !mIsCanceled.get());
if (operations.size() > 0 && !mIsCanceled.get())
mContext.getContentResolver().applyBatch(NetMonProvider.AUTHORITY, operations);
}
if (listener != null) {
if (mIsCanceled.get())
listener.onError(mContext.getString(R.string.import_notif_canceled_content));
else
listener.onComplete(mContext.getString(R.string.import_notif_complete_content, Share.readDisplayName(mContext, mUri)));
}
return;
} finally {
c.close();
}
}
} catch (SQLiteException e) {
Log.w(TAG, "Couldn't import database " + mUri, e);
}
if (listener != null) listener.onError(mContext.getString(R.string.import_notif_error_content, Share.readDisplayName(mContext, uri)));
}
}