let database sync run in the background. not handling all cases yet.

This commit is contained in:
Philipp Crocoll
2025-05-13 09:23:57 +02:00
parent fefcf8f30e
commit c3b6612591
10 changed files with 383 additions and 37 deletions

View File

@@ -19,13 +19,199 @@ using Android.App;
using Android.Content;
using Android.OS;
using Java.Lang;
using Java.Security;
using KeePassLib.Interfaces;
using System.Threading.Tasks;
using Enum = System.Enum;
namespace keepass2android
{
/// <summary>
/// Class to run a task while a progress dialog is shown
/// </summary>
public class BlockingOperationRunner
public class BackgroundOperationRunner
{
//singleton instance
private static BackgroundOperationRunner _instance = null;
public static BackgroundOperationRunner Instance
{
get
{
if (_instance == null)
{
_instance = new BackgroundOperationRunner();
}
return _instance;
}
}
private BackgroundOperationRunner()
{
//private constructor
}
private readonly Queue<OperationWithFinishHandler> _taskQueue = new Queue<OperationWithFinishHandler>();
private readonly object _taskQueueLock = new object();
private Java.Lang.Thread? _thread = null;
private ProgressUiAsStatusLoggerAdapter _statusLogger = null;
public void Run(Activity activity, IKp2aApp app, OperationWithFinishHandler operation)
{
lock (Instance._taskQueueLock)
{
_taskQueue.Enqueue(operation);
SetNewActiveActivity(activity, app);
// Start thread to run the task (unless it's already running)
if (_thread == null)
{
_statusLogger.StartLogging("", false);
_thread = new Java.Lang.Thread(() =>
{
while (true)
{
OperationWithFinishHandler task;
lock (_taskQueueLock)
{
if (!_taskQueue.Any())
{
_thread = null;
_statusLogger.EndLogging();
break;
}
else
{
task = _taskQueue.Dequeue();
}
}
task.Run();
}
});
_thread.Start();
}
}
}
public void SetNewActiveActivity(Activity? activity, IKp2aApp app)
{
lock (_taskQueueLock)
{
var progressUi = (activity as IProgressUiProvider)?.ProgressUi;
if (_statusLogger == null)
{
_statusLogger = new ProgressUiAsStatusLoggerAdapter(progressUi, app);
}
else
{
_statusLogger.SetNewProgressUi(progressUi);
}
foreach (var task in _taskQueue)
{
task.ActiveActivity = activity;
task.SetStatusLogger(_statusLogger);
}
}
}
}
public class ProgressUiAsStatusLoggerAdapter : IKp2aStatusLogger
{
private IProgressUi? _progressUi;
private readonly IKp2aApp _app;
private string _lastMessage = "";
private string _lastSubMessage = "";
private bool _isVisible = false;
public ProgressUiAsStatusLoggerAdapter(IProgressUi progressUi, IKp2aApp app)
{
_progressUi = progressUi;
_app = app;
}
public void SetNewProgressUi(IProgressUi progressUi)
{
_progressUi = progressUi;
if (_isVisible)
{
progressUi?.Show();
progressUi?.UpdateMessage(_lastMessage);
progressUi?.UpdateSubMessage(_lastSubMessage);
}
else
{
progressUi?.Hide();
}
}
public void StartLogging(string strOperation, bool bWriteOperationToLog)
{
_progressUi?.Show();
_isVisible = true;
}
public void EndLogging()
{
_progressUi?.Hide();
_isVisible = false;
}
public bool SetProgress(uint uPercent)
{
return true;
}
public bool SetText(string strNewText, LogStatusType lsType)
{
if (strNewText.StartsWith("KP2AKEY_"))
{
UiStringKey key;
if (Enum.TryParse(strNewText.Substring("KP2AKEY_".Length), true, out key))
{
UpdateMessage(_app.GetResourceString(key));
return true;
}
}
UpdateMessage(strNewText);
return true;
}
public void UpdateMessage(string message)
{
_progressUi?.UpdateMessage(message);
_lastMessage = message;
}
public void UpdateSubMessage(string submessage)
{
_progressUi?.UpdateSubMessage(submessage);
_lastSubMessage = submessage;
}
public bool ContinueWork()
{
return true;
}
public void UpdateMessage(UiStringKey stringKey)
{
if (_app != null)
UpdateMessage(_app.GetResourceString(stringKey));
}
}
/// <summary>
/// Class to run a task while a progress dialog is shown
/// </summary>
public class BlockingOperationRunner
{
//for handling Activity recreation situations, we need access to the currently active task. It must hold that there is no more than one active task.
private static BlockingOperationRunner _currentTask = null;

View File

@@ -27,6 +27,20 @@ namespace keepass2android
void UpdateMessage(UiStringKey stringKey);
}
public interface IProgressUi
{
void Show();
void Hide();
void UpdateMessage(String message);
void UpdateSubMessage(String submessage);
}
public interface IProgressUiProvider
{
IProgressUi? ProgressUi { get; }
}
public class Kp2aNullStatusLogger : IKp2aStatusLogger
{
public void StartLogging(string strOperation, bool bWriteOperationToLog)

View File

@@ -56,6 +56,7 @@ using Android.Util;
using AndroidX.Core.Content;
using Google.Android.Material.Dialog;
using keepass2android;
using keepass2android.views;
namespace keepass2android
{
@@ -97,7 +98,7 @@ namespace keepass2android
[Activity (Label = "@string/app_name", ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden,
Theme = "@style/Kp2aTheme_ActionBar")]
public class EntryActivity : LockCloseActivity
public class EntryActivity : LockCloseActivity, IProgressUiProvider
{
public const String KeyEntry = "entry";
public const String KeyRefreshPos = "refresh_pos";
@@ -1603,5 +1604,7 @@ namespace keepass2android
imageViewerIntent.PutExtra("EntryKey", key);
StartActivity(imageViewerIntent);
}
}
public IProgressUi? ProgressUi => FindViewById<BackgroundOperationContainer>(Resource.Id.background_ops_container);
}
}

