Trim search string and remove duplicate records from the database

Co-authored-by:  Yingwei Zheng <dtcxzyw@qq.com>
This commit is contained in:
TobiGr 2023-08-16 21:24:55 +02:00
parent db5ed48dbb
commit 90f0809029
6 changed files with 842 additions and 15 deletions

View file

@ -8,6 +8,7 @@ import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertNull
import org.junit.Rule
import org.junit.Test
@ -25,8 +26,11 @@ class DatabaseMigrationTest {
private const val DEFAULT_UPLOADER_NAME = "Uploader Test"
private const val DEFAULT_THUMBNAIL = "https://example.com/example.jpg"
private const val DEFAULT_SECOND_SERVICE_ID = 0
private const val DEFAULT_SECOND_SERVICE_ID = 1
private const val DEFAULT_SECOND_URL = "https://www.youtube.com/watch?v=ncQU6iBn5Fc"
private const val DEFAULT_SEARCH1 = " abc "
private const val DEFAULT_SEARCH2 = " abc"
}
@get:Rule
@ -106,6 +110,11 @@ class DatabaseMigrationTest {
Migrations.MIGRATION_6_7
)
testHelper.runMigrationsAndValidate(
AppDatabase.DATABASE_NAME, Migrations.DB_VER_6,
true, Migrations.MIGRATION_5_6
)
val migratedDatabaseV3 = getMigratedDatabase()
val listFromDB = migratedDatabaseV3.streamDAO().all.blockingFirst()
@ -140,6 +149,56 @@ class DatabaseMigrationTest {
assertNull(secondStreamFromMigratedDatabase.isUploadDateApproximation)
}
@Test
fun migrateDatabaseFrom5to6() {
val databaseInV5 = testHelper.createDatabase(AppDatabase.DATABASE_NAME, Migrations.DB_VER_5)
databaseInV5.run {
insert(
"search_history", SQLiteDatabase.CONFLICT_FAIL,
ContentValues().apply {
put("service_id", DEFAULT_SERVICE_ID)
put("search", DEFAULT_SEARCH1)
}
)
insert(
"search_history", SQLiteDatabase.CONFLICT_FAIL,
ContentValues().apply {
put("service_id", DEFAULT_SERVICE_ID)
put("search", DEFAULT_SEARCH2)
}
)
insert(
"search_history", SQLiteDatabase.CONFLICT_FAIL,
ContentValues().apply {
put("service_id", DEFAULT_SECOND_SERVICE_ID)
put("search", DEFAULT_SEARCH1)
}
)
insert(
"search_history", SQLiteDatabase.CONFLICT_FAIL,
ContentValues().apply {
put("service_id", DEFAULT_SECOND_SERVICE_ID)
put("search", DEFAULT_SEARCH2)
}
)
close()
}
testHelper.runMigrationsAndValidate(
AppDatabase.DATABASE_NAME, Migrations.DB_VER_6,
true, Migrations.MIGRATION_5_6
)
val migratedDatabaseV6 = getMigratedDatabase()
val listFromDB = migratedDatabaseV6.searchHistoryDAO().all.blockingFirst()
assertEquals(2, listFromDB.size)
assertEquals("abc", listFromDB[0].search)
assertEquals("abc", listFromDB[1].search)
assertNotEquals(listFromDB[0].serviceId, listFromDB[1].serviceId)
}
private fun getMigratedDatabase(): AppDatabase {
val database: AppDatabase = Room.databaseBuilder(
ApplicationProvider.getApplicationContext(),

View file

@ -7,6 +7,7 @@ import static org.schabi.newpipe.database.Migrations.MIGRATION_3_4;
import static org.schabi.newpipe.database.Migrations.MIGRATION_4_5;
import static org.schabi.newpipe.database.Migrations.MIGRATION_5_6;
import static org.schabi.newpipe.database.Migrations.MIGRATION_6_7;
import static org.schabi.newpipe.database.Migrations.MIGRATION_7_8;
import android.content.Context;
import android.database.Cursor;
@ -27,7 +28,7 @@ public final class NewPipeDatabase {
return Room
.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5,
MIGRATION_5_6, MIGRATION_6_7)
MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8)
.build();
}

View file

@ -1,6 +1,6 @@
package org.schabi.newpipe.database;
import static org.schabi.newpipe.database.Migrations.DB_VER_7;
import static org.schabi.newpipe.database.Migrations.DB_VER_8;
import androidx.room.Database;
import androidx.room.RoomDatabase;
@ -38,7 +38,7 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity;
FeedEntity.class, FeedGroupEntity.class, FeedGroupSubscriptionEntity.class,
FeedLastUpdatedEntity.class
},
version = DB_VER_7
version = DB_VER_8
)
public abstract class AppDatabase extends RoomDatabase {
public static final String DATABASE_NAME = "newpipe.db";

View file

@ -25,6 +25,7 @@ public final class Migrations {
public static final int DB_VER_5 = 5;
public static final int DB_VER_6 = 6;
public static final int DB_VER_7 = 7;
public static final int DB_VER_8 = 8;
private static final String TAG = Migrations.class.getName();
public static final boolean DEBUG = MainActivity.DEBUG;
@ -235,6 +236,15 @@ public final class Migrations {
}
};
public static final Migration MIGRATION_7_8 = new Migration(DB_VER_7, DB_VER_8) {
@Override
public void migrate(@NonNull final SupportSQLiteDatabase database) {
database.execSQL("DELETE FROM search_history WHERE id NOT IN (SELECT id FROM "
+ "(SELECT id FROM search_history GROUP BY trim(search), service_id) tmp)");
database.execSQL("UPDATE search_history SET search = trim(search)");
}
};
private Migrations() {
}
}

