2017-08-06 16:03:36 +02:00
|
|
|
package me.impy.aegis.db;
|
|
|
|
|
|
|
|
import android.content.Context;
|
2017-12-10 19:19:48 +01:00
|
|
|
import android.os.Environment;
|
2017-08-06 16:03:36 +02:00
|
|
|
|
2018-02-13 22:06:24 +01:00
|
|
|
import org.json.JSONObject;
|
|
|
|
|
2017-12-10 19:19:48 +01:00
|
|
|
import java.io.DataInputStream;
|
|
|
|
import java.io.File;
|
|
|
|
import java.io.FileInputStream;
|
|
|
|
import java.io.FileOutputStream;
|
|
|
|
import java.io.IOException;
|
2018-12-11 11:44:36 +01:00
|
|
|
import java.text.Collator;
|
2017-08-06 16:03:36 +02:00
|
|
|
import java.util.List;
|
2018-12-11 11:44:36 +01:00
|
|
|
import java.util.TreeSet;
|
2018-09-22 14:12:42 +02:00
|
|
|
import java.util.UUID;
|
2017-08-06 16:03:36 +02:00
|
|
|
|
2017-12-12 12:25:55 +01:00
|
|
|
import me.impy.aegis.BuildConfig;
|
2018-10-09 23:13:51 +02:00
|
|
|
import me.impy.aegis.R;
|
2017-08-06 16:03:36 +02:00
|
|
|
|
|
|
|
public class DatabaseManager {
|
2018-02-13 22:06:24 +01:00
|
|
|
private static final String FILENAME = "aegis.json";
|
|
|
|
private static final String FILENAME_EXPORT = "aegis_export.json";
|
|
|
|
private static final String FILENAME_EXPORT_PLAIN = "aegis_export_plain.json";
|
2017-12-04 22:08:50 +01:00
|
|
|
|
2017-08-06 16:03:36 +02:00
|
|
|
private Database _db;
|
2018-10-06 22:23:38 +02:00
|
|
|
private DatabaseFile _file;
|
|
|
|
private DatabaseFileCredentials _creds;
|
|
|
|
private boolean _encrypt;
|
|
|
|
|
2017-08-06 16:03:36 +02:00
|
|
|
private Context _context;
|
|
|
|
|
|
|
|
public DatabaseManager(Context context) {
|
|
|
|
_context = context;
|
|
|
|
}
|
|
|
|
|
2017-12-25 00:16:24 +01:00
|
|
|
public boolean fileExists() {
|
|
|
|
File file = new File(_context.getFilesDir(), FILENAME);
|
|
|
|
return file.exists() && file.isFile();
|
|
|
|
}
|
|
|
|
|
2018-03-19 18:00:53 +01:00
|
|
|
public void load() throws DatabaseManagerException {
|
2017-12-24 22:29:32 +01:00
|
|
|
assertState(true, false);
|
|
|
|
|
2018-10-06 22:23:38 +02:00
|
|
|
try (FileInputStream file = _context.openFileInput(FILENAME)) {
|
|
|
|
byte[] fileBytes = new byte[(int) file.getChannel().size()];
|
|
|
|
DataInputStream stream = new DataInputStream(file);
|
|
|
|
stream.readFully(fileBytes);
|
|
|
|
stream.close();
|
2017-12-10 19:19:48 +01:00
|
|
|
|
2018-10-06 22:23:38 +02:00
|
|
|
_file = DatabaseFile.fromBytes(fileBytes);
|
|
|
|
_encrypt = _file.isEncrypted();
|
|
|
|
if (!isEncryptionEnabled()) {
|
2018-03-19 18:00:53 +01:00
|
|
|
JSONObject obj = _file.getContent();
|
2018-10-06 22:23:38 +02:00
|
|
|
_db = Database.fromJson(obj);
|
2018-03-19 18:00:53 +01:00
|
|
|
}
|
|
|
|
} catch (IOException | DatabaseFileException | DatabaseException e) {
|
|
|
|
throw new DatabaseManagerException(e);
|
2017-08-06 16:03:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-19 18:00:53 +01:00
|
|
|
public void lock() {
|
2017-12-24 22:29:32 +01:00
|
|
|
assertState(false, true);
|
2018-10-06 22:23:38 +02:00
|
|
|
_creds = null;
|
2017-12-23 22:33:32 +01:00
|
|
|
_db = null;
|
|
|
|
}
|
|
|
|
|
2018-10-06 22:23:38 +02:00
|
|
|
public void unlock(DatabaseFileCredentials creds) throws DatabaseManagerException {
|
2017-12-24 22:29:32 +01:00
|
|
|
assertState(true, true);
|
2017-08-06 16:03:36 +02:00
|
|
|
|
2018-03-19 18:00:53 +01:00
|
|
|
try {
|
2018-10-06 22:23:38 +02:00
|
|
|
JSONObject obj = _file.getContent(creds);
|
|
|
|
_db = Database.fromJson(obj);
|
|
|
|
_creds = creds;
|
2018-03-19 18:00:53 +01:00
|
|
|
} catch (DatabaseFileException | DatabaseException e) {
|
|
|
|
throw new DatabaseManagerException(e);
|
|
|
|
}
|
|
|
|
}
|
2017-12-10 19:19:48 +01:00
|
|
|
|
2018-03-19 18:00:53 +01:00
|
|
|
public static void save(Context context, DatabaseFile file) throws DatabaseManagerException {
|
2018-10-06 22:23:38 +02:00
|
|
|
byte[] bytes = file.toBytes();
|
|
|
|
try (FileOutputStream stream = context.openFileOutput(FILENAME, Context.MODE_PRIVATE)) {
|
|
|
|
stream.write(bytes);
|
2018-06-06 16:15:31 +02:00
|
|
|
} catch (IOException e) {
|
2018-03-19 18:00:53 +01:00
|
|
|
throw new DatabaseManagerException(e);
|
2017-12-10 19:19:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-19 18:00:53 +01:00
|
|
|
public void save() throws DatabaseManagerException {
|
2017-12-24 22:29:32 +01:00
|
|
|
assertState(false, true);
|
2018-03-19 18:00:53 +01:00
|
|
|
|
|
|
|
try {
|
2018-10-06 22:23:38 +02:00
|
|
|
JSONObject obj = _db.toJson();
|
|
|
|
if (isEncryptionEnabled()) {
|
|
|
|
_file.setContent(obj, _creds);
|
2018-03-19 18:00:53 +01:00
|
|
|
} else {
|
|
|
|
_file.setContent(obj);
|
|
|
|
}
|
|
|
|
save(_context, _file);
|
2018-06-06 16:15:31 +02:00
|
|
|
} catch (DatabaseFileException e) {
|
2018-03-19 18:00:53 +01:00
|
|
|
throw new DatabaseManagerException(e);
|
2017-12-10 19:19:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-19 18:00:53 +01:00
|
|
|
public String export(boolean encrypt) throws DatabaseManagerException {
|
2017-12-24 22:29:32 +01:00
|
|
|
assertState(false, true);
|
2018-03-13 18:30:47 +01:00
|
|
|
|
2017-12-10 19:19:48 +01:00
|
|
|
try {
|
2018-03-19 18:00:53 +01:00
|
|
|
DatabaseFile dbFile = new DatabaseFile();
|
2018-10-06 22:23:38 +02:00
|
|
|
if (encrypt && isEncryptionEnabled()) {
|
|
|
|
dbFile.setContent(_db.toJson(), _creds);
|
2018-03-19 18:00:53 +01:00
|
|
|
} else {
|
2018-10-06 22:23:38 +02:00
|
|
|
dbFile.setContent(_db.toJson());
|
2017-12-10 19:19:48 +01:00
|
|
|
}
|
|
|
|
|
2018-10-09 23:13:51 +02:00
|
|
|
String dirName = !BuildConfig.DEBUG ? _context.getString(R.string.app_name) : _context.getString(R.string.app_name_dev);
|
2018-10-06 22:23:38 +02:00
|
|
|
File dir = new File(Environment.getExternalStorageDirectory(), dirName);
|
|
|
|
if (!dir.exists() && !dir.mkdirs()) {
|
|
|
|
throw new IOException("error creating external storage directory");
|
|
|
|
}
|
|
|
|
|
|
|
|
byte[] bytes = dbFile.toBytes();
|
|
|
|
File file = new File(dir.getAbsolutePath(), encrypt ? FILENAME_EXPORT : FILENAME_EXPORT_PLAIN);
|
|
|
|
try (FileOutputStream stream = new FileOutputStream(file)) {
|
2018-03-19 18:00:53 +01:00
|
|
|
stream.write(bytes);
|
2017-12-10 19:19:48 +01:00
|
|
|
}
|
|
|
|
|
2018-03-19 18:00:53 +01:00
|
|
|
return file.getAbsolutePath();
|
2018-06-06 16:15:31 +02:00
|
|
|
} catch (IOException | DatabaseFileException e) {
|
2018-03-19 18:00:53 +01:00
|
|
|
throw new DatabaseManagerException(e);
|
|
|
|
}
|
2017-08-06 16:03:36 +02:00
|
|
|
}
|
|
|
|
|
2018-06-06 16:15:31 +02:00
|
|
|
public void addEntry(DatabaseEntry entry) {
|
2017-12-24 22:29:32 +01:00
|
|
|
assertState(false, true);
|
2018-06-06 16:15:31 +02:00
|
|
|
_db.addEntry(entry);
|
2017-08-06 16:03:36 +02:00
|
|
|
}
|
|
|
|
|
2018-06-06 16:15:31 +02:00
|
|
|
public void removeEntry(DatabaseEntry entry) {
|
2017-12-24 22:29:32 +01:00
|
|
|
assertState(false, true);
|
2018-06-06 16:15:31 +02:00
|
|
|
_db.removeEntry(entry);
|
2017-08-06 16:03:36 +02:00
|
|
|
}
|
|
|
|
|
2018-06-06 16:15:31 +02:00
|
|
|
public void replaceEntry(DatabaseEntry entry) {
|
2017-12-27 22:04:22 +01:00
|
|
|
assertState(false, true);
|
2018-06-06 16:15:31 +02:00
|
|
|
_db.replaceEntry(entry);
|
2017-12-27 22:04:22 +01:00
|
|
|
}
|
|
|
|
|
2018-06-06 16:15:31 +02:00
|
|
|
public void swapEntries(DatabaseEntry entry1, DatabaseEntry entry2) {
|
2017-12-24 22:29:32 +01:00
|
|
|
assertState(false, true);
|
2018-06-06 16:15:31 +02:00
|
|
|
_db.swapEntries(entry1, entry2);
|
2017-12-12 01:50:00 +01:00
|
|
|
}
|
|
|
|
|
2018-06-06 16:15:31 +02:00
|
|
|
public List<DatabaseEntry> getEntries() {
|
2017-12-24 22:29:32 +01:00
|
|
|
assertState(false, true);
|
2018-06-06 16:15:31 +02:00
|
|
|
return _db.getEntries();
|
2017-08-06 16:03:36 +02:00
|
|
|
}
|
|
|
|
|
2018-09-22 14:12:42 +02:00
|
|
|
public DatabaseEntry getEntryByUUID(UUID uuid) {
|
|
|
|
assertState(false, true);
|
|
|
|
return _db.getEntryByUUID(uuid);
|
|
|
|
}
|
|
|
|
|
2018-12-11 11:44:36 +01:00
|
|
|
public TreeSet<String> getGroups() {
|
|
|
|
TreeSet<String> groups = new TreeSet<>(Collator.getInstance());
|
|
|
|
for (DatabaseEntry entry : getEntries()) {
|
|
|
|
String group = entry.getGroup();
|
|
|
|
if (group != null) {
|
|
|
|
groups.add(group);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return groups;
|
|
|
|
}
|
|
|
|
|
2018-12-16 22:57:04 +01:00
|
|
|
public void removeGroup(String groupName) {
|
|
|
|
TreeSet<String> groups = new TreeSet<>(Collator.getInstance());
|
|
|
|
for (DatabaseEntry entry : getEntries()) {
|
|
|
|
String group = entry.getGroup();
|
|
|
|
if (group != null && group.equals(groupName)) {
|
|
|
|
entry.setGroup(null);
|
|
|
|
_db.replaceEntry(entry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-06 22:23:38 +02:00
|
|
|
public DatabaseFileCredentials getCredentials() {
|
2018-02-09 17:31:07 +01:00
|
|
|
assertState(false, true);
|
2018-10-06 22:23:38 +02:00
|
|
|
return _creds;
|
2018-02-09 17:31:07 +01:00
|
|
|
}
|
|
|
|
|
2018-10-06 22:23:38 +02:00
|
|
|
public void setCredentials(DatabaseFileCredentials creds) {
|
|
|
|
assertState(false, true);
|
|
|
|
_creds = creds;
|
2017-08-06 18:15:47 +02:00
|
|
|
}
|
|
|
|
|
2018-10-06 22:23:38 +02:00
|
|
|
public DatabaseFile.Header getFileHeader() {
|
|
|
|
assertLoaded(true);
|
|
|
|
return _file.getHeader();
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isEncryptionEnabled() {
|
|
|
|
assertLoaded(true);
|
|
|
|
return _encrypt;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void enableEncryption(DatabaseFileCredentials creds) throws DatabaseManagerException {
|
2018-05-14 16:53:27 +02:00
|
|
|
assertState(false, true);
|
2018-10-06 22:23:38 +02:00
|
|
|
_creds = creds;
|
|
|
|
_encrypt = true;
|
|
|
|
save();
|
2018-05-14 16:53:27 +02:00
|
|
|
}
|
|
|
|
|
2018-10-06 22:23:38 +02:00
|
|
|
public void disableEncryption() throws DatabaseManagerException {
|
2018-05-14 16:53:27 +02:00
|
|
|
assertState(false, true);
|
2018-10-06 22:23:38 +02:00
|
|
|
_creds = null;
|
|
|
|
_encrypt = false;
|
|
|
|
save();
|
2018-05-14 16:53:27 +02:00
|
|
|
}
|
|
|
|
|
2017-08-06 16:03:36 +02:00
|
|
|
public boolean isLoaded() {
|
|
|
|
return _file != null;
|
|
|
|
}
|
|
|
|
|
2017-12-24 22:29:32 +01:00
|
|
|
public boolean isLocked() {
|
|
|
|
return _db == null;
|
2017-08-06 16:03:36 +02:00
|
|
|
}
|
|
|
|
|
2018-03-19 18:00:53 +01:00
|
|
|
private void assertState(boolean locked, boolean loaded) {
|
2018-10-06 22:23:38 +02:00
|
|
|
assertLoaded(loaded);
|
2017-08-06 16:03:36 +02:00
|
|
|
|
2017-12-24 22:29:32 +01:00
|
|
|
if (isLocked() && !locked) {
|
2018-03-19 18:00:53 +01:00
|
|
|
throw new AssertionError("database file has not been unlocked yet");
|
2017-12-24 22:29:32 +01:00
|
|
|
} else if (!isLocked() && locked) {
|
2018-10-06 22:23:38 +02:00
|
|
|
throw new AssertionError("database file has already been unlocked");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void assertLoaded(boolean loaded) {
|
|
|
|
if (isLoaded() && !loaded) {
|
|
|
|
throw new AssertionError("database file has already been loaded");
|
|
|
|
} else if (!isLoaded() && loaded) {
|
|
|
|
throw new AssertionError("database file has not been loaded yet");
|
2017-08-06 16:03:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|