diff --git a/src/org/kiwix/kiwixmobile/DataModel.java b/src/org/kiwix/kiwixmobile/DataModel.java new file mode 100644 index 000000000..3de703026 --- /dev/null +++ b/src/org/kiwix/kiwixmobile/DataModel.java @@ -0,0 +1,85 @@ +package org.kiwix.kiwixmobile; + +import android.os.Parcel; +import android.os.Parcelable; + +// This items class stores the Data for the ArrayAdapter. +// We Have to implement Parcelable, so we can store ArrayLists with this generic type in the Bundle +// of onSaveInstanceState() and retrieve it later on in onRestoreInstanceState() +public class DataModel implements Parcelable { + + // Interface that must be implemented and provided as a public CREATOR field. + // It generates instances of our Parcelable class from a Parcel. + public Creator CREATOR = new Creator() { + + @Override + public DataModel createFromParcel(Parcel source) { + return new DataModel(source); + } + + @Override + public boolean equals(Object o) { + return super.equals(o); + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public DataModel[] newArray(int size) { + return new DataModel[size]; + } + }; + + private String mTitle; + + private String mPath; + + public DataModel(String title, String path) { + mTitle = title; + mPath = path; + } + + // This constructor will be called when this class is generated by a Parcel. + // We have to read the previously written Data in this Parcel. + public DataModel(Parcel parcel) { + String[] data = new String[2]; + parcel.readStringArray(data); + mTitle = data[0]; + mTitle = data[1]; + } + + public String getTitle() { + return mTitle; + } + + public String getPath() { + return mPath; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + // Write the data to the Parcel, so we can restore this Data later on. + // It will be restored by the DataModel(Parcel parcel) constructor. + dest.writeArray(new String[]{mTitle, mPath}); + } + + // Override equals(Object) so we can compare objects. Specifically, so List#contains() works. + @Override + public boolean equals(Object object) { + boolean isEqual = false; + + if (object != null && object instanceof DataModel) { + isEqual = (this.mPath.equals(((DataModel) object).mPath)); + } + + return isEqual; + } +} diff --git a/src/org/kiwix/kiwixmobile/FileSearch.java b/src/org/kiwix/kiwixmobile/FileSearch.java new file mode 100644 index 000000000..1cf3e0a53 --- /dev/null +++ b/src/org/kiwix/kiwixmobile/FileSearch.java @@ -0,0 +1,109 @@ +package org.kiwix.kiwixmobile; + + +import android.os.Environment; + +import java.io.File; +import java.io.FilenameFilter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Vector; + +public class FileSearch { + + // Array of zim file extensions + public static final String[] zimFiles = {"zim", "zimaa"}; + + // Scan through the file system and find all the files with .zim and .zimaa extensions + public ArrayList findFiles() { + String directory = new File( + Environment.getExternalStorageDirectory().getAbsolutePath()).toString(); + final List fileList = new ArrayList(); + FilenameFilter[] filter = new FilenameFilter[zimFiles.length]; + + int i = 0; + for (final String extension : zimFiles) { + filter[i] = new FilenameFilter() { + public boolean accept(File dir, String name) { + return name.endsWith("." + extension); + } + }; + i++; + } + + File[] foundFiles = listFilesAsArray(new File(directory), filter, -1); + for (File f : foundFiles) { + fileList.add(f.getAbsolutePath()); + } + + return createDataForAdapter(fileList); + } + + public ArrayList sortDataModel(ArrayList data) { + + // Sorting the data in alphabetical order + Collections.sort(data, new Comparator() { + @Override + public int compare(DataModel a, DataModel b) { + return a.getTitle().compareToIgnoreCase(b.getTitle()); + } + }); + + return data; + } + + // Iterate through the file system + private Collection listFiles(File directory, FilenameFilter[] filter, int recurse) { + + Vector files = new Vector(); + + File[] entries = directory.listFiles(); + + if (entries != null) { + for (File entry : entries) { + for (FilenameFilter filefilter : filter) { + if (filter == null || filefilter.accept(directory, entry.getName())) { + files.add(entry); + } + } + if ((recurse <= -1) || (recurse > 0 && entry.isDirectory())) { + recurse--; + files.addAll(listFiles(entry, filter, recurse)); + recurse++; + } + } + } + return files; + } + + private File[] listFilesAsArray(File directory, FilenameFilter[] filter, int recurse) { + Collection files = listFiles(directory, filter, recurse); + + File[] arr = new File[files.size()]; + return files.toArray(arr); + } + + // Create an ArrayList with our DataModel + private ArrayList createDataForAdapter(List list) { + + ArrayList data = new ArrayList(); + for (String file : list) { + + data.add(new DataModel(getTitleFromFilePath(file), file)); + } + + data = sortDataModel(data); + + return data; + } + + // Remove the file path and the extension and return a file name for the given file path + private String getTitleFromFilePath(String path) { + return new File(path).getName().replaceFirst("[.][^.]+$", ""); + } + + +} diff --git a/src/org/kiwix/kiwixmobile/FileWriter.java b/src/org/kiwix/kiwixmobile/FileWriter.java index a26578da4..748c04b7c 100644 --- a/src/org/kiwix/kiwixmobile/FileWriter.java +++ b/src/org/kiwix/kiwixmobile/FileWriter.java @@ -16,23 +16,23 @@ public class FileWriter { private Context mContext; - private ArrayList mDataList; + private ArrayList mDataList; public FileWriter(Context context) { mContext = context; } - public FileWriter(Context context, ArrayList dataList) { + public FileWriter(Context context, ArrayList dataList) { mDataList = dataList; mContext = context; } // Build a CSV list from the file paths - public void saveArray(ArrayList files) { + public void saveArray(ArrayList files) { ArrayList list = new ArrayList(); - for (ZimFileSelectActivity.DataModel file : files) { + for (DataModel file : files) { list.add(file.getPath()); } @@ -48,12 +48,12 @@ public class FileWriter { // Add items to the MediaStore list, that are not in the MediaStore database. // These will be loaded from a previously saved CSV file. // We are checking, if these file still exist as well. - public ArrayList getDataModelList() { + public ArrayList getDataModelList() { for (String file : readCsv()) { - if (!mDataList.contains(new ZimFileSelectActivity.DataModel(getTitleFromFilePath(file), file))) { + if (!mDataList.contains(new DataModel(getTitleFromFilePath(file), file))) { Log.i("kiwix", "Added file: " + file); - mDataList.add(new ZimFileSelectActivity.DataModel(getTitleFromFilePath(file), file)); + mDataList.add(new DataModel(getTitleFromFilePath(file), file)); } } diff --git a/src/org/kiwix/kiwixmobile/ZimFileSelectActivity.java b/src/org/kiwix/kiwixmobile/ZimFileSelectActivity.java index 1dbfeafb1..a52cca3ec 100644 --- a/src/org/kiwix/kiwixmobile/ZimFileSelectActivity.java +++ b/src/org/kiwix/kiwixmobile/ZimFileSelectActivity.java @@ -3,13 +3,9 @@ package org.kiwix.kiwixmobile; import android.content.Context; import android.content.Intent; import android.database.Cursor; -import android.media.MediaScannerConnection; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; -import android.os.Environment; -import android.os.Parcel; -import android.os.Parcelable; import android.provider.MediaStore; import android.support.v4.app.FragmentActivity; import android.support.v4.app.LoaderManager; @@ -31,22 +27,14 @@ import android.widget.ProgressBar; import android.widget.TextView; import java.io.File; -import java.io.FilenameFilter; import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; import java.util.List; -import java.util.Vector; public class ZimFileSelectActivity extends FragmentActivity implements LoaderManager.LoaderCallbacks, OnItemClickListener { private static final int LOADER_ID = 0x02; - // Array of zim file extensions - private static final String[] zimFiles = {"zim", "zimaa"}; - // Adapter of the Data populated by the MediaStore private SimpleCursorAdapter mCursorAdapter; @@ -61,10 +49,6 @@ public class ZimFileSelectActivity extends FragmentActivity private TextView mProgressBarMessage; - private boolean mNeedsUpdate = false; - - private boolean mAdapterRefreshed = false; - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -72,64 +56,11 @@ public class ZimFileSelectActivity extends FragmentActivity mProgressBar = (ProgressBar) findViewById(R.id.progressBar); mProgressBarMessage = (TextView) findViewById(R.id.progressbar_message); - mProgressBar.setVisibility(View.VISIBLE); - mZimFileList = (ListView) findViewById(R.id.zimfilelist); mFiles = new ArrayList(); - selectZimFile(); - } - - private void finishResult(String path) { - // Add new files to MediaStore - addDataToMediaStore(mFiles); - // Remove the nonexistent files from the MediaStore - removeNonExistentFiles(mCursorAdapter.getCursor()); - if (path != null) { - File file = new File(path); - Uri uri = Uri.fromFile(file); - Log.i("kiwix", "Opening " + uri); - setResult(RESULT_OK, new Intent().setData(uri)); - finish(); - } else { - setResult(RESULT_CANCELED); - finish(); - } - } - - protected void selectZimFile() { - - // Stop endless loops - if (mAdapterRefreshed) { - return; - } else { - mAdapterRefreshed = true; - } - - // Defines a list of columns to retrieve from the Cursor and load into an output row - String[] mZimListColumns = {MediaStore.Files.FileColumns.TITLE, MediaStore.Files.FileColumns.DATA}; - - // Defines a list of View IDs that will receive the Cursor columns for each row - int[] mZimListItems = {android.R.id.text1, android.R.id.text2}; - - mCursorAdapter = new SimpleCursorAdapter( - // The Context object - ZimFileSelectActivity.this, - // A layout in XML for one row in the ListView - android.R.layout.simple_list_item_2, - // The cursor, swapped later by cursorloader - null, - // A string array of column names in the cursor - mZimListColumns, - // An integer array of view IDs in the row layout - mZimListItems, - // Flags for the Adapter - Adapter.NO_SELECTION); - - mZimFileList.setOnItemClickListener(this); - - getSupportLoaderManager().initLoader(LOADER_ID, null, this); + startQuery(); } @Override @@ -150,9 +81,9 @@ public class ZimFileSelectActivity extends FragmentActivity String query = MediaStore.Files.FileColumns.MEDIA_TYPE + "=" + MediaStore.Files.FileColumns.MEDIA_TYPE_NONE + " AND" + " ( LOWER(" + - MediaStore.Images.Media.DATA + ") LIKE '%." + zimFiles[0] + "'" + MediaStore.Images.Media.DATA + ") LIKE '%." + FileSearch.zimFiles[0] + "'" + " OR LOWER(" + - MediaStore.Images.Media.DATA + ") LIKE '%." + zimFiles[1] + "'" + MediaStore.Images.Media.DATA + ") LIKE '%." + FileSearch.zimFiles[1] + "'" + " ) "; String[] selectionArgs = null; // There is no ? in query so null here @@ -181,98 +112,6 @@ public class ZimFileSelectActivity extends FragmentActivity mCursorAdapter.notifyDataSetChanged(); } - // Get the data of our cursor and wrap it all in our ArrayAdapter. - // We are doing this because the CursorAdapter does not allow us do remove rows from its dataset. - private RescanDataAdapter buildArrayAdapter(Cursor cursor) { - - ArrayList files = new ArrayList(); - - for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { - - if (new File(cursor.getString(2)).exists()) { - files.add(new DataModel(cursor.getString(1), cursor.getString(2))); - } - } - - files = new FileWriter(ZimFileSelectActivity.this, files).getDataModelList(); - - for (int i = 0; i < files.size(); i++) { - - if (!new File(files.get(i).getPath()).exists()) { - Log.e("kiwix", "File removed: " + files.get(i).getTitle()); - files.remove(i); - } - } - - files = sortDataModel(files); - mFiles = files; - - return new RescanDataAdapter(ZimFileSelectActivity.this, 0, mFiles); - } - - // Connect to the MediaScannerConnection service and scan all the files, that are returned to us by - // our MediaStore query. The file will ideally get removed from the MediaStore, - // if the scan resturns null and our CursorAdapter will update. - private void removeNonExistentFiles(Cursor cursor) { - - ArrayList files = new ArrayList(); - - // Iterate trough the data from our curser and add every file path column to an ArrayList - for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { - files.add(cursor.getString(2)); - } - updateMediaStore(files); - - } - - // Add new files to the MediaStore - private void addDataToMediaStore(ArrayList files) { - - ArrayList paths = new ArrayList(); - - for (DataModel file : files) { - paths.add(file.getPath()); - } - updateMediaStore(paths); - } - - private void updateMediaStore(ArrayList files) { - - // Abort endless loops. Let this update process only run on rescan. - if (!mNeedsUpdate) { - return; - } - - Log.i("kiwix", "Updating MediaStore"); - - // Scan every file (and delete it from the MediaStore, if it does not exist) - MediaScannerConnection.scanFile( - ZimFileSelectActivity.this, - files.toArray(new String[files.size()]), - null, - new MediaScannerConnection.OnScanCompletedListener() { - @Override - public void onScanCompleted(String path, Uri uri) { - - } - }); - - mNeedsUpdate = false; - } - - public ArrayList sortDataModel(ArrayList data) { - - // Sorting the data in alphabetical order - Collections.sort(data, new Comparator() { - @Override - public int compare(DataModel a, DataModel b) { - return a.getTitle().compareToIgnoreCase(b.getTitle()); - } - }); - - return data; - } - @Override public void onLoaderReset(Loader cursorLoader) { mCursorAdapter.swapCursor(null); @@ -320,7 +159,6 @@ public class ZimFileSelectActivity extends FragmentActivity // Make sure, that we set mNeedsUpdate to true and to false, after the MediaStore has been // updated. Otherwise it will result in a endless loop. - mNeedsUpdate = true; } return super.onOptionsItemSelected(item); @@ -349,193 +187,75 @@ public class ZimFileSelectActivity extends FragmentActivity finishResult(file); } - // This items class stores the Data for the ArrayAdapter. - // We Have to implement Parcelable, so we can store ArrayLists with this generic type in the Bundle - // of onSaveInstanceState() and retrieve it later on in onRestoreInstanceState() - public static class DataModel implements Parcelable { + // Query through the MediaStore + protected void startQuery() { - // Interface that must be implemented and provided as a public CREATOR field. - // It generates instances of our Parcelable class from a Parcel. - public Parcelable.Creator CREATOR = new Parcelable.Creator() { + // Defines a list of columns to retrieve from the Cursor and load into an output row + String[] mZimListColumns = {MediaStore.Files.FileColumns.TITLE, MediaStore.Files.FileColumns.DATA}; - @Override - public DataModel createFromParcel(Parcel source) { - return new DataModel(source); - } + // Defines a list of View IDs that will receive the Cursor columns for each row + int[] mZimListItems = {android.R.id.text1, android.R.id.text2}; - @Override - public boolean equals(Object o) { - return super.equals(o); - } + mCursorAdapter = new SimpleCursorAdapter( + // The Context object + ZimFileSelectActivity.this, + // A layout in XML for one row in the ListView + android.R.layout.simple_list_item_2, + // The cursor, swapped later by cursorloader + null, + // A string array of column names in the cursor + mZimListColumns, + // An integer array of view IDs in the row layout + mZimListItems, + // Flags for the Adapter + Adapter.NO_SELECTION); - @Override - public int hashCode() { - return super.hashCode(); - } + mZimFileList.setOnItemClickListener(this); - @Override - public DataModel[] newArray(int size) { - return new DataModel[size]; - } - }; - - private String mTitle; - - private String mPath; - - public DataModel(String title, String path) { - mTitle = title; - mPath = path; - } - - // This constructor will be called when this class is generated by a Parcel. - // We have to read the previously written Data in this Parcel. - public DataModel(Parcel parcel) { - String[] data = new String[2]; - parcel.readStringArray(data); - mTitle = data[0]; - mTitle = data[1]; - } - - public String getTitle() { - return mTitle; - } - - public String getPath() { - return mPath; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - // Write the data to the Parcel, so we can restore this Data later on. - // It will be restored by the DataModel(Parcel parcel) constructor. - dest.writeArray(new String[]{mTitle, mPath}); - } - - // Override equals(Object) so we can compare objects. Specifically, so List#contains() works. - @Override - public boolean equals(Object object) { - boolean isEqual = false; - - if (object != null && object instanceof DataModel) { - isEqual = (this.mPath.equals(((DataModel) object).mPath)); - } - - return isEqual; - } + getSupportLoaderManager().initLoader(LOADER_ID, null, this); } - // This AsyncTask will scan the file system for files with the Extension ".zim" or ".zimaa" - private class RescanFileSystem extends AsyncTask { + // Get the data of our cursor and wrap it all in our ArrayAdapter. + // We are doing this because the CursorAdapter does not allow us do remove rows from its dataset. + private RescanDataAdapter buildArrayAdapter(Cursor cursor) { - @Override - protected void onPreExecute() { + ArrayList files = new ArrayList(); - mProgressBarMessage.setVisibility(View.VISIBLE); - mProgressBar.setVisibility(View.VISIBLE); + for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { - super.onPreExecute(); - } - - @Override - protected Void doInBackground(Void... params) { - - mFiles = FindFiles(); - return null; - } - - @Override - protected void onPostExecute(Void result) { - mRescanAdapter = new RescanDataAdapter(ZimFileSelectActivity.this, 0, mFiles); - - mZimFileList.setAdapter(mRescanAdapter); - - mProgressBarMessage.setVisibility(View.GONE); - mProgressBar.setVisibility(View.GONE); - - new FileWriter(ZimFileSelectActivity.this).saveArray(mFiles); - - super.onPostExecute(result); - } - - // Scan through the file system and find all the files with .zim and .zimaa extensions - private ArrayList FindFiles() { - String directory = new File( - Environment.getExternalStorageDirectory().getAbsolutePath()).toString(); - final List fileList = new ArrayList(); - FilenameFilter[] filter = new FilenameFilter[zimFiles.length]; - - int i = 0; - for (final String extension : zimFiles) { - filter[i] = new FilenameFilter() { - public boolean accept(File dir, String name) { - return name.endsWith("." + extension); - } - }; - i++; + if (new File(cursor.getString(2)).exists()) { + files.add(new DataModel(cursor.getString(1), cursor.getString(2))); } + } - File[] foundFiles = listFilesAsArray(new File(directory), filter, -1); - for (File f : foundFiles) { - fileList.add(f.getAbsolutePath()); + files = new FileWriter(ZimFileSelectActivity.this, files).getDataModelList(); + + for (int i = 0; i < files.size(); i++) { + + if (!new File(files.get(i).getPath()).exists()) { + Log.e("kiwix", "File removed: " + files.get(i).getTitle()); + files.remove(i); } - - return createDataForAdapter(fileList); } - private Collection listFiles(File directory, FilenameFilter[] filter, - int recurse) { + files = new FileSearch().sortDataModel(files); + mFiles = files; - Vector files = new Vector(); + return new RescanDataAdapter(ZimFileSelectActivity.this, 0, mFiles); + } - File[] entries = directory.listFiles(); + // Get the selected file and return the result to the Activity, that called this Activity + private void finishResult(String path) { - if (entries != null) { - for (File entry : entries) { - for (FilenameFilter filefilter : filter) { - if (filter == null || filefilter.accept(directory, entry.getName())) { - files.add(entry); - } - } - if ((recurse <= -1) || (recurse > 0 && entry.isDirectory())) { - recurse--; - files.addAll(listFiles(entry, filter, recurse)); - recurse++; - } - } - } - return files; - } - - public File[] listFilesAsArray(File directory, FilenameFilter[] filter, int recurse) { - Collection files = listFiles(directory, filter, recurse); - - File[] arr = new File[files.size()]; - return files.toArray(arr); - } - - // Create an ArrayList with our DataModel - private ArrayList createDataForAdapter(List list) { - - ArrayList data = new ArrayList(); - for (String file : list) { - - data.add(new DataModel(getTitleFromFilePath(file), file)); - } - - data = sortDataModel(data); - - return data; - } - - // Remove the file path and the extension and return a file name for the given file path - private String getTitleFromFilePath(String path) { - return new File(path).getName().replaceFirst("[.][^.]+$", ""); + if (path != null) { + File file = new File(path); + Uri uri = Uri.fromFile(file); + Log.i("kiwix", "Opening " + uri); + setResult(RESULT_OK, new Intent().setData(uri)); + finish(); + } else { + setResult(RESULT_CANCELED); + finish(); } } @@ -576,4 +296,39 @@ public class ZimFileSelectActivity extends FragmentActivity TextView path; } } + + // This AsyncTask will scan the file system for files with the Extension ".zim" or ".zimaa" + private class RescanFileSystem extends AsyncTask { + + @Override + protected void onPreExecute() { + + mProgressBarMessage.setVisibility(View.VISIBLE); + mProgressBar.setVisibility(View.VISIBLE); + + super.onPreExecute(); + } + + @Override + protected Void doInBackground(Void... params) { + + mFiles = new FileSearch().findFiles(); + return null; + } + + @Override + protected void onPostExecute(Void result) { + mRescanAdapter = new RescanDataAdapter(ZimFileSelectActivity.this, 0, mFiles); + + mZimFileList.setAdapter(mRescanAdapter); + + mProgressBarMessage.setVisibility(View.GONE); + mProgressBar.setVisibility(View.GONE); + + new FileWriter(ZimFileSelectActivity.this).saveArray(mFiles); + + super.onPostExecute(result); + } + + } }