package com.gettingmobile.google.reader.sync;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import com.gettingmobile.goodnews.storage.StorageProvider;
import com.gettingmobile.google.reader.ElementId;
import com.gettingmobile.google.reader.Item;
import com.gettingmobile.google.reader.db.ItemDatabaseAdapter;
import com.gettingmobile.io.Base64;
import com.gettingmobile.io.IOUtils;
import com.gettingmobile.io.SimpleFileFilter;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
public class ItemFileCleanup {
private static final String LOG_TAG = "goodnews.ItemFileCleanup";
private final FileFilter dirFilter = new SimpleFileFilter(false, true, true);
private final ItemDatabaseAdapter dbAdapter = new ItemDatabaseAdapter();
private final SQLiteDatabase db;
private final StorageProvider storageProvider;
public ItemFileCleanup(SQLiteDatabase db, StorageProvider storageProvider) {
this.db = db;
this.storageProvider = storageProvider;
}
public void cleanup() {
Log.d(LOG_TAG, "starting cleanup");
if (storageProvider == null) {
Log.d(LOG_TAG, "nothing to cleanup (no storage provider set)");
return;
}
/*
* get the directory storing the content and check whether it is existing
*/
/*
* get the names of the directory we need to keep
*/
final Set<ElementId> ids = dbAdapter.readAllIds(db);
final Set<String> requiredDirNames = new HashSet<String>(ids.size());
for (ElementId id : ids) {
requiredDirNames.add(Item.getDirectoryName(id));
}
/*
* iterate through all directories and check whether they are required or not.
*
* We may encounter an OutOfMemoryException if we use the old (flat) directory naming and have a lot of files.
* That's why we loop based on the start character
*/
final File dir = storageProvider.getDirectory(Item.STORAGE_CATEGORY);
if (dir.exists()) {
for (char c : Base64.ALPHABET) {
if (c == '/') {
c = '-';
}
final File[] subdirs = dir.listFiles(new DirFilter(c));
if (subdirs != null) {
for (File d : subdirs) {
deleteDirectoryIfApplicable(requiredDirNames, storageProvider, d, true);
}
}
}
}
Log.d(LOG_TAG, "cleanup done");
}
private void deleteDirectoryIfApplicable(Set<String> requiredDirNames, StorageProvider storageProvider, File dir, boolean isAtRootLevel) {
Log.d(LOG_TAG, "Looking at directory " + dir);
if (dir.getName().length() == 1) {
/*
* handle new tree like directory structure and recurse into sub directories
*/
final File[] subdirs = dir.listFiles(dirFilter);
if (subdirs != null) {
for (File d : subdirs) {
deleteDirectoryIfApplicable(requiredDirNames, storageProvider, d, false);
}
}
} else if (isAtRootLevel) {
/*
* handle old styled base64 encoded directory name
*/
try {
final String newDirName = Item.convertFromOldDirectoryName(dir.getName());
Log.d(LOG_TAG, "Converted directory name is " + newDirName);
if (!requiredDirNames.contains(newDirName)) {
deleteDirectory(dir);
} else {
final File newDir = Item.getDirectory(storageProvider, newDirName);
//noinspection ResultOfMethodCallIgnored
newDir.getParentFile().mkdirs();
//noinspection ResultOfMethodCallIgnored
dir.renameTo(newDir);
}
} catch (IOException ex) {
Log.w(LOG_TAG, "Failed to convert old directory name to new: " + dir.getName());
}
} else if (!requiredDirNames.contains(dir.getName())) {
/*
* delete leafe-directories
*/
deleteDirectory(dir);
}
}
private void deleteDirectory(File dir) {
try {
Log.d(LOG_TAG, "Deleting directory " + dir);
IOUtils.deleteRecursive(dir);
} catch (IOException ex) {
Log.w(LOG_TAG, "Failed to delete content directory " + dir);
}
}
/*
* inner classes
*/
static final class DirFilter extends SimpleFileFilter {
private final char startChar;
DirFilter(char startChar) {
super(false, true, true);
this.startChar = startChar;
}
@Override
public boolean accept(File f) {
return super.accept(f) && f.getName().charAt(0) == startChar;
}
}
}