commit
6f076891cd
89
res/layout/sdscanner_fragment.xml
Normal file
89
res/layout/sdscanner_fragment.xml
Normal file
@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2013-2014 Jeremy Erickson
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
-->
|
||||
<!--
|
||||
Copied from SD Scanner's layout/main.xml with minor changes
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:orientation="vertical" >
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/path_label" />
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:orientation="horizontal" >
|
||||
<EditText
|
||||
android:id="@+id/path_widget"
|
||||
android:layout_weight="1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent" />
|
||||
<ImageButton
|
||||
android:id="@+id/path_button"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@android:drawable/ic_menu_revert"
|
||||
style="?android:attr/borderlessButtonStyle" />
|
||||
</LinearLayout>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/db_label" />
|
||||
<CheckBox
|
||||
android:id="@+id/restrict_checkbox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/restrict_label" />
|
||||
<Button
|
||||
android:id="@+id/start_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="64dp"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/button_start">
|
||||
<requestFocus />
|
||||
</Button>
|
||||
<ProgressBar
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:max="100"
|
||||
android:id="@+id/progress_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:gravity="center_vertical"
|
||||
android:progress="0" />
|
||||
<TextView
|
||||
android:id="@+id/progress_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="10000"
|
||||
android:text="@string/progress_unstarted_label" />
|
||||
<TextView
|
||||
android:id="@+id/debug_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:text="" />
|
||||
</LinearLayout>
|
||||
|
@ -293,4 +293,24 @@ THE SOFTWARE.
|
||||
|
||||
<string name="permission_request_summary">Vanilla Music needs read permission to display your music library</string>
|
||||
<string name="reverse_sort">Reverse sort</string>
|
||||
|
||||
<!-- SD Scanner -->
|
||||
|
||||
<string name="button_start">Start Rescan</string>
|
||||
<string name="database_proc">Examined</string>
|
||||
<string name="delete_proc">Removed reference to</string>
|
||||
<string name="db_label">Will also check existing media database for updated or deleted files.</string>
|
||||
<string name="db_error_failure">Encountered error reading media database, and might miss updated or deleted files or rescan up-to-date files.</string>
|
||||
<string name="db_error_recovered">Encountered error reading media database, but recovered.</string>
|
||||
<string name="db_error_retrying">Encountered error reading media database. Retrying in 1 second...</string>
|
||||
<string name="final_proc">Processed</string>
|
||||
<string name="path_label">Path to check for new files:</string>
|
||||
<string name="progress_completed_label">Completed, ready to start another scan.</string>
|
||||
<string name="progress_error_bad_path_label">Scan failed: bad path specified for new file search.</string>
|
||||
<string name="progress_filelist_label">Preparing initial list of files...</string>
|
||||
<string name="progress_database_label">Querying database...</string>
|
||||
<string name="progress_unstarted_label">Not yet started.</string>
|
||||
<string name="restrict_label">Ignore updated and deleted files outside of the specified path.</string>
|
||||
<string name="skipping_folder_label">Encountered an error and skipping</string>
|
||||
<string name="sdscanner">SD Scanner</string>
|
||||
</resources>
|
||||
|
@ -45,6 +45,9 @@ THE SOFTWARE.
|
||||
<header
|
||||
android:fragment="ch.blinkenlights.android.vanilla.PreferencesTheme"
|
||||
android:title="@string/theme" />
|
||||
<header
|
||||
android:fragment="ch.blinkenlights.android.vanilla.SDScannerFragment"
|
||||
android:title="@string/sdscanner" />
|
||||
<header
|
||||
android:fragment="ch.blinkenlights.android.vanilla.PreferencesActivity$AboutFragment"
|
||||
android:title="@string/about" />
|
||||
|
202
src/ch/blinkenlights/android/vanilla/SDScannerFragment.java
Normal file
202
src/ch/blinkenlights/android/vanilla/SDScannerFragment.java
Normal file
@ -0,0 +1,202 @@
|
||||
/* SD Scanner - A manual implementation of the SD rescan process, compatible
|
||||
* with Android 4.4
|
||||
*
|
||||
* Copyright (C) 2013-2014 Jeremy Erickson
|
||||
* Copyright (C) 2016 Xiao Bao Clark
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
package ch.blinkenlights.android.vanilla;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.app.FragmentManager;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.text.method.ScrollingMovementMethod;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.gmail.jerickson314.sdscanner.ScanFragment;
|
||||
import com.gmail.jerickson314.sdscanner.UIStringGenerator;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Fragment version of the MainActivity from the SD Scanner app
|
||||
*/
|
||||
public class SDScannerFragment extends Fragment
|
||||
implements ScanFragment.ScanProgressCallbacks
|
||||
{
|
||||
ScanFragment mScanFragment;
|
||||
|
||||
@Override
|
||||
public void updateProgressNum(int progressNum) {
|
||||
ProgressBar progressBar = (ProgressBar)findViewById(R.id.progress_bar);
|
||||
progressBar.setProgress(progressNum);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProgressText(UIStringGenerator progressText) {
|
||||
TextView progressLabel = (TextView)findViewById(R.id.progress_label);
|
||||
progressLabel.setText(progressText.toString(getActivity()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDebugMessages(UIStringGenerator debugMessages) {
|
||||
TextView debugLabel = (TextView)findViewById(R.id.debug_label);
|
||||
debugLabel.setText(debugMessages.toString(getActivity()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePath(String path) {
|
||||
EditText pathText = (EditText) findViewById(R.id.path_widget);
|
||||
pathText.setText(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateStartButtonEnabled(boolean startButtonEnabled) {
|
||||
Button startButton = (Button)findViewById(R.id.start_button);
|
||||
startButton.setEnabled(startButtonEnabled);
|
||||
}
|
||||
|
||||
public void updateRestrictCheckboxChecked(boolean checked) {
|
||||
CheckBox restrictCheckbox = (CheckBox) findViewById(R.id.restrict_checkbox);
|
||||
restrictCheckbox.setChecked(checked);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void signalFinished() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.sdscanner_fragment, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
// Setup with values from fragment.
|
||||
updateProgressNum(mScanFragment.getProgressNum());
|
||||
updateProgressText(mScanFragment.getProgressText());
|
||||
updateDebugMessages(mScanFragment.getDebugMessages());
|
||||
updateStartButtonEnabled(mScanFragment.getStartButtonEnabled());
|
||||
|
||||
// Update path from preferences
|
||||
SharedPreferences preferences = getActivity().getPreferences(Context.MODE_PRIVATE);
|
||||
try {
|
||||
updatePath(preferences.getString("path",
|
||||
Environment.getExternalStorageDirectory().getCanonicalPath()));
|
||||
updateRestrictCheckboxChecked(preferences.getBoolean(
|
||||
"restrict_db_scan", false));
|
||||
}
|
||||
catch (IOException Ex) {
|
||||
// Should never happen, but getCanonicalPath() declares the throw.
|
||||
updatePath("");
|
||||
updateRestrictCheckboxChecked(false);
|
||||
}
|
||||
|
||||
// Make debug output scrollable.
|
||||
TextView debugLabel = (TextView)findViewById(R.id.debug_label);
|
||||
debugLabel.setMovementMethod(new ScrollingMovementMethod());
|
||||
|
||||
view.findViewById(R.id.path_button).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
try {
|
||||
defaultButtonPressed(v);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
view.findViewById(R.id.start_button).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
try {
|
||||
startButtonPressed(v);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Called when the activity is first created. */
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
FragmentManager fm = getFragmentManager();
|
||||
mScanFragment = (ScanFragment) fm.findFragmentByTag("scan");
|
||||
|
||||
if (mScanFragment == null) {
|
||||
mScanFragment = new ScanFragment();
|
||||
fm.beginTransaction().add(mScanFragment, "scan").commit();
|
||||
}
|
||||
mScanFragment.setScanProgressCallbacks(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
mScanFragment.setScanProgressCallbacks(null);
|
||||
}
|
||||
|
||||
private View findViewById(int viewId) {
|
||||
return getView().findViewById(viewId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
|
||||
// Write setting to preferences
|
||||
EditText pathText = (EditText) findViewById(R.id.path_widget);
|
||||
CheckBox restrictCheckbox = (CheckBox) findViewById(R.id.restrict_checkbox);
|
||||
|
||||
SharedPreferences preferences = getActivity().getPreferences(Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = preferences.edit();
|
||||
editor.putString("path", pathText.getText().toString());
|
||||
editor.putBoolean("restrict_db_scan", restrictCheckbox.isChecked());
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public void defaultButtonPressed(View view) throws IOException {
|
||||
updatePath(Environment.getExternalStorageDirectory().getCanonicalPath());
|
||||
}
|
||||
|
||||
public void startButtonPressed(View view) throws IOException {
|
||||
startScan();
|
||||
}
|
||||
|
||||
public void startScan() throws IOException {
|
||||
EditText pathText = (EditText) findViewById(R.id.path_widget);
|
||||
File path = new File(pathText.getText().toString());
|
||||
CheckBox restrictCheckbox = (CheckBox) findViewById(R.id.restrict_checkbox);
|
||||
|
||||
mScanFragment.startScan(path.getCanonicalFile(), restrictCheckbox.isChecked());
|
||||
}
|
||||
|
||||
}
|
533
src/com/gmail/jerickson314/sdscanner/ScanFragment.java
Normal file
533
src/com/gmail/jerickson314/sdscanner/ScanFragment.java
Normal file
@ -0,0 +1,533 @@
|
||||
/* SD Scanner - A manual implementation of the SD rescan process, compatible
|
||||
* with Android 4.4.
|
||||
*
|
||||
* This file contains the fragment that actually performs all scan activity
|
||||
* and retains state across configuration changes.
|
||||
*
|
||||
* Copyright (C) 2013-2014 Jeremy Erickson
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
package com.gmail.jerickson314.sdscanner;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.media.MediaScannerConnection;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import ch.blinkenlights.android.vanilla.R;
|
||||
|
||||
public class ScanFragment extends Fragment {
|
||||
|
||||
private static final String[] MEDIA_PROJECTION =
|
||||
{MediaStore.MediaColumns.DATA,
|
||||
MediaStore.MediaColumns.DATE_MODIFIED};
|
||||
|
||||
private static final String[] STAR = {"*"};
|
||||
|
||||
private static final int DB_RETRIES = 3;
|
||||
|
||||
Context mApplicationContext;
|
||||
|
||||
ArrayList<String> mPathNames;
|
||||
TreeSet<File> mFilesToProcess;
|
||||
int mLastGoodProcessedIndex;
|
||||
|
||||
private Handler mHandler = new Handler();
|
||||
|
||||
int mProgressNum;
|
||||
UIStringGenerator mProgressText =
|
||||
new UIStringGenerator(R.string.progress_unstarted_label);
|
||||
UIStringGenerator mDebugMessages = new UIStringGenerator();
|
||||
boolean mStartButtonEnabled;
|
||||
boolean mHasStarted = false;
|
||||
|
||||
/**
|
||||
* Callback interface used by the fragment to update the Activity.
|
||||
*/
|
||||
public static interface ScanProgressCallbacks {
|
||||
void updateProgressNum(int progressNum);
|
||||
void updateProgressText(UIStringGenerator progressText);
|
||||
void updateDebugMessages(UIStringGenerator debugMessages);
|
||||
void updatePath(String path);
|
||||
void updateStartButtonEnabled(boolean startButtonEnabled);
|
||||
void signalFinished();
|
||||
}
|
||||
|
||||
private ScanProgressCallbacks mCallbacks;
|
||||
|
||||
private void updateProgressNum(int progressNum) {
|
||||
mProgressNum = progressNum;
|
||||
if (mCallbacks != null) {
|
||||
mCallbacks.updateProgressNum(mProgressNum);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateProgressText(int resId) {
|
||||
updateProgressText(new UIStringGenerator(resId));
|
||||
}
|
||||
|
||||
private void updateProgressText(int resId, String string) {
|
||||
updateProgressText(new UIStringGenerator(resId, string));
|
||||
}
|
||||
|
||||
private void updateProgressText(UIStringGenerator progressText) {
|
||||
mProgressText = progressText;
|
||||
if (mCallbacks != null) {
|
||||
mCallbacks.updateProgressText(mProgressText);
|
||||
}
|
||||
}
|
||||
|
||||
private void addDebugMessage(int resId, String string) {
|
||||
mDebugMessages.addSubGenerator(resId);
|
||||
mDebugMessages.addSubGenerator(string + "\n");
|
||||
if (mCallbacks != null) {
|
||||
mCallbacks.updateDebugMessages(mDebugMessages);
|
||||
}
|
||||
}
|
||||
|
||||
private void addDebugMessage(String debugMessage) {
|
||||
mDebugMessages.addSubGenerator(debugMessage + "\n");
|
||||
if (mCallbacks != null) {
|
||||
mCallbacks.updateDebugMessages(mDebugMessages);
|
||||
}
|
||||
}
|
||||
|
||||
private void resetDebugMessages() {
|
||||
mDebugMessages = new UIStringGenerator();
|
||||
if (mCallbacks != null) {
|
||||
mCallbacks.updateDebugMessages(mDebugMessages);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateStartButtonEnabled(boolean startButtonEnabled) {
|
||||
mStartButtonEnabled = startButtonEnabled;
|
||||
if (mCallbacks != null) {
|
||||
mCallbacks.updateStartButtonEnabled(mStartButtonEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
private void signalFinished() {
|
||||
if (mCallbacks != null) {
|
||||
mCallbacks.signalFinished();
|
||||
}
|
||||
}
|
||||
|
||||
public int getProgressNum() {
|
||||
return mProgressNum;
|
||||
}
|
||||
|
||||
public UIStringGenerator getProgressText() {
|
||||
return mProgressText;
|
||||
}
|
||||
|
||||
public UIStringGenerator getDebugMessages() {
|
||||
return mDebugMessages;
|
||||
}
|
||||
|
||||
public boolean getStartButtonEnabled() {
|
||||
return mStartButtonEnabled;
|
||||
}
|
||||
|
||||
public boolean getHasStarted() {
|
||||
return mHasStarted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
if(activity instanceof ScanProgressCallbacks) {
|
||||
mCallbacks = (ScanProgressCallbacks) activity;
|
||||
}
|
||||
mApplicationContext = activity.getApplicationContext();
|
||||
}
|
||||
|
||||
public void setScanProgressCallbacks(ScanProgressCallbacks callbacks) {
|
||||
mCallbacks = callbacks;
|
||||
}
|
||||
|
||||
public ScanFragment() {
|
||||
super();
|
||||
|
||||
// Set correct initial values.
|
||||
mProgressNum = 0;
|
||||
mStartButtonEnabled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Retain this fragment across configuration changes.
|
||||
setRetainInstance(true);
|
||||
}
|
||||
|
||||
// Purely for debugging and not normally used, so does not translate
|
||||
// strings.
|
||||
public void listPathNamesOnDebug() {
|
||||
StringBuffer listString = new StringBuffer();
|
||||
listString.append("\n\nScanning paths:\n");
|
||||
Iterator<String> iterator = mPathNames.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
listString.append(iterator.next() + "\n");
|
||||
}
|
||||
addDebugMessage(listString.toString());
|
||||
}
|
||||
|
||||
public void scannerEnded() {
|
||||
updateProgressNum(0);
|
||||
updateProgressText(R.string.progress_completed_label);
|
||||
updateStartButtonEnabled(true);
|
||||
signalFinished();
|
||||
}
|
||||
|
||||
public void startMediaScanner(){
|
||||
//listPathNamesOnDebug();
|
||||
if (mPathNames.size() == 0) {
|
||||
scannerEnded();
|
||||
}
|
||||
else {
|
||||
MediaScannerConnection.scanFile(
|
||||
mApplicationContext,
|
||||
mPathNames.toArray(new String[mPathNames.size()]),
|
||||
null,
|
||||
new MediaScannerConnection.OnScanCompletedListener() {
|
||||
public void onScanCompleted(String path, Uri uri) {
|
||||
mHandler.post(new Updater(path));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void startScan(File path, boolean restrictDbUpdate) {
|
||||
mHasStarted = true;
|
||||
updateStartButtonEnabled(false);
|
||||
updateProgressText(R.string.progress_filelist_label);
|
||||
mFilesToProcess = new TreeSet<File>();
|
||||
resetDebugMessages();
|
||||
if (path.exists()) {
|
||||
this.new PreprocessTask().execute(new ScanParameters(path, restrictDbUpdate));
|
||||
}
|
||||
else {
|
||||
updateProgressText(R.string.progress_error_bad_path_label);
|
||||
updateStartButtonEnabled(true);
|
||||
signalFinished();
|
||||
}
|
||||
}
|
||||
|
||||
static class ProgressUpdate {
|
||||
public enum Type {
|
||||
DATABASE, STATE, DEBUG
|
||||
}
|
||||
|
||||
Type mType;
|
||||
|
||||
public Type getType() {
|
||||
return mType;
|
||||
}
|
||||
|
||||
int mResId;
|
||||
|
||||
public int getResId() {
|
||||
return mResId;
|
||||
}
|
||||
|
||||
String mString;
|
||||
|
||||
public String getString() {
|
||||
return mString;
|
||||
}
|
||||
|
||||
int mProgress;
|
||||
|
||||
public int getProgress() {
|
||||
return mProgress;
|
||||
}
|
||||
|
||||
public ProgressUpdate(Type type, int resId, String string,
|
||||
int progress) {
|
||||
mType = type;
|
||||
mResId = resId;
|
||||
mString = string;
|
||||
mProgress = progress;
|
||||
}
|
||||
}
|
||||
|
||||
static ProgressUpdate debugUpdate(int resId, String string) {
|
||||
return new ProgressUpdate(ProgressUpdate.Type.DEBUG, resId, string, 0);
|
||||
}
|
||||
|
||||
static ProgressUpdate debugUpdate(int resId) {
|
||||
return debugUpdate(resId, "");
|
||||
}
|
||||
|
||||
static ProgressUpdate databaseUpdate(String file, int progress) {
|
||||
return new ProgressUpdate(ProgressUpdate.Type.DATABASE, 0, file,
|
||||
progress);
|
||||
}
|
||||
|
||||
static ProgressUpdate stateUpdate(int resId) {
|
||||
return new ProgressUpdate(ProgressUpdate.Type.STATE, resId, "", 0);
|
||||
}
|
||||
|
||||
static class ScanParameters {
|
||||
File mPath;
|
||||
boolean mRestrictDbUpdate;
|
||||
|
||||
public ScanParameters(File path, boolean restrictDbUpdate) {
|
||||
mPath = path;
|
||||
mRestrictDbUpdate = restrictDbUpdate;
|
||||
}
|
||||
|
||||
public File getPath() {
|
||||
return mPath;
|
||||
}
|
||||
|
||||
public boolean shouldScan(File file, boolean fromDb)
|
||||
throws IOException {
|
||||
// Empty directory check.
|
||||
if (file.isDirectory()) {
|
||||
File[] files = file.listFiles();
|
||||
if (files == null || files.length == 0) {
|
||||
Log.w("SDScanner", "Scan of empty directory " +
|
||||
file.getCanonicalPath() + " skipped to avoid bug.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!mRestrictDbUpdate && fromDb) {
|
||||
return true;
|
||||
}
|
||||
while (file != null) {
|
||||
if (file.equals(mPath)) {
|
||||
return true;
|
||||
}
|
||||
file = file.getParentFile();
|
||||
}
|
||||
// If we fell through here, got up to root without encountering the
|
||||
// path to scan.
|
||||
if (!fromDb) {
|
||||
Log.w("SDScanner", "File " + file.getCanonicalPath() +
|
||||
" outside of scan directory skipped.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class PreprocessTask extends AsyncTask<ScanParameters, ProgressUpdate, Void> {
|
||||
|
||||
private void recursiveAddFiles(File file, ScanParameters scanParameters)
|
||||
throws IOException {
|
||||
if (!scanParameters.shouldScan(file, false)) {
|
||||
// If we got here, there file was either outside the scan
|
||||
// directory, or was an empty directory.
|
||||
return;
|
||||
}
|
||||
if (!mFilesToProcess.add(file)) {
|
||||
// Avoid infinite recursion caused by symlinks.
|
||||
// If mFilesToProcess already contains this file, add() will
|
||||
// return false.
|
||||
return;
|
||||
}
|
||||
if (file.isDirectory()) {
|
||||
boolean nomedia = new File(file, ".nomedia").exists();
|
||||
// Only recurse downward if not blocked by nomedia.
|
||||
if (!nomedia) {
|
||||
File[] files = file.listFiles();
|
||||
if (files != null) {
|
||||
for (File nextFile : files) {
|
||||
recursiveAddFiles(nextFile.getCanonicalFile(),
|
||||
scanParameters);
|
||||
}
|
||||
}
|
||||
else {
|
||||
publishProgress(debugUpdate(
|
||||
R.string.skipping_folder_label,
|
||||
" " + file.getPath()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void dbOneTry(ScanParameters parameters) {
|
||||
Cursor cursor = mApplicationContext.getContentResolver().query(
|
||||
MediaStore.Files.getContentUri("external"),
|
||||
MEDIA_PROJECTION,
|
||||
//STAR,
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
int data_column =
|
||||
cursor.getColumnIndex(MediaStore.MediaColumns.DATA);
|
||||
int modified_column =
|
||||
cursor.getColumnIndex(MediaStore.MediaColumns.DATE_MODIFIED);
|
||||
int totalSize = cursor.getCount();
|
||||
int currentItem = 0;
|
||||
int reportFreq = 0;
|
||||
// Used to calibrate reporting frequency
|
||||
long startTime = SystemClock.currentThreadTimeMillis();
|
||||
while (cursor.moveToNext()) {
|
||||
currentItem++;
|
||||
try {
|
||||
File file = new File(cursor.getString(data_column)).getCanonicalFile();
|
||||
if ((!file.exists() ||
|
||||
file.lastModified() / 1000L >
|
||||
cursor.getLong(modified_column))
|
||||
&& parameters.shouldScan(file, true)) {
|
||||
// Media scanner handles these cases.
|
||||
// Is a set, so OK if already present.
|
||||
mFilesToProcess.add(file);
|
||||
}
|
||||
else {
|
||||
// Don't want to waste time scanning an up-to-date
|
||||
// file.
|
||||
mFilesToProcess.remove(file);
|
||||
}
|
||||
if (reportFreq == 0) {
|
||||
// Calibration phase
|
||||
if (SystemClock.currentThreadTimeMillis() - startTime > 25) {
|
||||
reportFreq = currentItem + 1;
|
||||
}
|
||||
}
|
||||
else if (currentItem % reportFreq == 0) {
|
||||
publishProgress(databaseUpdate(file.getPath(),
|
||||
(100 * currentItem) / totalSize));
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// Just ignore it for now.
|
||||
}
|
||||
}
|
||||
// Don't need the cursor any more.
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(ScanParameters... parameters) {
|
||||
try {
|
||||
recursiveAddFiles(parameters[0].getPath(), parameters[0]);
|
||||
}
|
||||
catch (IOException Ex) {
|
||||
// Do nothing.
|
||||
}
|
||||
// Parse database
|
||||
publishProgress(stateUpdate(R.string.progress_database_label));
|
||||
boolean dbSuccess = false;
|
||||
int numRetries = 0;
|
||||
while (!dbSuccess && numRetries < DB_RETRIES) {
|
||||
dbSuccess = true;
|
||||
try {
|
||||
dbOneTry(parameters[0]);
|
||||
}
|
||||
catch (Exception Ex) {
|
||||
// For any of these errors, try again.
|
||||
numRetries++;
|
||||
dbSuccess = false;
|
||||
if (numRetries < DB_RETRIES) {
|
||||
publishProgress(stateUpdate(
|
||||
R.string.db_error_retrying));
|
||||
SystemClock.sleep(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (numRetries > 0) {
|
||||
if (dbSuccess) {
|
||||
publishProgress(debugUpdate(R.string.db_error_recovered));
|
||||
}
|
||||
else {
|
||||
publishProgress(debugUpdate(R.string.db_error_failure));
|
||||
}
|
||||
}
|
||||
// Prepare final path list for processing.
|
||||
mPathNames = new ArrayList<String>(mFilesToProcess.size());
|
||||
Iterator<File> iterator = mFilesToProcess.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
mPathNames.add(iterator.next().getPath());
|
||||
}
|
||||
mLastGoodProcessedIndex = -1;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(ProgressUpdate... progress) {
|
||||
switch (progress[0].getType()) {
|
||||
case DATABASE:
|
||||
updateProgressText(R.string.database_proc,
|
||||
" " + progress[0].getString());
|
||||
updateProgressNum(progress[0].getProgress());
|
||||
break;
|
||||
case STATE:
|
||||
updateProgressText(progress[0].getResId());
|
||||
updateProgressNum(0);
|
||||
break;
|
||||
case DEBUG:
|
||||
addDebugMessage(progress[0].getResId(), progress[0].getString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
startMediaScanner();
|
||||
}
|
||||
}
|
||||
|
||||
class Updater implements Runnable {
|
||||
String mPathScanned;
|
||||
|
||||
public Updater(String path) {
|
||||
mPathScanned = path;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
if (mLastGoodProcessedIndex + 1 < mPathNames.size() &&
|
||||
mPathNames.get(mLastGoodProcessedIndex
|
||||
+ 1).equals(mPathScanned)) {
|
||||
mLastGoodProcessedIndex++;
|
||||
}
|
||||
else {
|
||||
int newIndex = mPathNames.indexOf(mPathScanned);
|
||||
if (newIndex > -1) {
|
||||
mLastGoodProcessedIndex = newIndex;
|
||||
}
|
||||
}
|
||||
int progress = (100 * (mLastGoodProcessedIndex + 1))
|
||||
/ mPathNames.size();
|
||||
if (progress == 100) {
|
||||
scannerEnded();
|
||||
}
|
||||
else {
|
||||
updateProgressNum(progress);
|
||||
updateProgressText(R.string.final_proc, " " + mPathScanned);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
87
src/com/gmail/jerickson314/sdscanner/UIStringGenerator.java
Normal file
87
src/com/gmail/jerickson314/sdscanner/UIStringGenerator.java
Normal file
@ -0,0 +1,87 @@
|
||||
/* SD Scanner - A manual implementation of the SD rescan process, compatible
|
||||
* with Android 4.4
|
||||
*
|
||||
* Copyright (C) 2013-2014 Jeremy Erickson
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
package com.gmail.jerickson314.sdscanner;
|
||||
|
||||
import android.app.Activity;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class UIStringGenerator {
|
||||
private static interface SubGenerator {
|
||||
public String toString(Activity activity);
|
||||
}
|
||||
|
||||
private static class ResourceSubGenerator implements SubGenerator {
|
||||
int mResId;
|
||||
|
||||
public ResourceSubGenerator(int resId) {
|
||||
mResId = resId;
|
||||
}
|
||||
|
||||
public String toString(Activity activity) {
|
||||
return activity.getString(mResId);
|
||||
}
|
||||
}
|
||||
|
||||
private static class StringSubGenerator implements SubGenerator {
|
||||
String mString;
|
||||
|
||||
public StringSubGenerator(String string) {
|
||||
mString = string;
|
||||
}
|
||||
|
||||
public String toString(Activity activity) {
|
||||
return mString;
|
||||
}
|
||||
}
|
||||
|
||||
ArrayList<SubGenerator> mSubGenerators = new ArrayList<SubGenerator>();
|
||||
|
||||
public void addSubGenerator(int resId) {
|
||||
mSubGenerators.add(new ResourceSubGenerator(resId));
|
||||
}
|
||||
|
||||
public void addSubGenerator(String string) {
|
||||
mSubGenerators.add(new StringSubGenerator(string));
|
||||
}
|
||||
|
||||
public UIStringGenerator(int resId) {
|
||||
addSubGenerator(resId);
|
||||
}
|
||||
|
||||
public UIStringGenerator(int resId, String string) {
|
||||
addSubGenerator(resId);
|
||||
addSubGenerator(string);
|
||||
}
|
||||
|
||||
public UIStringGenerator(String string) {
|
||||
addSubGenerator(string);
|
||||
}
|
||||
|
||||
public UIStringGenerator() {
|
||||
// Doing nothing results in empty string.
|
||||
}
|
||||
|
||||
public String toString(Activity activity) {
|
||||
StringBuilder toReturn = new StringBuilder();
|
||||
Iterator<SubGenerator> iterator = mSubGenerators.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
toReturn.append(iterator.next().toString(activity));
|
||||
}
|
||||
return toReturn.toString();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user