View file

@ -384,6 +384,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
@Override
public void onSaveInstanceState(@NonNull final Bundle bundle) {
searchEditText.setText(searchEditText.getText().toString().trim());
searchString = searchEditText != null
? searchEditText.getText().toString()
: searchString;
@ -396,8 +397,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
@Override
public void reloadContent() {
if (!TextUtils.isEmpty(searchString)
|| (searchEditText != null && !TextUtils.isEmpty(searchEditText.getText()))) {
if (!TextUtils.isEmpty(searchString) || (searchEditText != null
&& TextUtils.getTrimmedLength(searchEditText.getText()) > 0)) {
search(!TextUtils.isEmpty(searchString)
? searchString
: searchEditText.getText().toString(), this.contentFilter, "");
@ -494,7 +495,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
}
searchEditText.setText(searchString);
if (TextUtils.isEmpty(searchString) || TextUtils.isEmpty(searchEditText.getText())) {
if (TextUtils.isEmpty(searchString)
|| TextUtils.getTrimmedLength(searchEditText.getText()) == 0) {
searchToolbarContainer.setTranslationX(100);
searchToolbarContainer.setAlpha(0.0f);
searchToolbarContainer.setVisibility(View.VISIBLE);
@ -518,7 +520,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
if (DEBUG) {
Log.d(TAG, "onClick() called with: v = [" + v + "]");
}
if (TextUtils.isEmpty(searchEditText.getText())) {
if (TextUtils.getTrimmedLength(searchEditText.getText()) == 0) {
NavigationHelper.gotoMainFragment(getFM());
return;
}
@ -580,9 +582,12 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
searchEditText.removeTextChangedListener(textWatcher);
}
textWatcher = new TextWatcher() {
private boolean isPastedText = false;
@Override
public void beforeTextChanged(final CharSequence s, final int start,
final int count, final int after) {
isPastedText = TextUtils.isEmpty(s) && after > 1;
}
@Override
@ -597,8 +602,13 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
s.removeSpan(span);
}
final String newText = searchEditText.getText().toString();
final String newText = searchEditText.getText().toString().trim();
suggestionPublisher.onNext(newText);
if (isPastedText) {
// trim pasted text
searchEditText.setText(newText);
}
}
};
searchEditText.addTextChangedListener(textWatcher);
@ -613,6 +623,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
} else if (event != null
&& (event.getKeyCode() == KeyEvent.KEYCODE_ENTER
|| event.getAction() == EditorInfo.IME_ACTION_SEARCH)) {
searchEditText.setText(searchEditText.getText().toString().trim());
search(searchEditText.getText().toString(), new String[0], "");
return true;
}
@ -717,9 +728,9 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
.getRelatedSearches(query, similarQueryLimit, 25)
.toObservable()
.map(searchHistoryEntries ->
searchHistoryEntries.stream()
.map(entry -> new SuggestionItem(true, entry))
.collect(Collectors.toList()));
searchHistoryEntries.stream()
.map(entry -> new SuggestionItem(true, entry))
.collect(Collectors.toList()));
}
private Observable<List<SuggestionItem>> getRemoteSuggestionsObservable(final String query) {
@ -786,12 +797,12 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
} else if (listNotification.isOnError()
&& listNotification.getError() != null
&& !ExceptionUtils.isInterruptedCaused(
listNotification.getError())) {
listNotification.getError())) {
showSnackBarError(new ErrorInfo(listNotification.getError(),
UserAction.GET_SUGGESTIONS, searchString, serviceId));
}
}, throwable -> showSnackBarError(new ErrorInfo(
throwable, UserAction.GET_SUGGESTIONS, searchString, serviceId)));
throwable, UserAction.GET_SUGGESTIONS, searchString, serviceId)));
}
@Override
@ -804,6 +815,11 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
final String theSortFilter) {
if (DEBUG) {
Log.d(TAG, "search() called with: query = [" + theSearchString + "]");
final String trimmedSearchString = theSearchString.trim();
if (!trimmedSearchString.equals(theSearchString)) {
Log.d(TAG, "The precondition is not satisfied. "
+ "\"theSearchString\" is not allowed to have leading or trailing spaces");
}
}
if (theSearchString.isEmpty()) {
return;
@ -839,7 +855,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
disposables.add(historyRecordManager.onSearched(serviceId, theSearchString)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
ignored -> { },
ignored -> {
},
throwable -> showSnackBarError(new ErrorInfo(throwable, UserAction.SEARCHED,
theSearchString, serviceId))
));
@ -973,6 +990,9 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
}
searchSuggestion = result.getSearchSuggestion();
if (searchSuggestion != null) {
searchSuggestion = searchSuggestion.trim();
}
isCorrectedSearch = result.isCorrectedSearch();
// List<MetaInfo> cannot be bundled without creating some containers