View File

@@ -43,11 +43,12 @@ using keepass2android;
using KeeTrayTOTP.Libraries;
using AndroidX.AppCompat.Widget;
using Google.Android.Material.Dialog;
using keepass2android.views;
using SearchView = AndroidX.AppCompat.Widget.SearchView;
namespace keepass2android
{
public abstract class GroupBaseActivity : LockCloseActivity
public abstract class GroupBaseActivity : LockCloseActivity, IProgressUiProvider
{
public const String KeyEntry = "entry";
public const String KeyMode = "mode";
@@ -1408,6 +1409,14 @@ namespace keepass2android
{
GroupEditActivity.Launch(this, pwGroup.ParentGroup, pwGroup);
}
public IProgressUi ProgressUi
{
get
{
return FindViewById<BackgroundOperationContainer>(Resource.Id.background_ops_container);
}
}
}
public class GroupListFragment : ListFragment, AbsListView.IMultiChoiceModeListener

View File

@@ -105,6 +105,7 @@ namespace keepass2android
protected override void OnStart()
{
BlockingOperationRunner.SetNewActiveActivity(this);
BackgroundOperationRunner.Instance.SetNewActiveActivity(this, App.Kp2a);
base.OnStart();
Kp2aLog.Log(ClassName + ".OnStart" + " " + ID);
}

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/background_ops_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/md_theme_surfaceVariant"
android:orientation="vertical"
>
<com.google.android.material.progressindicator.LinearProgressIndicator
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true" />
<TextView
android:id="@+id/background_ops_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="0dp"
android:layout_marginBottom="3dp"
android:text="" />
<TextView
android:id="@+id/background_ops_submessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="0dp"
android:layout_marginBottom="3dp"
android:textSize="12sp"
android:text="" />
</LinearLayout>

View File

@@ -4,18 +4,35 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<LinearLayout android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<keepass2android.views.BackgroundOperationContainer
android:visibility="gone"
android:id="@+id/background_ops_container"
android:layout_below="@id/top"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<ScrollView
android:id="@+id/entry_scroll"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:fillViewport="true"
android:background="?android:attr/colorBackground"
android:scrollbarStyle="insideOverlay">
<keepass2android.view.EntryContentsView
android:id="@+id/entry_contents"
android:layout_height="wrap_content"
android:layout_width="fill_parent" />
</ScrollView>
</LinearLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/entry_edit"
android:layout_width="wrap_content"

View File

@@ -355,29 +355,14 @@
android:layout_above="@id/bottom_bar"
android:background="#b8b8b8" />
<LinearLayout
android:id="@+id/background_ops_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/md_theme_surfaceVariant"
android:layout_below="@id/top"
android:orientation="vertical" >
<keepass2android.views.BackgroundOperationContainer
android:visibility="gone"
android:id="@+id/background_ops_container"
android:layout_below="@id/top"
android:layout_width="match_parent"
android:layout_height="wrap_content"
<com.google.android.material.progressindicator.LinearProgressIndicator
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true" />
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="Synchronisierung läuft..." />
</LinearLayout>
/>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/main_content"

View File

@@ -1,5 +1,6 @@
using System;
using Android.App;
using Android.OS;
using Android.Widget;
using keepass2android.Io;
using KeePassLib.Serialization;
@@ -56,17 +57,17 @@ namespace keepass2android
OnOperationFinishedHandler onOperationFinishedHandler = new ActionOnOperationFinished(_activity, (success, message, activity) =>
{
if (!String.IsNullOrEmpty(message))
App.Kp2a.ShowMessage(activity, message, MessageSeverity.Error);
App.Kp2a.ShowMessage(activity, message, success ? MessageSeverity.Info : MessageSeverity.Error);
// Tell the adapter to refresh it's list
BaseAdapter adapter = (activity as GroupBaseActivity)?.ListAdapter;
adapter?.NotifyDataSetChanged();
BaseAdapter adapter = (activity as GroupBaseActivity)?.ListAdapter;
new Handler(Looper.MainLooper).Post(() => adapter?.NotifyDataSetChanged());
if (App.Kp2a.CurrentDb?.OtpAuxFileIoc != null)
{
var task2 = new SyncOtpAuxFile(_activity, App.Kp2a.CurrentDb.OtpAuxFileIoc);
//TODO new BackgroundOperationRunner(App.Kp2a, activity, task2).Run(true);
BackgroundOperationRunner.Instance.Run(_activity, App.Kp2a, task2);
}
});
@@ -82,8 +83,7 @@ namespace keepass2android
task = new CheckDatabaseForChanges(_activity, App.Kp2a, onOperationFinishedHandler);
}
//TODO var backgroundTaskRunner = new BackgroundOperationRunner(App.Kp2a, _activity, task);
//TODO backgroundTaskRunner.Run();
BackgroundOperationRunner.Instance.Run(_activity, App.Kp2a, task);
}
}

