package org.jetbrains.android.database; import com.intellij.openapi.components.*; import com.intellij.openapi.util.io.FileUtil; import com.intellij.util.containers.HashMap; import com.intellij.util.containers.HashSet; import com.intellij.util.xmlb.annotations.MapAnnotation; import com.intellij.util.xmlb.annotations.Tag; import org.jetbrains.android.util.AndroidUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.util.*; /** * @author Eugene.Kudelevsky */ @State( name = "AndroidRemoteDataBaseManager", storages = { @Storage( file = StoragePathMacros.APP_CONFIG + "/androidRemoteDatabases.xml" )} ) public class AndroidRemoteDataBaseManager implements PersistentStateComponent<AndroidRemoteDataBaseManager.State> { private State myState = new State(); public synchronized static AndroidRemoteDataBaseManager getInstance() { return ServiceManager.getService(AndroidRemoteDataBaseManager.class); } @NotNull public static String buildLocalDbFileOsPath(@Nullable String deviceId, @Nullable String packageName, @Nullable String databaseName, boolean external) { if (deviceId == null || packageName == null || databaseName == null || deviceId.length() == 0 || packageName.length() == 0 || databaseName.length() == 0) { return ""; } return AndroidUtils.getAndroidSystemDirectoryOsPath() + File.separator + "databases" + File.separator + deviceId + File.separator + packageName + File.separator + (external ? "external" : "internal") + File.separator + databaseName; } public synchronized void processRemovedProjects() { final List<MyDbKey> keysToRemove = new ArrayList<MyDbKey>(); for (Map.Entry<MyDbKey, MyDatabaseInfo> entry : myState.databases.entrySet()) { final MyDbKey key = entry.getKey(); final MyDatabaseInfo value = entry.getValue(); for (Iterator<String> it = value.referringProjects.iterator(); it.hasNext(); ) { final String projectBasePath = it.next(); if (!new File(projectBasePath).exists()) { it.remove(); keysToRemove.add(key); } } } removeUnusedDatabases(keysToRemove); } public synchronized void updateDbUsagesForProject(@NotNull String projectBasePath, @NotNull Set<AndroidRemoteDbInfo> usedDatabases) { final List<MyDbKey> keysToRemove = new ArrayList<MyDbKey>(); for (Map.Entry<MyDbKey, MyDatabaseInfo> entry : myState.databases.entrySet()) { final MyDbKey key = entry.getKey(); final MyDatabaseInfo value = entry.getValue(); if (value.referringProjects.contains(projectBasePath) && !usedDatabases.contains(new AndroidRemoteDbInfo(key.deviceId, key.packageName, key.databaseName, key.external))) { value.referringProjects.remove(projectBasePath); keysToRemove.add(key); } } removeUnusedDatabases(keysToRemove); } private synchronized void removeUnusedDatabases(@NotNull List<MyDbKey> keysToRemove) { final Map<MyDbKey, MyDatabaseInfo> dbMap = myState.databases; for (Iterator<MyDbKey> it = keysToRemove.iterator(); it.hasNext(); ) { final MyDbKey key = it.next(); final MyDatabaseInfo value = dbMap.get(key); if (value.referringProjects.isEmpty()) { it.remove(); final String localDbPath = buildLocalDbFileOsPath(key.deviceId, key.packageName, key.databaseName, key.external); final File localDbFile = new File(localDbPath); if (localDbFile.exists()) { FileUtil.delete(localDbFile); } } } } @Nullable public synchronized MyDatabaseInfo getDatabaseInfo(@NotNull String deviceId, @NotNull String packageName, @NotNull String databaseName, boolean external) { return myState.databases.get(new MyDbKey(deviceId, packageName, databaseName, external)); } public synchronized void setDatabaseInfo(@NotNull String deviceId, @NotNull String packageName, @NotNull String databaseName, @NotNull MyDatabaseInfo databaseInfo, boolean external) { myState.databases.put(new MyDbKey(deviceId, packageName, databaseName, external), databaseInfo); } @Override public synchronized State getState() { return myState; } @Override public synchronized void loadState(State state) { myState = state; } public static class State { @Tag("databases") @MapAnnotation(surroundWithTag = false) public Map<MyDbKey, MyDatabaseInfo> databases = new HashMap<MyDbKey, MyDatabaseInfo>(); } @Tag("db-key") public static class MyDbKey { public String deviceId = ""; public String packageName = ""; public String databaseName = ""; public boolean external; public MyDbKey(@NotNull String deviceId, @NotNull String packageName, @NotNull String databaseName, boolean external) { this.deviceId = deviceId; this.packageName = packageName; this.databaseName = databaseName; this.external = external; } public MyDbKey() { } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; MyDbKey key = (MyDbKey)o; if (external != key.external) return false; if (databaseName != null ? !databaseName.equals(key.databaseName) : key.databaseName != null) return false; if (deviceId != null ? !deviceId.equals(key.deviceId) : key.deviceId != null) return false; if (packageName != null ? !packageName.equals(key.packageName) : key.packageName != null) return false; return true; } @Override public int hashCode() { int result = deviceId != null ? deviceId.hashCode() : 0; result = 31 * result + (packageName != null ? packageName.hashCode() : 0); result = 31 * result + (databaseName != null ? databaseName.hashCode() : 0); result = 31 * result + (external ? 1 : 0); return result; } } @Tag("db-info") public static class MyDatabaseInfo { public long modificationTime = 0; public Set<String> referringProjects = new HashSet<String>(); } }