Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 22 additions & 5 deletions AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ import com.ichi2.ui.FixedEditText
import com.ichi2.utils.AdaptionUtil.hasWebBrowser
import com.ichi2.utils.AndroidUiUtils.isRunningOnTv
import com.ichi2.utils.AssetHelper.guessMimeType
import com.ichi2.utils.BlocksSchemaUpgrade
import com.ichi2.utils.ClipboardUtil.getText
import com.ichi2.utils.Computation
import com.ichi2.utils.HandlerUtils.executeFunctionWithDelay
Expand Down Expand Up @@ -853,8 +854,8 @@ abstract class AbstractFlashcardViewer :
if (BackendFactory.defaultLegacySchema) {
legacyUndo()
} else {
return launchCatchingCollectionTask { col ->
if (!backendUndoAndShowPopup(col)) {
return launchCatchingTask {
if (!backendUndoAndShowPopup()) {
Comment thread
dae marked this conversation as resolved.
legacyUndo()
}
}
Expand Down Expand Up @@ -2257,7 +2258,9 @@ abstract class AbstractFlashcardViewer :
// We play sounds through these links when a user taps the sound icon.
fun filterUrl(url: String): Boolean {
if (url.startsWith("playsound:")) {
controlSound(url)
launchCatchingTask {
controlSound(url)
}
return true
}
if (url.startsWith("file") || url.startsWith("data:")) {
Expand Down Expand Up @@ -2450,8 +2453,22 @@ abstract class AbstractFlashcardViewer :
* Also, Check if the user clicked on the running audio icon
* @param url
*/
private fun controlSound(url: String) {
val replacedUrl = url.replaceFirst("playsound:".toRegex(), "")
@BlocksSchemaUpgrade("handle TTS tags")
private suspend fun controlSound(url: String) {
val replacedUrl = if (BackendFactory.defaultLegacySchema) {
url.replaceFirst("playsound:".toRegex(), "")
} else {
val tag = currentCard?.let { getAvTag(it, url) }
val filename = when (tag) {
is SoundOrVideoTag -> tag.filename
// not currently supported
is TTSTag -> null
else -> null
}
filename?.let {
Sound.getSoundPath(mBaseUrl, it)
} ?: return
}
if (replacedUrl != mSoundPlayer.currentAudioUri || mSoundPlayer.isCurrentAudioFinished) {
onCurrentAudioChanged(replacedUrl)
} else {
Expand Down
24 changes: 11 additions & 13 deletions AnkiDroid/src/main/java/com/ichi2/anki/BackendBackups.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,47 +18,45 @@

package com.ichi2.anki

import com.ichi2.libanki.CollectionV16
import com.ichi2.anki.CollectionManager.withCol
import com.ichi2.libanki.awaitBackupCompletion
import com.ichi2.libanki.createBackup
import kotlinx.coroutines.*

fun DeckPicker.performBackupInBackground() {
launchCatchingCollectionTask { col ->
launchCatchingTask {
// Wait a second to allow the deck list to finish loading first, or it
// will hang until the first stage of the backup completes.
delay(1000)
createBackup(col, false)
createBackup(force = false)
}
}

fun DeckPicker.importColpkg(colpkgPath: String) {
launchCatchingTask {
val helper = CollectionHelper.getInstance()
val backend = helper.getOrCreateBackend(baseContext)
runInBackgroundWithProgress(
backend,
withProgress(
extractProgress = {
if (progress.hasImporting()) {
text = progress.importing
}
},
) {
helper.importColpkg(baseContext, colpkgPath)
CollectionManager.importColpkg(colpkgPath)
}

invalidateOptionsMenu()
updateDeckList()
}
}

private suspend fun createBackup(col: CollectionV16, force: Boolean) {
runInBackground {
private suspend fun createBackup(force: Boolean) {
withCol {
// this two-step approach releases the backend lock after the initial copy
col.createBackup(
BackupManager.getBackupDirectoryFromCollection(col.path),
newBackend.createBackup(
BackupManager.getBackupDirectoryFromCollection(this.path),
force,
waitForCompletion = false
)
col.awaitBackupCompletion()
newBackend.awaitBackupCompletion()
}
}
47 changes: 32 additions & 15 deletions AnkiDroid/src/main/java/com/ichi2/anki/BackendImporting.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,26 @@
package com.ichi2.anki

import anki.import_export.ImportResponse
import com.ichi2.anki.CollectionManager.withCol
import com.ichi2.libanki.DeckId
import com.ichi2.libanki.exportAnkiPackage
import com.ichi2.libanki.exportCollectionPackage
import com.ichi2.libanki.importAnkiPackage
import com.ichi2.libanki.undoableOp
import net.ankiweb.rsdroid.Translations

fun DeckPicker.importApkgs(apkgPaths: List<String>) {
launchCatchingCollectionTask { col ->
launchCatchingTask {
for (apkgPath in apkgPaths) {
val report = runInBackgroundWithProgress(
col.backend,
val report = withProgress(
extractProgress = {
if (progress.hasImporting()) {
text = progress.importing
}
},
) {
undoableOp {
col.importAnkiPackage(apkgPath)
importAnkiPackage(apkgPath)
}
}
showSimpleMessageDialog(summarizeReport(col.tr, report))
Expand All @@ -64,22 +65,38 @@ private fun summarizeReport(tr: Translations, output: ImportResponse): String {
return msgs.joinToString("\n")
}

fun DeckPicker.exportApkg(
suspend fun AnkiActivity.exportApkg(
apkgPath: String,
withScheduling: Boolean,
withMedia: Boolean,
deckId: DeckId?
) {
launchCatchingCollectionTask { col ->
runInBackgroundWithProgress(
col.backend,
extractProgress = {
if (progress.hasExporting()) {
text = progress.exporting
}
},
) {
col.exportAnkiPackage(apkgPath, withScheduling, withMedia, deckId)
withProgress(
extractProgress = {
if (progress.hasExporting()) {
text = progress.exporting
}
},
) {
withCol {
newBackend.exportAnkiPackage(apkgPath, withScheduling, withMedia, deckId)
}
}
}

suspend fun AnkiActivity.exportColpkg(
colpkgPath: String,
withMedia: Boolean,
) {
withProgress(
extractProgress = {
if (progress.hasExporting()) {
text = progress.exporting
}
},
) {
withCol {
newBackend.exportCollectionPackage(colpkgPath, withMedia, true)
}
}
}
11 changes: 6 additions & 5 deletions AnkiDroid/src/main/java/com/ichi2/anki/BackendUndo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,24 @@

package com.ichi2.anki

import androidx.fragment.app.FragmentActivity
import com.ichi2.anki.CollectionManager.TR
import com.ichi2.anki.UIUtils.showSimpleSnackbar
import com.ichi2.libanki.CollectionV16
import com.ichi2.libanki.undoNew
import com.ichi2.libanki.undoableOp
import com.ichi2.utils.BlocksSchemaUpgrade
import net.ankiweb.rsdroid.BackendException

suspend fun AnkiActivity.backendUndoAndShowPopup(col: CollectionV16): Boolean {
suspend fun FragmentActivity.backendUndoAndShowPopup(): Boolean {
return try {
val changes = runInBackgroundWithProgress() {
val changes = withProgress() {
undoableOp {
col.undoNew()
undoNew()
}
}
showSimpleSnackbar(
this,
col.tr.undoActionUndone(changes.operation),
TR.undoActionUndone(changes.operation),
false
)
true
Expand Down
7 changes: 3 additions & 4 deletions AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1262,8 +1262,8 @@ open class CardBrowser :
if (BackendFactory.defaultLegacySchema) {
Undo().runWithHandler(mUndoHandler)
} else {
launchCatchingCollectionTask { col ->
if (!backendUndoAndShowPopup(col)) {
launchCatchingTask {
if (!backendUndoAndShowPopup()) {
Undo().runWithHandler(mUndoHandler)
}
}
Expand Down Expand Up @@ -2615,8 +2615,7 @@ open class CardBrowser :
changes.card
) && handler !== this
) {
// executing this only for the refresh side effects; there may be a better way
Undo().runWithHandler(mUndoHandler)
mUndoHandler.actualOnPostExecute(this@CardBrowser, Computation.ok(NextCard.withNoResult(null)))
}
}

Expand Down
74 changes: 6 additions & 68 deletions AnkiDroid/src/main/java/com/ichi2/anki/CollectionHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,9 @@
* Singleton which opens, stores, and closes the reference to the Collection.
*/
public class CollectionHelper {

// Collection instance belonging to sInstance
private Collection mCollection;
// Name of anki2 file
public static final String COLLECTION_FILENAME = "collection.anki2";

// A backend instance is reused after collection close.
private @Nullable Backend mBackend;

/**
* The preference key for the path to the current AnkiDroid directory
* <br>
Expand Down Expand Up @@ -143,54 +137,19 @@ public static CollectionHelper getInstance() {
*/
private Collection openCollection(Context context, String path) {
Timber.i("Begin openCollection: %s", path);
Backend backend = getOrCreateBackend(context);
Backend backend = BackendFactory.getBackend(context);
Collection collection = Storage.collection(context, path, false, true, backend);
Timber.i("End openCollection: %s", path);
return collection;
}

synchronized @NonNull Backend getOrCreateBackend(Context context) {
if (mBackend == null) {
mBackend = BackendFactory.getBackend(context);
}
return mBackend;
}


/**
* Close the currently cached backend and discard it. Useful when enabling the V16 scheduler in the
* dev preferences, or if the active language changes. The collection should be closed before calling
* this.
*/
public synchronized void discardBackend() {
if (mBackend != null) {
mBackend.close();
mBackend = null;
}
}

/**
* Get the single instance of the {@link Collection}, creating it if necessary (lazy initialization).
* @param _context is no longer used, as the global AnkidroidApp instance is used instead
* @return instance of the Collection
*/
public synchronized Collection getCol(Context _context) {
// Open collection
Context context = AnkiDroidApp.getInstance();
if (!colIsOpen()) {
String path = getCollectionPath(context);
// Check that the directory has been created and initialized
try {
initializeAnkiDroidDirectory(getParentDirectory(path));
// Path to collection, cached for the reopenCollection() method
} catch (StorageAccessException e) {
Timber.e(e, "Could not initialize AnkiDroid directory");
return null;
}
// Open the database
mCollection = openCollection(context, path);
}
return mCollection;
public synchronized Collection getCol(Context context) {
return CollectionManager.getColUnsafe();
}

/**
Expand Down Expand Up @@ -260,29 +219,15 @@ public synchronized Collection getColSafe(Context context) {
*/
public synchronized void closeCollection(boolean save, String reason) {
Timber.i("closeCollection: %s", reason);
if (mCollection != null) {
mCollection.close(save);
}
CollectionManager.closeCollectionBlocking(save);
}

/**
* Replace the collection with the provided colpkg file if it is valid.
*/
public synchronized void importColpkg(Context context, String colpkgPath) {
Backend backend = getOrCreateBackend(context);
if (mCollection != null) {
mCollection.close(true);
}
String colPath = getCollectionPath(context);
importCollectionPackage(backend, colPath, colpkgPath);
getCol(context);
}

/**
* @return Whether or not {@link Collection} and its child database are open.
*/
public boolean colIsOpen() {
return mCollection != null && !mCollection.isDbClosed();
return CollectionManager.isOpenUnsafe();
}

/**
Expand Down Expand Up @@ -621,13 +566,6 @@ public enum CollectionOpenFailure {

@VisibleForTesting(otherwise = VisibleForTesting.NONE)
public void setColForTests(Collection col) {
if (col == null) {
try {
mCollection.close();
} catch (Exception exc) {
// may not be open
}
}
this.mCollection = col;
CollectionManager.setColForTests(col);
}
}
Loading