View File

@@ -18,6 +18,7 @@ using Android.Views;
using Android.Widget;
using Google.Android.Material.Dialog;
using keepass2android;
using KeePassLib.Interfaces;
namespace keepass2android.views
{
@@ -103,4 +104,92 @@ namespace keepass2android.views
}
}
public class BackgroundOperationContainer : LinearLayout, IProgressUi
{
protected BackgroundOperationContainer(IntPtr javaReference, JniHandleOwnership transfer) : base(
javaReference, transfer)
{
}
public BackgroundOperationContainer(Context context) : base(context)
{
}
public BackgroundOperationContainer(Context context, IAttributeSet attrs) : base(context, attrs)
{
Initialize(attrs);
}
public BackgroundOperationContainer(Context context, IAttributeSet attrs, int defStyle) : base(context,
attrs, defStyle)
{
Initialize(attrs);
}
private void Initialize(IAttributeSet attrs)
{
LayoutInflater inflater = (LayoutInflater)Context.GetSystemService(Context.LayoutInflaterService);
inflater.Inflate(Resource.Layout.background_operation_container, this);
}
public void Show()
{
new Handler(Looper.MainLooper).Post(() =>
{
Visibility = ViewStates.Visible;
FindViewById<TextView>(Resource.Id.background_ops_message)!.Visibility = ViewStates.Gone;
FindViewById<TextView>(Resource.Id.background_ops_submessage)!.Visibility = ViewStates.Gone;
});
}
public void Hide()
{
new Handler(Looper.MainLooper).Post(() =>
{
String activityType = Context.GetType().FullName;
Kp2aLog.Log("Hiding background ops container in" + activityType);
Visibility = ViewStates.Gone;
});
}
public void UpdateMessage(string message)
{
new Handler(Looper.MainLooper).Post(() =>
{
TextView messageTextView = FindViewById<TextView>(Resource.Id.background_ops_message)!;
if (string.IsNullOrEmpty(message))
{
messageTextView.Visibility = ViewStates.Gone;
}
else
{
messageTextView.Visibility = ViewStates.Visible;
messageTextView.Text = message;
}
});
}
public void UpdateSubMessage(string submessage)
{
new Handler(Looper.MainLooper).Post(() =>
{
TextView subMessageTextView = FindViewById<TextView>(Resource.Id.background_ops_submessage)!;
if (string.IsNullOrEmpty(submessage))
{
subMessageTextView.Visibility = ViewStates.Gone;
}
else
{
subMessageTextView.Visibility = ViewStates.Visible;
subMessageTextView.Text = submessage;
}
});
}
}
}