diff --git a/Apps/Contoso.Android.Puppet/Constants.cs b/Apps/Contoso.Android.Puppet/Constants.cs index a4c68e015..c840a814e 100644 --- a/Apps/Contoso.Android.Puppet/Constants.cs +++ b/Apps/Contoso.Android.Puppet/Constants.cs @@ -10,5 +10,6 @@ public static class Constants public const string Info = "Info"; public const string Warning = "Warning"; public const string Error = "Error"; + public const string StorageSizeKey = "StorageSizeKey"; } } diff --git a/Apps/Contoso.Android.Puppet/MainActivity.cs b/Apps/Contoso.Android.Puppet/MainActivity.cs index 23bfec37d..f48d6a3db 100644 --- a/Apps/Contoso.Android.Puppet/MainActivity.cs +++ b/Apps/Contoso.Android.Puppet/MainActivity.cs @@ -3,6 +3,7 @@ using System.Linq; using Android.App; +using Android.Content; using Android.Content.PM; using Android.OS; using Android.Support.Design.Widget; @@ -47,14 +48,20 @@ protected override void OnCreate(Bundle savedInstanceState) Crashes.SendingErrorReport += SendingErrorReportHandler; Crashes.SentErrorReport += SentErrorReportHandler; Crashes.FailedToSendErrorReport += FailedToSendErrorReportHandler; - // Set callbacks Crashes.ShouldProcessErrorReport = ShouldProcess; Crashes.ShouldAwaitUserConfirmation = ConfirmationHandler; Distribute.ReleaseAvailable = OnReleaseAvailable; + Distribute.NoReleaseAvailable = OnNoReleaseAvailable; AppCenterLog.Assert(LogTag, "AppCenter.Configured=" + AppCenter.Configured); AppCenter.SetLogUrl("https://in-integration.dev.avalanch.es"); + var prefs = GetSharedPreferences("AppCenter", FileCreationMode.Private); + var storageSizeValue = prefs.GetLong(Constants.StorageSizeKey, 0); + if (storageSizeValue > 0) + { + AppCenter.SetMaxStorageSizeAsync(storageSizeValue); + } Distribute.SetInstallUrl("https://install.portal-server-core-integration.dev.avalanch.es"); Distribute.SetApiUrl("https://asgard-int.trafficmanager.net/api/v0.1"); AppCenter.Start("bff0949b-7970-439d-9745-92cdc59b10fe", typeof(Analytics), typeof(Crashes), typeof(Distribute)); @@ -119,6 +126,11 @@ bool ConfirmationHandler() return true; } + void OnNoReleaseAvailable() + { + AppCenterLog.Info(LogTag, "No release available callback invoked."); + } + bool OnReleaseAvailable(ReleaseDetails releaseDetails) { AppCenterLog.Info(LogTag, "OnReleaseAvailable id=" + releaseDetails.Id diff --git a/Apps/Contoso.Android.Puppet/ModulePages/AppCenterFragment.cs b/Apps/Contoso.Android.Puppet/ModulePages/AppCenterFragment.cs index cedaae38c..57eb647cd 100644 --- a/Apps/Contoso.Android.Puppet/ModulePages/AppCenterFragment.cs +++ b/Apps/Contoso.Android.Puppet/ModulePages/AppCenterFragment.cs @@ -38,6 +38,8 @@ public class AppCenterFragment : PageFragment private TextView LogWriteLevelLabel; private Button LogWriteButton; private EditText UserIdText; + private Button SaveStorageSizeButton; + private EditText StorageSizeText; public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -56,14 +58,24 @@ public override void OnViewCreated(View view, Bundle savedInstanceState) LogWriteLevelLabel = view.FindViewById(Resource.Id.write_log_level) as TextView; LogWriteButton = view.FindViewById(Resource.Id.write_log) as Button; UserIdText = view.FindViewById(Resource.Id.write_user_id) as EditText; + SaveStorageSizeButton = view.FindViewById(Resource.Id.save_storage_size) as Button; + StorageSizeText = view.FindViewById(Resource.Id.write_storage_size) as EditText; // Subscribe to events. AppCenterEnabledSwitch.CheckedChange += UpdateEnabled; ((View)LogLevelLabel.Parent).Click += LogLevelClicked; ((View)LogWriteLevelLabel.Parent).Click += LogWriteLevelClicked; LogWriteButton.Click += WriteLog; + SaveStorageSizeButton.Click += SaveStorageSize; UserIdText.KeyPress += UserIdTextKeyPressedHandler; + // Set max storage size value. + var prefs = Context.GetSharedPreferences("AppCenter", FileCreationMode.Private); + var storageSizeValue = prefs.GetLong(Constants.StorageSizeKey, 0); + if (storageSizeValue > 0) + { + StorageSizeText.Text = storageSizeValue.ToString(); + } UpdateState(); } @@ -130,5 +142,16 @@ private void WriteLog(object sender, EventArgs e) string tag = LogWriteTagText.Text; LogFunctions[mLogWriteLevel](tag, message); } + + private void SaveStorageSize(object sender, EventArgs e) + { + var inputText = StorageSizeText.Text; + var size = string.IsNullOrEmpty(inputText) ? 0 : long.Parse(inputText); + AppCenter.SetMaxStorageSizeAsync(size); + var prefs = Context.GetSharedPreferences("AppCenter", FileCreationMode.Private); + var prefEditor = prefs.Edit(); + prefEditor.PutLong(Constants.StorageSizeKey, size); + prefEditor.Commit(); + } } } diff --git a/Apps/Contoso.Android.Puppet/Properties/AndroidManifest.xml b/Apps/Contoso.Android.Puppet/Properties/AndroidManifest.xml index b3304b147..4a3bc78e2 100644 --- a/Apps/Contoso.Android.Puppet/Properties/AndroidManifest.xml +++ b/Apps/Contoso.Android.Puppet/Properties/AndroidManifest.xml @@ -1,5 +1,5 @@ - + diff --git a/Apps/Contoso.Android.Puppet/Properties/AssemblyInfo.cs b/Apps/Contoso.Android.Puppet/Properties/AssemblyInfo.cs index 582ada77c..3729514fb 100644 --- a/Apps/Contoso.Android.Puppet/Properties/AssemblyInfo.cs +++ b/Apps/Contoso.Android.Puppet/Properties/AssemblyInfo.cs @@ -28,5 +28,5 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("0.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] -[assembly: AssemblyInformationalVersion("4.0.0-SNAPSHOT")] +[assembly: AssemblyFileVersion("4.1.0.0")] +[assembly: AssemblyInformationalVersion("4.1.0-SNAPSHOT")] diff --git a/Apps/Contoso.Android.Puppet/Resources/layout/AppCenter.axml b/Apps/Contoso.Android.Puppet/Resources/layout/AppCenter.axml index 53f44bf9e..954ad2dee 100644 --- a/Apps/Contoso.Android.Puppet/Resources/layout/AppCenter.axml +++ b/Apps/Contoso.Android.Puppet/Resources/layout/AppCenter.axml @@ -1,114 +1,143 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + @@ -224,6 +269,7 @@ + diff --git a/Apps/Contoso.iOS.Puppet/ModulePages/AppCenterController.cs b/Apps/Contoso.iOS.Puppet/ModulePages/AppCenterController.cs index 0545a40cd..f5a4c9ced 100644 --- a/Apps/Contoso.iOS.Puppet/ModulePages/AppCenterController.cs +++ b/Apps/Contoso.iOS.Puppet/ModulePages/AppCenterController.cs @@ -37,6 +37,14 @@ public override void ViewDidAppear(bool animated) AppCenterEnabledSwitch.On = AppCenter.IsEnabledAsync().Result; LogLevelLabel.Text = LogLevelNames[AppCenter.LogLevel]; LogWriteLevelLabel.Text = LogLevelNames[mLogWriteLevel]; + + // Set max storage size value. + var plist = NSUserDefaults.StandardUserDefaults; + var storageSizeValue = plist.IntForKey(Constants.StorageSizeKey); + if (storageSizeValue > 0) + { + StorageSizeText.Text = storageSizeValue.ToString(); + } } partial void UpdateUserId(UITextField sender) @@ -83,5 +91,16 @@ partial void WriteLog() string tag = LogWriteTag.Text; LogFunctions[mLogWriteLevel](tag, message); } + + partial void SaveStorageSize(NSObject sender) + { + var inputText = StorageSizeText.Text; + if (int.TryParse(inputText, out var size)) + { + AppCenter.SetMaxStorageSizeAsync(size); + var plist = NSUserDefaults.StandardUserDefaults; + plist.SetInt(size, Constants.StorageSizeKey); + } + } } } diff --git a/Apps/Contoso.iOS.Puppet/ModulePages/AppCenterController.designer.cs b/Apps/Contoso.iOS.Puppet/ModulePages/AppCenterController.designer.cs index f212f56d4..178ae0f0f 100644 --- a/Apps/Contoso.iOS.Puppet/ModulePages/AppCenterController.designer.cs +++ b/Apps/Contoso.iOS.Puppet/ModulePages/AppCenterController.designer.cs @@ -1,84 +1,87 @@ // WARNING // -// This file has been generated automatically by Visual Studio from the outlets and -// actions declared in your storyboard file. -// Manual changes to this file will not be maintained. +// This file has been generated automatically by Visual Studio to store outlets and +// actions made in the UI designer. If it is removed, they will be lost. +// Manual changes to this file may not be handled correctly. // using Foundation; -using System; using System.CodeDom.Compiler; namespace Contoso.iOS.Puppet { - [Register ("AppCenterController")] - partial class AppCenterController - { - [Outlet] - UIKit.UILabel LogLevelLabel { get; set; } - - - [Outlet] - UIKit.UILabel LogWriteLevelLabel { get; set; } - - - [Outlet] - UIKit.UITextField LogWriteMessage { get; set; } - - - [Outlet] - UIKit.UITextField LogWriteTag { get; set; } - - - [Outlet] - UIKit.UISwitch AppCenterEnabledSwitch { get; set; } - - [Outlet] - [GeneratedCode ("iOS Designer", "1.0")] - UIKit.UITextField UserIdTextField { get; set; } - - - [Action ("UpdateEnabled")] - partial void UpdateEnabled (); - - - [Action ("WriteLog")] - partial void WriteLog (); - - [Action ("UpdateUserId:")] - [GeneratedCode ("iOS Designer", "1.0")] - partial void UpdateUserId (UIKit.UITextField sender); - - void ReleaseDesignerOutlets () - { - if (AppCenterEnabledSwitch != null) { - AppCenterEnabledSwitch.Dispose (); - AppCenterEnabledSwitch = null; - } - - if (LogLevelLabel != null) { - LogLevelLabel.Dispose (); - LogLevelLabel = null; - } - - if (LogWriteLevelLabel != null) { - LogWriteLevelLabel.Dispose (); - LogWriteLevelLabel = null; - } - - if (LogWriteMessage != null) { - LogWriteMessage.Dispose (); - LogWriteMessage = null; - } - - if (LogWriteTag != null) { - LogWriteTag.Dispose (); - LogWriteTag = null; - } - - if (UserIdTextField != null) { - UserIdTextField.Dispose (); - UserIdTextField = null; - } - } - } -} \ No newline at end of file + [Register ("AppCenterController")] + partial class AppCenterController + { + [Outlet] + UIKit.UISwitch AppCenterEnabledSwitch { get; set; } + + [Outlet] + UIKit.UILabel LogLevelLabel { get; set; } + + [Outlet] + UIKit.UILabel LogWriteLevelLabel { get; set; } + + [Outlet] + UIKit.UITextField LogWriteMessage { get; set; } + + [Outlet] + UIKit.UITextField LogWriteTag { get; set; } + + [Outlet] + UIKit.UITextField StorageSizeText { get; set; } + + [Outlet] + [GeneratedCode ("iOS Designer", "1.0")] + UIKit.UITextField UserIdTextField { get; set; } + + [Action ("SaveStorageSize:")] + partial void SaveStorageSize (Foundation.NSObject sender); + + [Action ("UpdateEnabled")] + partial void UpdateEnabled (); + + [Action ("UpdateUserId:")] + partial void UpdateUserId (UIKit.UITextField sender); + + [Action ("WriteLog")] + partial void WriteLog (); + + void ReleaseDesignerOutlets () + { + if (AppCenterEnabledSwitch != null) { + AppCenterEnabledSwitch.Dispose (); + AppCenterEnabledSwitch = null; + } + + if (LogLevelLabel != null) { + LogLevelLabel.Dispose (); + LogLevelLabel = null; + } + + if (LogWriteLevelLabel != null) { + LogWriteLevelLabel.Dispose (); + LogWriteLevelLabel = null; + } + + if (LogWriteMessage != null) { + LogWriteMessage.Dispose (); + LogWriteMessage = null; + } + + if (StorageSizeText != null) { + StorageSizeText.Dispose (); + StorageSizeText = null; + } + + if (LogWriteTag != null) { + LogWriteTag.Dispose (); + LogWriteTag = null; + } + + if (UserIdTextField != null) { + UserIdTextField.Dispose (); + UserIdTextField = null; + } + } + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md index db47ec59e..1e2644cfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,38 @@ # App Center SDK for .NET Change Log +## Version 4.1.0 + +### AppCenter + +* **[Feature]** Add a `SetMaxStorageSizeAsync` API which allows setting a maximum size limit on the local SQLite storage. The default value is 10MiB. + +#### iOS + +* **[Fix]** Fix a crash when SQLite returns zero for `page_size`. + +### App Center Distribute + +#### iOS + +* **[Feature]** Add `WillExitApp` callback to distribute listener. + +#### Android + +* **[Fix]** Fix a crash when the app is trying to open the system settings screen from the background. +* **[Fix]** Fix browser opening when using a private distribution group on Android 11. + +#### iOS/Android + + * **[Feature]** Add `NoReleaseAvailable` callback to distribute listener. + + ### App Center Crashes + + #### Android + +* **[Fix]** Fix removing throwable files after rewriting error logs due to small database size. + +___ + ## Version 4.0.0 ### App Center diff --git a/SDK/AppCenter/Microsoft.AppCenter.Android.Bindings/Microsoft.AppCenter.Android.Bindings.csproj b/SDK/AppCenter/Microsoft.AppCenter.Android.Bindings/Microsoft.AppCenter.Android.Bindings.csproj index 7013dd0e9..1e0022eee 100644 --- a/SDK/AppCenter/Microsoft.AppCenter.Android.Bindings/Microsoft.AppCenter.Android.Bindings.csproj +++ b/SDK/AppCenter/Microsoft.AppCenter.Android.Bindings/Microsoft.AppCenter.Android.Bindings.csproj @@ -13,6 +13,7 @@ Microsoft.AppCenter.Android.Bindings 512 v8.0 + class-parse diff --git a/SDK/AppCenter/Microsoft.AppCenter.Android.Bindings/Properties/AssemblyInfo.cs b/SDK/AppCenter/Microsoft.AppCenter.Android.Bindings/Properties/AssemblyInfo.cs index de69baced..9924a7740 100644 --- a/SDK/AppCenter/Microsoft.AppCenter.Android.Bindings/Properties/AssemblyInfo.cs +++ b/SDK/AppCenter/Microsoft.AppCenter.Android.Bindings/Properties/AssemblyInfo.cs @@ -29,5 +29,5 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("0.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] -[assembly: AssemblyInformationalVersion("4.0.0-SNAPSHOT")] +[assembly: AssemblyFileVersion("4.1.0.0")] +[assembly: AssemblyInformationalVersion("4.1.0-SNAPSHOT")] diff --git a/SDK/AppCenter/Microsoft.AppCenter.Android/AppCenter.cs b/SDK/AppCenter/Microsoft.AppCenter.Android/AppCenter.cs index 50b9d9024..e71db8708 100644 --- a/SDK/AppCenter/Microsoft.AppCenter.Android/AppCenter.cs +++ b/SDK/AppCenter/Microsoft.AppCenter.Android/AppCenter.cs @@ -208,5 +208,11 @@ internal static void PlatformUnsetInstance() { AndroidAppCenter.UnsetInstance(); } + + static Task PlatformSetMaxStorageSizeAsync(long sizeInBytes) + { + var future = AndroidAppCenter.SetMaxStorageSize(sizeInBytes); + return Task.Run(() => (bool)future.Get()); + } } } diff --git a/SDK/AppCenter/Microsoft.AppCenter.Android/Microsoft.AppCenter.Android.csproj b/SDK/AppCenter/Microsoft.AppCenter.Android/Microsoft.AppCenter.Android.csproj index 7675d16da..a92b09a3d 100644 --- a/SDK/AppCenter/Microsoft.AppCenter.Android/Microsoft.AppCenter.Android.csproj +++ b/SDK/AppCenter/Microsoft.AppCenter.Android/Microsoft.AppCenter.Android.csproj @@ -15,6 +15,7 @@ Resources\Resource.Designer.cs Off v8.0 + class-parse diff --git a/SDK/AppCenter/Microsoft.AppCenter.Android/Properties/AssemblyInfo.cs b/SDK/AppCenter/Microsoft.AppCenter.Android/Properties/AssemblyInfo.cs index b0698016b..201a77c0c 100644 --- a/SDK/AppCenter/Microsoft.AppCenter.Android/Properties/AssemblyInfo.cs +++ b/SDK/AppCenter/Microsoft.AppCenter.Android/Properties/AssemblyInfo.cs @@ -29,6 +29,6 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("0.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] -[assembly: AssemblyInformationalVersion("4.0.0-SNAPSHOT")] +[assembly: AssemblyFileVersion("4.1.0.0")] +[assembly: AssemblyInformationalVersion("4.1.0-SNAPSHOT")] [assembly: InternalsVisibleTo("Microsoft.AppCenter.Test.Functional")] diff --git a/SDK/AppCenter/Microsoft.AppCenter.Shared/AppCenter.cs b/SDK/AppCenter/Microsoft.AppCenter.Shared/AppCenter.cs index 79705bc59..c8b66dbe7 100644 --- a/SDK/AppCenter/Microsoft.AppCenter.Shared/AppCenter.cs +++ b/SDK/AppCenter/Microsoft.AppCenter.Shared/AppCenter.cs @@ -198,6 +198,24 @@ public static void SetCustomProperties(CustomProperties customProperties) PlatformSetCustomProperties(customProperties); } + /// + /// Set the maximum size of the internal storage. + /// This method must be called before App Center is started. This method is only intended for applications. + /// + /// + /// This only sets the maximum size of the database, but App Center modules might store additional data. + /// The value passed to this method is not persisted on disk. The default maximum database size is 10485760 bytes (10 MiB). + /// + /// + /// Maximum size of the internal storage in bytes. This will be rounded up to the nearest multiple of a SQLite page size (default is 4096 bytes). + /// Values below 20,480 bytes (20 KiB) will be ignored. + /// + /// true if changing the size was successful. + public static Task SetMaxStorageSizeAsync(long sizeInBytes) + { + return PlatformSetMaxStorageSizeAsync(sizeInBytes); + } + internal static void UnsetInstance() { PlatformUnsetInstance(); diff --git a/SDK/AppCenter/Microsoft.AppCenter.Shared/WrapperSdk.cs b/SDK/AppCenter/Microsoft.AppCenter.Shared/WrapperSdk.cs index 9dd906470..b4d3e9624 100644 --- a/SDK/AppCenter/Microsoft.AppCenter.Shared/WrapperSdk.cs +++ b/SDK/AppCenter/Microsoft.AppCenter.Shared/WrapperSdk.cs @@ -8,6 +8,6 @@ public partial class WrapperSdk public const string Name = "appcenter.xamarin"; /* We can't use reflection for assemblyInformationalVersion on iOS with "Link All" optimization. */ - internal const string Version = "4.0.0-SNAPSHOT"; + internal const string Version = "4.1.0-SNAPSHOT"; } } diff --git a/SDK/AppCenter/Microsoft.AppCenter.UWP/Microsoft.AppCenter.UWP.csproj b/SDK/AppCenter/Microsoft.AppCenter.UWP/Microsoft.AppCenter.UWP.csproj index b836d501e..1eb68953e 100644 --- a/SDK/AppCenter/Microsoft.AppCenter.UWP/Microsoft.AppCenter.UWP.csproj +++ b/SDK/AppCenter/Microsoft.AppCenter.UWP/Microsoft.AppCenter.UWP.csproj @@ -5,9 +5,9 @@ uap10.0.16299;net461 Microsoft Corporation Microsoft Corp. All rights reserved. - 4.0.0-SNAPSHOT + 4.1.0-SNAPSHOT 0.0.0.0 - 4.0.0.0 + 4.1.0.0 Microsoft.AppCenter bin\$(Configuration)\$(TargetFramework)\Microsoft.AppCenter.xml diff --git a/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/AppCenter.cs b/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/AppCenter.cs index 9b215586d..60a29d424 100644 --- a/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/AppCenter.cs +++ b/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/AppCenter.cs @@ -28,6 +28,8 @@ public partial class AppCenter private const string NotConfiguredMessage = "App Center hasn't been configured. " + "You need to call AppCenter.Start with appSecret or AppCenter.Configure first."; private const string ChannelName = "core"; + private const long MinimumStorageSize = 1024 * 24; + private const long DefaultStorageMaxSize = 1024 * 1024 * 10; // The lock is static. Instance methods are not necessarily thread safe, but static methods are private static readonly object AppCenterLock = new object(); @@ -44,6 +46,8 @@ public partial class AppCenter private string _logUrl; private bool _instanceConfigured; private string _appSecret; + private long _storageMaxSize; + private TaskCompletionSource _storageTaskCompletionSource; #region static @@ -246,6 +250,14 @@ static void PlatformStart(string appSecret, params Type[] services) } } + static Task PlatformSetMaxStorageSizeAsync(long sizeInBytes) + { + lock (AppCenterLock) + { + return Instance.SetInstanceStorageMaxSize(sizeInBytes); + } + } + /// /// A wrapper SDK can use this method to pass extra information to device properties. /// @@ -315,6 +327,32 @@ private void SetInstanceLogUrl(string logUrl) _channelGroup?.SetLogUrl(logUrl); } + private Task SetInstanceStorageMaxSize(long storageMaxSize) + { + var resultTaskCompletionSource = new TaskCompletionSource(); + if (_instanceConfigured) + { + AppCenterLog.Error(AppCenterLog.LogTag, "SetMaxStorageSize may not be called after App Center has been configured."); + resultTaskCompletionSource.SetResult(false); + return resultTaskCompletionSource.Task; + } + if (_storageMaxSize > 0) + { + AppCenterLog.Error(AppCenterLog.LogTag, "SetMaxStorageSize may only be called once per app launch."); + resultTaskCompletionSource.SetResult(false); + return resultTaskCompletionSource.Task; + } + if (storageMaxSize < MinimumStorageSize) + { + AppCenterLog.Error(AppCenterLog.LogTag, $"Maximum storage size must be at least {MinimumStorageSize} bytes."); + resultTaskCompletionSource.SetResult(false); + return resultTaskCompletionSource.Task; + } + _storageMaxSize = storageMaxSize; + _storageTaskCompletionSource = resultTaskCompletionSource; + return _storageTaskCompletionSource.Task; + } + private void SetInstanceCustomProperties(CustomProperties customProperties) { if (!Configured) @@ -356,6 +394,14 @@ internal void InstanceConfigure(string appSecretOrSecrets) { _channelGroup.SetLogUrl(_logUrl); } + if (_storageMaxSize > 0) + { + _channelGroup.SetMaxStorageSizeAsync(_storageMaxSize).ContinueWith((task) => _storageTaskCompletionSource?.SetResult(task.Result)); + } + else + { + _channelGroup.SetMaxStorageSizeAsync(DefaultStorageMaxSize); + } _instanceConfigured = true; AppCenterLog.Info(AppCenterLog.LogTag, "App Center SDK configured successfully."); } diff --git a/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/Channel/ChannelGroup.cs b/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/Channel/ChannelGroup.cs index 55ab3c44a..ef57da69d 100644 --- a/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/Channel/ChannelGroup.cs +++ b/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/Channel/ChannelGroup.cs @@ -114,6 +114,12 @@ public void SetEnabled(bool enabled) } } + public Task SetMaxStorageSizeAsync(long sizeInBytes) + { + ThrowIfDisposed(); + return _storage.SetMaxStorageSizeAsync(sizeInBytes); + } + public Task WaitStorageOperationsAsync() { ThrowIfDisposed(); diff --git a/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/Channel/IChannelGroup.cs b/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/Channel/IChannelGroup.cs index aae51ee15..0190dc382 100644 --- a/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/Channel/IChannelGroup.cs +++ b/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/Channel/IChannelGroup.cs @@ -37,5 +37,15 @@ public interface IChannelGroup : IChannel /// Waits for any running storage operations to complete. /// Task WaitStorageOperationsAsync(); + + /// + /// Set the maximum size of the storage. + /// + /// + /// Maximum size of the storage in bytes. This will be rounded up to the nearest multiple of a SQLite page size (default is 4096 bytes). + /// Values below 20,480 bytes (20 KiB) will be ignored. + /// + /// true if changing the size was successful. + Task SetMaxStorageSizeAsync(long sizeInBytes); } } diff --git a/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/Microsoft.AppCenter.Windows.Shared.projitems b/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/Microsoft.AppCenter.Windows.Shared.projitems index 271d95209..b17fe3fd9 100644 --- a/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/Microsoft.AppCenter.Windows.Shared.projitems +++ b/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/Microsoft.AppCenter.Windows.Shared.projitems @@ -35,6 +35,7 @@ + diff --git a/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/Storage/IStorage.cs b/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/Storage/IStorage.cs index 853f0aba9..7a648c461 100644 --- a/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/Storage/IStorage.cs +++ b/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/Storage/IStorage.cs @@ -74,5 +74,19 @@ public interface IStorage : IDisposable /// The maximum amount of time to wait for remaining tasks /// True if remaining tasks completed in time; false otherwise Task ShutdownAsync(TimeSpan timeout); + + /// + /// Set the maximum size of the storage. + /// + /// + /// This only sets the maximum size of the database, but App Center modules might store additional data. + /// The value passed to this method is not persisted on disk. The default maximum database size is 10485760 bytes (10 MiB). + /// + /// + /// Maximum size of the storage in bytes. This will be rounded up to the nearest multiple of a SQLite page size (default is 4096 bytes). + /// Values below 20,480 bytes (20 KiB) will be ignored. + /// + /// true if changing the size was successful. + Task SetMaxStorageSizeAsync(long sizeInBytes); } } diff --git a/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/Storage/IStorageAdapter.cs b/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/Storage/IStorageAdapter.cs index 983ff3d1e..a95f8c9ae 100644 --- a/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/Storage/IStorageAdapter.cs +++ b/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/Storage/IStorageAdapter.cs @@ -43,8 +43,9 @@ public interface IStorageAdapter : IDisposable /// Column name to match excluded values by. /// Excluded values to match in query. /// Maximum amount of items to select. + /// List of a column names to order selection result ascending. /// Item list with array of objects. Array of objects is object[] representation of columns. - IList Select(string tableName, string columnName, object value, string excludeColumnName, object[] excludeValues, int? limit = null); + IList Select(string tableName, string columnName, object value, string excludeColumnName, object[] excludeValues, int? limit = null, string[] orderList = null); /// /// Inserts data to table. @@ -61,5 +62,25 @@ public interface IStorageAdapter : IDisposable /// Name of column to match value by. /// Array of values to match in a query. void Delete(string tableName, string columnName, params object[] values); + + /// + /// Set the maximum size of the storage. + /// + /// + /// This only sets the maximum size of the database, but App Center modules might store additional data. + /// The value passed to this method is not persisted on disk. The default maximum database size is 10485760 bytes (10 MiB). + /// + /// + /// Maximum size of the storage in bytes. This will be rounded up to the nearest multiple of a SQLite page size (default is 4096 bytes). + /// Values below 20,480 bytes (20 KiB) will be ignored. + /// + /// true if changing the size was successful. + bool SetMaxStorageSize(long sizeInBytes); + + /// + /// Gets the maximum size of the database. + /// + /// The maximum size of database in bytes. + long GetMaxStorageSize(); } } diff --git a/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/Storage/Storage.cs b/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/Storage/Storage.cs index 111dfe9bb..5b52ccb8b 100644 --- a/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/Storage/Storage.cs +++ b/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/Storage/Storage.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AppCenter.Ingestion.Models; @@ -85,11 +86,37 @@ public Task PutLog(string channelName, Log log) return AddTaskToQueue(() => { var logJsonString = LogSerializer.Serialize(log); - _storageAdapter.Insert(TableName, - new[] { ColumnChannelName, ColumnLogName }, - new List { - new object[] {channelName, logJsonString} - }); + var maxSize = _storageAdapter.GetMaxStorageSize(); + var logSize = Encoding.UTF8.GetBytes(logJsonString).Length; + if (maxSize < 0) + { + throw new StorageException("Failed to store a log to the database."); + } + if (maxSize <= logSize) + { + throw new StorageException($"Log is too large ({logSize} bytes) to store in database. " + + $"Current maximum database size is {maxSize} bytes."); + } + while (true) + { + try + { + _storageAdapter.Insert(TableName, + new[] { ColumnChannelName, ColumnLogName }, + new List { + new object[] {channelName, logJsonString} + }); + return; + } + catch (StorageFullException) + { + var oldestLog = _storageAdapter.Select(TableName, ColumnChannelName, channelName, string.Empty, null, 1, new string[] { ColumnIdName }); + if (oldestLog != null && oldestLog.Count > 0 && oldestLog[0].Length > 0) + { + _storageAdapter.Delete(TableName, ColumnIdName, oldestLog[0][0]); + } + } + } }); } @@ -255,6 +282,34 @@ public Task GetLogsAsync(string channelName, int limit, List logs) }); } + /// + /// Set the maximum size of the storage. + /// + /// + /// This only sets the maximum size of the database, but App Center modules might store additional data. + /// The value passed to this method is not persisted on disk. The default maximum database size is 10485760 bytes (10 MiB). + /// + /// + /// Maximum size of the storage in bytes. This will be rounded up to the nearest multiple of a SQLite page size (default is 4096 bytes). + /// Values below 20,480 bytes (20 KiB) will be ignored. + /// + /// true if changing the size was successful. + public Task SetMaxStorageSizeAsync(long sizeInBytes) + { + return AddTaskToQueue(() => + { + try + { + AppCenterLog.Debug(AppCenterLog.LogTag, $"Set max storage size."); + return _storageAdapter.SetMaxStorageSize(sizeInBytes); + } + catch (Exception e) + { + throw new StorageException(e); + } + }); + } + private void ProcessLogIds(string channelName, string batchId, IEnumerable> idPairs) { var ids = new List(); diff --git a/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/Storage/StorageAdapter.cs b/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/Storage/StorageAdapter.cs index 90597b87c..f60f32b96 100644 --- a/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/Storage/StorageAdapter.cs +++ b/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/Storage/StorageAdapter.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; using SQLitePCL; namespace Microsoft.AppCenter.Storage @@ -95,7 +96,7 @@ private object GetColumnValue(sqlite3_stmt stmt, int index) return null; } - private void ExecuteNonSelectionSqlQuery(string query, IList args = null) + private int ExecuteNonSelectionSqlQuery(string query, IList args = null) { var db = _db ?? throw new StorageException("The database wasn't initialized."); var result = raw.sqlite3_prepare_v2(db, query, out var stmt); @@ -120,6 +121,7 @@ private void ExecuteNonSelectionSqlQuery(string query, IList args = null AppCenterLog.Error(AppCenterLog.LogTag, $"Failed to finalize statement, result={result}"); } } + return result; } private List ExecuteSelectionSqlQuery(string query, IList args = null) @@ -151,6 +153,90 @@ private List ExecuteSelectionSqlQuery(string query, IList args } } + private long GetMaxPageCount() + { + return GetPragmaValue("max_page_count"); + } + + private long GetPageCount() + { + return GetPragmaValue("page_count"); + } + + private long GetPageSize() + { + return GetPragmaValue("page_size"); + } + + private long GetPragmaValue(string valueName) + { + var result = ExecuteSelectionSqlQuery($"PRAGMA {valueName};"); + var count = (long)(result.FirstOrDefault()?.FirstOrDefault() ?? 0L); + return count; + } + + public long GetMaxStorageSize() + { + try + { + return GetMaxPageCount() * GetPageSize(); + } + catch (StorageException) + { + AppCenterLog.Error(AppCenterLog.LogTag, $"Could not get max storage size."); + return -1; + } + } + + public bool SetMaxStorageSize(long sizeInBytes) + { + var db = _db ?? throw new StorageException("The database wasn't initialized."); + + // Check the current number of pages in the database to determine whether the requested size will shrink the database. + var currentPageCount = GetPageCount(); + var pageSize = GetPageSize(); + AppCenterLog.Info(AppCenterLog.LogTag, $"Found {currentPageCount} pages in the database."); + var requestedMaxPageCount = Convert.ToBoolean(sizeInBytes % pageSize) ? sizeInBytes / pageSize + 1 : sizeInBytes / pageSize; + if (currentPageCount > requestedMaxPageCount) + { + AppCenterLog.Warn(AppCenterLog.LogTag, $"Cannot change database size to {sizeInBytes} bytes as it would cause a loss of data. " + + "Maximum database size will not be changed."); + return false; + } + else + { + // Attempt to set the limit and check the page count to make sure the given limit works. + var result = raw.sqlite3_exec(db, $"PRAGMA max_page_count = {requestedMaxPageCount};"); + if (result != raw.SQLITE_OK) + { + AppCenterLog.Error(AppCenterLog.LogTag, $"Could not change maximum database size to {sizeInBytes} bytes. SQLite error code: {result}."); + return false; + } + else + { + var currentMaxPageCount = GetMaxPageCount(); + var actualMaxSize = currentMaxPageCount * pageSize; + if (requestedMaxPageCount != currentMaxPageCount) + { + AppCenterLog.Error(AppCenterLog.LogTag, $"Could not change maximum database size to {sizeInBytes} bytes, current maximum size is {actualMaxSize} bytes."); + return false; + } + else + { + if (sizeInBytes == actualMaxSize) + { + AppCenterLog.Info(AppCenterLog.LogTag, $"Changed maximum database size to {actualMaxSize} bytes."); + } + else + { + AppCenterLog.Info(AppCenterLog.LogTag, $"Changed maximum database size to {actualMaxSize} bytes (next multiple of 4KiB)."); + } + return true; + } + } + } + } + public void CreateTable(string tableName, string[] columnNames, string[] columnTypes) { var tableClause = string.Join(",", Enumerable.Range(0, columnNames.Length).Select(i => $"{columnNames[i]} {columnTypes[i]}")); @@ -164,7 +250,7 @@ public int Count(string tableName, string columnName, object value) return (int)count; } - public IList Select(string tableName, string columnName, object value, string excludeColumnName, object[] excludeValues, int? limit = null) + public IList Select(string tableName, string columnName, object value, string excludeColumnName, object[] excludeValues, int? limit = null, string[] orderList = null) { var whereClause = $"{columnName} = ?"; var args = new List { value }; @@ -174,7 +260,8 @@ public IList Select(string tableName, string columnName, object value, args.AddRange(excludeValues); } var limitClause = limit != null ? $" LIMIT {limit}" : string.Empty; - var query = $"SELECT * FROM {tableName} WHERE {whereClause}{limitClause};"; + var orderClause = orderList != null && orderList.Length > 0 ? $" ORDER BY {string.Join(",", orderList)} ASC" : string.Empty; + var query = $"SELECT * FROM {tableName} WHERE {whereClause}{orderClause}{limitClause};"; return ExecuteSelectionSqlQuery(query, args); } @@ -197,11 +284,16 @@ private StorageException ToStorageException(int result, string message) { var errorMessage = raw.sqlite3_errmsg(_db).utf8_to_string(); var exceptionMessage = $"{message}, result={result}\n\t{errorMessage}"; - if (result == raw.SQLITE_CORRUPT || result == raw.SQLITE_NOTADB) + switch(result) { - return new StorageCorruptedException(exceptionMessage); + case raw.SQLITE_CORRUPT: + case raw.SQLITE_NOTADB: + return new StorageCorruptedException(exceptionMessage); + case raw.SQLITE_FULL: + return new StorageFullException(exceptionMessage); + default: + return new StorageException(exceptionMessage); } - return new StorageException(exceptionMessage); } private static string BuildBindingMask(int amount) diff --git a/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/Storage/StorageFullException.cs b/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/Storage/StorageFullException.cs new file mode 100644 index 000000000..dc575b6e1 --- /dev/null +++ b/SDK/AppCenter/Microsoft.AppCenter.Windows.Shared/Storage/StorageFullException.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.AppCenter.Storage +{ + internal class StorageFullException : StorageException + { + private const string DefaultMessage = "The database is full."; + + public StorageFullException() : base(DefaultMessage) { } + + public StorageFullException(string message) : base(message) { } + } +} diff --git a/SDK/AppCenter/Microsoft.AppCenter.WindowsDesktop/Microsoft.AppCenter.WindowsDesktop.csproj b/SDK/AppCenter/Microsoft.AppCenter.WindowsDesktop/Microsoft.AppCenter.WindowsDesktop.csproj index 734488cf2..95c4e7105 100644 --- a/SDK/AppCenter/Microsoft.AppCenter.WindowsDesktop/Microsoft.AppCenter.WindowsDesktop.csproj +++ b/SDK/AppCenter/Microsoft.AppCenter.WindowsDesktop/Microsoft.AppCenter.WindowsDesktop.csproj @@ -4,9 +4,9 @@ netcoreapp3.0;net461 Microsoft.AppCenter true - 4.0.0-SNAPSHOT + 4.1.0-SNAPSHOT 0.0.0.0 - 4.0.0.0 + 4.1.0.0 Microsoft.AppCenter bin\Microsoft.AppCenter.xml diff --git a/SDK/AppCenter/Microsoft.AppCenter.iOS.Bindings/ApiDefinition.cs b/SDK/AppCenter/Microsoft.AppCenter.iOS.Bindings/ApiDefinition.cs index d91d170f8..6400cd63b 100644 --- a/SDK/AppCenter/Microsoft.AppCenter.iOS.Bindings/ApiDefinition.cs +++ b/SDK/AppCenter/Microsoft.AppCenter.iOS.Bindings/ApiDefinition.cs @@ -9,6 +9,9 @@ namespace Microsoft.AppCenter.iOS.Bindings { interface IMSACService { } + // typedef (void (^)(BOOL))completionHandler(); + delegate void MSACSetLogLevelCompletionHandlerCallback(bool result); + // typedef NSString * (^MSACLogMessageProvider)(); delegate string MSACLogMessageProvider(); @@ -261,6 +264,11 @@ interface MSACAppCenter [Static] [Export("setCustomProperties:")] void SetCustomProperties([NullAllowed] MSACCustomProperties properties); + + // +(void)setMaxStorageSize:(long)sizeInBytes completionHandler(void (^)(BOOL))completionHandler; + [Static] + [Export("setMaxStorageSize:completionHandler:")] + void SetMaxStorageSize(long sizeInBytes, MSACSetLogLevelCompletionHandlerCallback callback); } // @protocol MSACService diff --git a/SDK/AppCenter/Microsoft.AppCenter.iOS.Bindings/Properties/AssemblyInfo.cs b/SDK/AppCenter/Microsoft.AppCenter.iOS.Bindings/Properties/AssemblyInfo.cs index c3d955155..e9db58fba 100644 --- a/SDK/AppCenter/Microsoft.AppCenter.iOS.Bindings/Properties/AssemblyInfo.cs +++ b/SDK/AppCenter/Microsoft.AppCenter.iOS.Bindings/Properties/AssemblyInfo.cs @@ -33,5 +33,5 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("0.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] -[assembly: AssemblyInformationalVersion("4.0.0-SNAPSHOT")] +[assembly: AssemblyFileVersion("4.1.0.0")] +[assembly: AssemblyInformationalVersion("4.1.0-SNAPSHOT")] diff --git a/SDK/AppCenter/Microsoft.AppCenter.iOS/AppCenter.cs b/SDK/AppCenter/Microsoft.AppCenter.iOS/AppCenter.cs index 703bd9d98..af6c546f9 100644 --- a/SDK/AppCenter/Microsoft.AppCenter.iOS/AppCenter.cs +++ b/SDK/AppCenter/Microsoft.AppCenter.iOS/AppCenter.cs @@ -193,5 +193,12 @@ internal static void PlatformUnsetInstance() { iOSAppCenter.ResetSharedInstance(); } + + static Task PlatformSetMaxStorageSizeAsync(long sizeInBytes) + { + TaskCompletionSource taskCompletionSource = new TaskCompletionSource(); + iOSAppCenter.SetMaxStorageSize(sizeInBytes, (result) => taskCompletionSource.SetResult(result)); + return taskCompletionSource.Task; + } } } diff --git a/SDK/AppCenter/Microsoft.AppCenter.iOS/Properties/AssemblyInfo.cs b/SDK/AppCenter/Microsoft.AppCenter.iOS/Properties/AssemblyInfo.cs index 054c960fb..7aea1acfe 100644 --- a/SDK/AppCenter/Microsoft.AppCenter.iOS/Properties/AssemblyInfo.cs +++ b/SDK/AppCenter/Microsoft.AppCenter.iOS/Properties/AssemblyInfo.cs @@ -27,6 +27,6 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("0.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] -[assembly: AssemblyInformationalVersion("4.0.0-SNAPSHOT")] +[assembly: AssemblyFileVersion("4.1.0.0")] +[assembly: AssemblyInformationalVersion("4.1.0-SNAPSHOT")] [assembly: InternalsVisibleTo("Microsoft.AppCenter.Test.Functional")] diff --git a/SDK/AppCenter/Microsoft.AppCenter/AppCenter.cs b/SDK/AppCenter/Microsoft.AppCenter/AppCenter.cs index d540385fa..c5312b8e1 100644 --- a/SDK/AppCenter/Microsoft.AppCenter/AppCenter.cs +++ b/SDK/AppCenter/Microsoft.AppCenter/AppCenter.cs @@ -65,6 +65,11 @@ static void PlatformSetCustomProperties(CustomProperties customProperties) { } + static Task PlatformSetMaxStorageSizeAsync(long sizeInBytes) + { + return Task.FromResult(false); + } + internal static void PlatformUnsetInstance() { } diff --git a/SDK/AppCenter/Microsoft.AppCenter/Microsoft.AppCenter.csproj b/SDK/AppCenter/Microsoft.AppCenter/Microsoft.AppCenter.csproj index c050735a2..29e38e1ab 100644 --- a/SDK/AppCenter/Microsoft.AppCenter/Microsoft.AppCenter.csproj +++ b/SDK/AppCenter/Microsoft.AppCenter/Microsoft.AppCenter.csproj @@ -8,9 +8,9 @@ Microsoft Corp. All rights reserved. Microsoft.AppCenter.Core Microsoft Corporation - 4.0.0-SNAPSHOT + 4.1.0-SNAPSHOT 0.0.0.0 - 4.0.0.0 + 4.1.0.0 Microsoft.AppCenter.Core bin\$(Configuration)\$(TargetFramework)\Microsoft.AppCenter.xml diff --git a/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics.Android.Bindings/Microsoft.AppCenter.Analytics.Android.Bindings.csproj b/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics.Android.Bindings/Microsoft.AppCenter.Analytics.Android.Bindings.csproj index bde648156..cf6923914 100644 --- a/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics.Android.Bindings/Microsoft.AppCenter.Analytics.Android.Bindings.csproj +++ b/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics.Android.Bindings/Microsoft.AppCenter.Analytics.Android.Bindings.csproj @@ -9,6 +9,7 @@ Microsoft.AppCenter.Analytics.Android.Bindings Microsoft.AppCenter.Analytics.Android.Bindings v8.0 + class-parse Resources Assets diff --git a/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics.Android.Bindings/Properties/AssemblyInfo.cs b/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics.Android.Bindings/Properties/AssemblyInfo.cs index 6fd7db442..296d0f9de 100644 --- a/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics.Android.Bindings/Properties/AssemblyInfo.cs +++ b/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics.Android.Bindings/Properties/AssemblyInfo.cs @@ -21,8 +21,8 @@ // and "{Major}.{Minor}.{Build}.*" will update just the revision. [assembly: AssemblyVersion("0.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] -[assembly: AssemblyInformationalVersion("4.0.0-SNAPSHOT")] +[assembly: AssemblyFileVersion("4.1.0.0")] +[assembly: AssemblyInformationalVersion("4.1.0-SNAPSHOT")] // The following attributes are used to specify the signing key for the assembly, // if desired. See the Mono documentation for more information about signing. diff --git a/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics.Android/Microsoft.AppCenter.Analytics.Android.csproj b/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics.Android/Microsoft.AppCenter.Analytics.Android.csproj index 1417199b6..e3fbeb5b2 100644 --- a/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics.Android/Microsoft.AppCenter.Analytics.Android.csproj +++ b/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics.Android/Microsoft.AppCenter.Analytics.Android.csproj @@ -15,6 +15,7 @@ Resources\Resource.Designer.cs Off v8.0 + class-parse diff --git a/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics.Android/Properties/AssemblyInfo.cs b/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics.Android/Properties/AssemblyInfo.cs index 0bd50302e..4627c61fb 100644 --- a/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics.Android/Properties/AssemblyInfo.cs +++ b/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics.Android/Properties/AssemblyInfo.cs @@ -29,6 +29,6 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("0.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] -[assembly: AssemblyInformationalVersion("4.0.0-SNAPSHOT")] +[assembly: AssemblyFileVersion("4.1.0.0")] +[assembly: AssemblyInformationalVersion("4.1.0-SNAPSHOT")] [assembly: InternalsVisibleTo("Microsoft.AppCenter.Test.Functional")] diff --git a/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics.UWP/Microsoft.AppCenter.Analytics.UWP.csproj b/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics.UWP/Microsoft.AppCenter.Analytics.UWP.csproj index e5ee6ca34..8ebd85fb3 100644 --- a/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics.UWP/Microsoft.AppCenter.Analytics.UWP.csproj +++ b/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics.UWP/Microsoft.AppCenter.Analytics.UWP.csproj @@ -5,9 +5,9 @@ uap10.0.16299;net461 Microsoft Corporation Microsoft Corp. All rights reserved. - 4.0.0-SNAPSHOT + 4.1.0-SNAPSHOT 0.0.0.0 - 4.0.0.0 + 4.1.0.0 Microsoft.AppCenter.Analytics bin\$(Configuration)\$(TargetFramework)\Microsoft.AppCenter.Analytics.xml diff --git a/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics.WindowsDesktop/Microsoft.AppCenter.Analytics.WindowsDesktop.csproj b/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics.WindowsDesktop/Microsoft.AppCenter.Analytics.WindowsDesktop.csproj index 5398fed12..d2f662d43 100644 --- a/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics.WindowsDesktop/Microsoft.AppCenter.Analytics.WindowsDesktop.csproj +++ b/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics.WindowsDesktop/Microsoft.AppCenter.Analytics.WindowsDesktop.csproj @@ -3,9 +3,9 @@ netcoreapp3.0;net461 Microsoft.AppCenter.Analytics - 4.0.0-SNAPSHOT + 4.1.0-SNAPSHOT 0.0.0.0 - 4.0.0.0 + 4.1.0.0 Microsoft.AppCenter.Analytics bin\Microsoft.AppCenter.Analytics.xml diff --git a/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics.iOS.Bindings/Properties/AssemblyInfo.cs b/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics.iOS.Bindings/Properties/AssemblyInfo.cs index 2a0a8d68e..1d0d465a5 100644 --- a/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics.iOS.Bindings/Properties/AssemblyInfo.cs +++ b/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics.iOS.Bindings/Properties/AssemblyInfo.cs @@ -33,5 +33,5 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("0.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] -[assembly: AssemblyInformationalVersion("4.0.0-SNAPSHOT")] +[assembly: AssemblyFileVersion("4.1.0.0")] +[assembly: AssemblyInformationalVersion("4.1.0-SNAPSHOT")] diff --git a/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics.iOS/Properties/AssemblyInfo.cs b/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics.iOS/Properties/AssemblyInfo.cs index 42288266a..396e5949e 100644 --- a/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics.iOS/Properties/AssemblyInfo.cs +++ b/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics.iOS/Properties/AssemblyInfo.cs @@ -27,6 +27,6 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("0.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] -[assembly: AssemblyInformationalVersion("4.0.0-SNAPSHOT")] +[assembly: AssemblyFileVersion("4.1.0.0")] +[assembly: AssemblyInformationalVersion("4.1.0-SNAPSHOT")] [assembly: InternalsVisibleTo("Microsoft.AppCenter.Test.Functional")] diff --git a/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics/Microsoft.AppCenter.Analytics.csproj b/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics/Microsoft.AppCenter.Analytics.csproj index 86a49d3f9..1f5872c9b 100644 --- a/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics/Microsoft.AppCenter.Analytics.csproj +++ b/SDK/AppCenterAnalytics/Microsoft.AppCenter.Analytics/Microsoft.AppCenter.Analytics.csproj @@ -6,9 +6,9 @@ Microsoft Corp. All rights reserved. Microsoft.AppCenter.Analytics Microsoft Corporation - 4.0.0-SNAPSHOT + 4.1.0-SNAPSHOT 0.0.0.0 - 4.0.0.0 + 4.1.0.0 Microsoft.AppCenter.Analytics bin\$(Configuration)\$(TargetFramework)\Microsoft.AppCenter.Analytics.xml diff --git a/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes.Android.Bindings/Microsoft.AppCenter.Crashes.Android.Bindings.csproj b/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes.Android.Bindings/Microsoft.AppCenter.Crashes.Android.Bindings.csproj index 9088624ed..8b00e8fda 100644 --- a/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes.Android.Bindings/Microsoft.AppCenter.Crashes.Android.Bindings.csproj +++ b/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes.Android.Bindings/Microsoft.AppCenter.Crashes.Android.Bindings.csproj @@ -9,6 +9,7 @@ Microsoft.AppCenter.Crashes.Android.Bindings Microsoft.AppCenter.Crashes.Android.Bindings v8.0 + class-parse Resources Assets diff --git a/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes.Android.Bindings/Properties/AssemblyInfo.cs b/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes.Android.Bindings/Properties/AssemblyInfo.cs index dc3f45c68..4cc47eb0e 100644 --- a/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes.Android.Bindings/Properties/AssemblyInfo.cs +++ b/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes.Android.Bindings/Properties/AssemblyInfo.cs @@ -20,8 +20,8 @@ // and "{Major}.{Minor}.{Build}.*" will update just the revision. [assembly: AssemblyVersion("0.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] -[assembly: AssemblyInformationalVersion("4.0.0-SNAPSHOT")] +[assembly: AssemblyFileVersion("4.1.0.0")] +[assembly: AssemblyInformationalVersion("4.1.0-SNAPSHOT")] // The following attributes are used to specify the signing key for the assembly, // if desired. See the Mono documentation for more information about signing. diff --git a/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes.Android/Microsoft.AppCenter.Crashes.Android.csproj b/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes.Android/Microsoft.AppCenter.Crashes.Android.csproj index d4d209661..b6298ee71 100644 --- a/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes.Android/Microsoft.AppCenter.Crashes.Android.csproj +++ b/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes.Android/Microsoft.AppCenter.Crashes.Android.csproj @@ -15,6 +15,7 @@ Resources\Resource.Designer.cs Off v8.0 + class-parse diff --git a/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes.Android/Properties/AssemblyInfo.cs b/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes.Android/Properties/AssemblyInfo.cs index bed4268e0..6ebea77bc 100644 --- a/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes.Android/Properties/AssemblyInfo.cs +++ b/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes.Android/Properties/AssemblyInfo.cs @@ -30,6 +30,6 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("0.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] -[assembly: AssemblyInformationalVersion("4.0.0-SNAPSHOT")] +[assembly: AssemblyFileVersion("4.1.0.0")] +[assembly: AssemblyInformationalVersion("4.1.0-SNAPSHOT")] [assembly: InternalsVisibleTo("Microsoft.AppCenter.Test.Functional")] diff --git a/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes.UWP/Microsoft.AppCenter.Crashes.UWP.csproj b/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes.UWP/Microsoft.AppCenter.Crashes.UWP.csproj index fff7169f0..3f7f44795 100644 --- a/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes.UWP/Microsoft.AppCenter.Crashes.UWP.csproj +++ b/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes.UWP/Microsoft.AppCenter.Crashes.UWP.csproj @@ -5,9 +5,9 @@ uap10.0.16299;net461 Microsoft Corporation Microsoft Corp. All rights reserved. - 4.0.0-SNAPSHOT + 4.1.0-SNAPSHOT 0.0.0.0 - 4.0.0.0 + 4.1.0.0 Microsoft.AppCenter.Crashes bin\$(Configuration)\$(TargetFramework)\Microsoft.AppCenter.Crashes.xml true diff --git a/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes.WindowsDesktop/Microsoft.AppCenter.Crashes.WindowsDesktop.csproj b/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes.WindowsDesktop/Microsoft.AppCenter.Crashes.WindowsDesktop.csproj index e2816ddec..6b887d674 100644 --- a/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes.WindowsDesktop/Microsoft.AppCenter.Crashes.WindowsDesktop.csproj +++ b/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes.WindowsDesktop/Microsoft.AppCenter.Crashes.WindowsDesktop.csproj @@ -3,9 +3,9 @@ netcoreapp3.0;net461 Microsoft.AppCenter.Crashes - 4.0.0-SNAPSHOT + 4.1.0-SNAPSHOT 0.0.0.0 - 4.0.0.0 + 4.1.0.0 Microsoft.AppCenter.Crashes bin\Microsoft.AppCenter.Crashes.xml diff --git a/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes.iOS.Bindings/Properties/AssemblyInfo.cs b/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes.iOS.Bindings/Properties/AssemblyInfo.cs index 258c82546..a7d1d1ce4 100644 --- a/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes.iOS.Bindings/Properties/AssemblyInfo.cs +++ b/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes.iOS.Bindings/Properties/AssemblyInfo.cs @@ -33,5 +33,5 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("0.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] -[assembly: AssemblyInformationalVersion("4.0.0-SNAPSHOT")] +[assembly: AssemblyFileVersion("4.1.0.0")] +[assembly: AssemblyInformationalVersion("4.1.0-SNAPSHOT")] diff --git a/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes.iOS/Properties/AssemblyInfo.cs b/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes.iOS/Properties/AssemblyInfo.cs index 7f8090b73..b1c76e01c 100644 --- a/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes.iOS/Properties/AssemblyInfo.cs +++ b/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes.iOS/Properties/AssemblyInfo.cs @@ -27,6 +27,6 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("0.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] -[assembly: AssemblyInformationalVersion("4.0.0-SNAPSHOT")] +[assembly: AssemblyFileVersion("4.1.0.0")] +[assembly: AssemblyInformationalVersion("4.1.0-SNAPSHOT")] [assembly: InternalsVisibleTo("Microsoft.AppCenter.Test.Functional")] diff --git a/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes/Microsoft.AppCenter.Crashes.csproj b/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes/Microsoft.AppCenter.Crashes.csproj index 3b6ee1065..b14d71697 100644 --- a/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes/Microsoft.AppCenter.Crashes.csproj +++ b/SDK/AppCenterCrashes/Microsoft.AppCenter.Crashes/Microsoft.AppCenter.Crashes.csproj @@ -7,9 +7,9 @@ Microsoft Corp. All rights reserved. Microsoft.AppCenter.Crashes Microsoft Corporation - 4.0.0-SNAPSHOT + 4.1.0-SNAPSHOT 0.0.0.0 - 4.0.0.0 + 4.1.0.0 Microsoft.AppCenter.Crashes bin\$(Configuration)\$(TargetFramework)\Microsoft.AppCenter.Crashes.xml diff --git a/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.Android.Bindings/Microsoft.AppCenter.Distribute.Android.Bindings.csproj b/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.Android.Bindings/Microsoft.AppCenter.Distribute.Android.Bindings.csproj index fc4720fb0..ffac2f37d 100644 --- a/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.Android.Bindings/Microsoft.AppCenter.Distribute.Android.Bindings.csproj +++ b/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.Android.Bindings/Microsoft.AppCenter.Distribute.Android.Bindings.csproj @@ -9,6 +9,7 @@ Microsoft.AppCenter.Distribute.Android.Bindings Microsoft.AppCenter.Distribute.Android.Bindings v8.0 + class-parse Resources Assets diff --git a/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.Android.Bindings/Properties/AssemblyInfo.cs b/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.Android.Bindings/Properties/AssemblyInfo.cs index e06f262d5..383e197b5 100644 --- a/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.Android.Bindings/Properties/AssemblyInfo.cs +++ b/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.Android.Bindings/Properties/AssemblyInfo.cs @@ -20,8 +20,8 @@ // and "{Major}.{Minor}.{Build}.*" will update just the revision. [assembly: AssemblyVersion("0.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] -[assembly: AssemblyInformationalVersion("4.0.0-SNAPSHOT")] +[assembly: AssemblyFileVersion("4.1.0.0")] +[assembly: AssemblyInformationalVersion("4.1.0-SNAPSHOT")] // The following attributes are used to specify the signing key for the assembly, // if desired. See the Mono documentation for more information about signing. diff --git a/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.Android/Distribute.cs b/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.Android/Distribute.cs index ee9e26d1d..ff561b8bd 100644 --- a/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.Android/Distribute.cs +++ b/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.Android/Distribute.cs @@ -89,6 +89,8 @@ public static void SetEnabledForDebuggableBuild(bool enabled) static ReleaseAvailableCallback _releaseAvailableCallback; + static NoReleaseAvailableCallback _noReleaseAvailableCallback; + static void SetReleaseAvailableCallback(ReleaseAvailableCallback releaseAvailableCallback) { lock (typeof(Distribute)) @@ -102,6 +104,23 @@ static void SetReleaseAvailableCallback(ReleaseAvailableCallback releaseAvailabl } } + static void SetWillExitAppCallback(WillExitAppCallback willExitAppCallback) + { + } + + static void SetNoReleaseAvailable(NoReleaseAvailableCallback noReleaseAvailable) + { + lock (typeof(Distribute)) + { + _noReleaseAvailableCallback = noReleaseAvailable; + if (_listener == null && _noReleaseAvailableCallback != null) + { + _listener = new Listener(); + AndroidDistribute.SetListener(_listener); + } + } + } + class Listener : Java.Lang.Object, IDistributeListener { public bool OnReleaseAvailable(Activity activity, AndroidReleaseDetails androidReleaseDetails) @@ -126,6 +145,11 @@ public bool OnReleaseAvailable(Activity activity, AndroidReleaseDetails androidR } return false; } + + public void OnNoReleaseAvailable(Activity activity) + { + _noReleaseAvailableCallback?.Invoke(); + } } } } diff --git a/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.Android/Microsoft.AppCenter.Distribute.Android.csproj b/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.Android/Microsoft.AppCenter.Distribute.Android.csproj index f9cbde866..968941033 100644 --- a/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.Android/Microsoft.AppCenter.Distribute.Android.csproj +++ b/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.Android/Microsoft.AppCenter.Distribute.Android.csproj @@ -9,6 +9,7 @@ Microsoft.AppCenter.Distribute Microsoft.AppCenter.Distribute v8.0 + class-parse Resources\Resource.designer.cs Resource Resources diff --git a/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.Android/Properties/AssemblyInfo.cs b/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.Android/Properties/AssemblyInfo.cs index e48f62f4a..ef71d5f54 100644 --- a/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.Android/Properties/AssemblyInfo.cs +++ b/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.Android/Properties/AssemblyInfo.cs @@ -29,6 +29,6 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("0.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] -[assembly: AssemblyInformationalVersion("4.0.0-SNAPSHOT")] +[assembly: AssemblyFileVersion("4.1.0.0")] +[assembly: AssemblyInformationalVersion("4.1.0-SNAPSHOT")] [assembly: InternalsVisibleTo("Microsoft.AppCenter.Test.Functional")] diff --git a/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.Shared/Distribute.cs b/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.Shared/Distribute.cs index cb1426844..c42496952 100644 --- a/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.Shared/Distribute.cs +++ b/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.Shared/Distribute.cs @@ -58,6 +58,30 @@ public static ReleaseAvailableCallback ReleaseAvailable } } + /// + /// Sets the app will close callback. + /// + /// The app will close callback. + public static WillExitAppCallback WillExitApp + { + set + { + SetWillExitAppCallback(value); + } + } + + /// + /// Sets the no release available callback. + /// + /// The no release available callback. + public static NoReleaseAvailableCallback NoReleaseAvailable + { + set + { + SetNoReleaseAvailable(value); + } + } + /// /// If update dialog is customized by returning true in , /// You need to tell the distribute SDK using this function what is the user action. diff --git a/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.Shared/DistributeDelegates.cs b/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.Shared/DistributeDelegates.cs index 6044b7f70..770af9738 100644 --- a/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.Shared/DistributeDelegates.cs +++ b/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.Shared/DistributeDelegates.cs @@ -7,4 +7,14 @@ namespace Microsoft.AppCenter.Distribute /// Release available callback. /// public delegate bool ReleaseAvailableCallback(ReleaseDetails releaseDetails); + + /// + /// App will close callback. + /// + public delegate void WillExitAppCallback(); + + /// + /// No release available callback. + /// + public delegate void NoReleaseAvailableCallback(); } diff --git a/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.iOS.Bindings/ApiDefinition.cs b/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.iOS.Bindings/ApiDefinition.cs index bf5c79265..a65cbe054 100644 --- a/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.iOS.Bindings/ApiDefinition.cs +++ b/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.iOS.Bindings/ApiDefinition.cs @@ -9,12 +9,12 @@ namespace Microsoft.AppCenter.Distribute.iOS.Bindings [BaseType(typeof(NSObject))] interface MSACDistribute { - // +(void)setEnabled:(BOOL)isEnabled; + // + (void)setEnabled:(BOOL)isEnabled; [Static] [Export("setEnabled:")] void SetEnabled(bool isEnabled); - // +(BOOL)isEnabled; + // + (BOOL)isEnabled; [Static] [Export("isEnabled")] bool IsEnabled(); @@ -78,6 +78,14 @@ interface MSACDistributeDelegate // @optional - (BOOL)distribute:(MSACDistribute *)distribute releaseAvailableWithDetails:(MSACReleaseDetails *)details; [Export("distribute:releaseAvailableWithDetails:")] bool OnReleaseAvailable(MSACDistribute distribute, MSACReleaseDetails details); + + // - (void)distributeWillExitApp:(MSACDistribute *)distribute; + [Export("distributeWillExitApp:")] + void WillExitApp(MSACDistribute distribute); + + // - (void)distributeNoReleaseAvailable:(MSACDistribute *)distribute; + [Export("distributeNoReleaseAvailable:")] + void OnNoReleaseAvailable(MSACDistribute distribute); } // @interface MSACReleaseDetails : NSObject diff --git a/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.iOS.Bindings/Properties/AssemblyInfo.cs b/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.iOS.Bindings/Properties/AssemblyInfo.cs index c82cdc486..b40daedf0 100644 --- a/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.iOS.Bindings/Properties/AssemblyInfo.cs +++ b/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.iOS.Bindings/Properties/AssemblyInfo.cs @@ -35,5 +35,5 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("0.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] -[assembly: AssemblyInformationalVersion("4.0.0-SNAPSHOT")] +[assembly: AssemblyFileVersion("4.1.0.0")] +[assembly: AssemblyInformationalVersion("4.1.0-SNAPSHOT")] diff --git a/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.iOS/Distribute.cs b/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.iOS/Distribute.cs index 0d68f75d5..12efe86f7 100644 --- a/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.iOS/Distribute.cs +++ b/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.iOS/Distribute.cs @@ -75,6 +75,10 @@ public static void DontCheckForUpdatesInDebug() static ReleaseAvailableCallback _releaseAvailableCallback; + static WillExitAppCallback _willExitAppCallback; + + static NoReleaseAvailableCallback _noReleaseAvailableCallback; + static void SetReleaseAvailableCallback(ReleaseAvailableCallback releaseAvailableCallback) { lock (typeof(Distribute)) @@ -88,6 +92,32 @@ static void SetReleaseAvailableCallback(ReleaseAvailableCallback releaseAvailabl } } + static void SetWillExitAppCallback(WillExitAppCallback willExitAppCallback) + { + lock (typeof(Distribute)) + { + _willExitAppCallback = willExitAppCallback; + if (_delegate == null && _willExitAppCallback != null) + { + _delegate = new Delegate(); + iOSDistribute.SetDelegate(_delegate); + } + } + } + + static void SetNoReleaseAvailable(NoReleaseAvailableCallback noReleaseAvailable) + { + lock (typeof(Distribute)) + { + _noReleaseAvailableCallback = noReleaseAvailable; + if (_delegate == null && _noReleaseAvailableCallback != null) + { + _delegate = new Delegate(); + iOSDistribute.SetDelegate(_delegate); + } + } + } + static void HandleUpdateAction(UpdateAction updateAction) { switch (updateAction) @@ -153,6 +183,16 @@ public override bool OnReleaseAvailable(iOSDistribute distribute, MSACReleaseDet } return false; } + + public override void WillExitApp(iOSDistribute distribute) + { + _willExitAppCallback?.Invoke(); + } + + public override void OnNoReleaseAvailable(MSACDistribute distribute) + { + _noReleaseAvailableCallback?.Invoke(); + } } } } diff --git a/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.iOS/Properties/AssemblyInfo.cs b/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.iOS/Properties/AssemblyInfo.cs index 479413168..219e20914 100644 --- a/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.iOS/Properties/AssemblyInfo.cs +++ b/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute.iOS/Properties/AssemblyInfo.cs @@ -27,6 +27,6 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("0.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] -[assembly: AssemblyInformationalVersion("4.0.0-SNAPSHOT")] +[assembly: AssemblyFileVersion("4.1.0.0")] +[assembly: AssemblyInformationalVersion("4.1.0-SNAPSHOT")] [assembly: InternalsVisibleTo("Microsoft.AppCenter.Test.Functional")] \ No newline at end of file diff --git a/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute/Distribute.cs b/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute/Distribute.cs index ae65a468d..f53833d7c 100644 --- a/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute/Distribute.cs +++ b/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute/Distribute.cs @@ -29,6 +29,14 @@ static void SetReleaseAvailableCallback(ReleaseAvailableCallback releaseAvailabl { } + static void SetWillExitAppCallback(WillExitAppCallback willExitAppCallback) + { + } + + static void SetNoReleaseAvailable(NoReleaseAvailableCallback noReleaseAvailable) + { + } + static void HandleUpdateAction(UpdateAction updateAction) { } diff --git a/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute/Microsoft.AppCenter.Distribute.csproj b/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute/Microsoft.AppCenter.Distribute.csproj index 21600751d..c45f1e9aa 100644 --- a/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute/Microsoft.AppCenter.Distribute.csproj +++ b/SDK/AppCenterDistribute/Microsoft.AppCenter.Distribute/Microsoft.AppCenter.Distribute.csproj @@ -7,9 +7,9 @@ Microsoft Corp. All rights reserved. Microsoft.AppCenter.Distribute Microsoft Corporation - 4.0.0-SNAPSHOT + 4.1.0-SNAPSHOT 0.0.0.0 - 4.0.0.0 + 4.1.0.0 Microsoft.AppCenter.Distribute bin\$(Configuration)\$(TargetFramework)\Microsoft.AppCenter.Distribute.xml diff --git a/Tests/Contoso.Test.Functional.Droid/Properties/AndroidManifest.xml b/Tests/Contoso.Test.Functional.Droid/Properties/AndroidManifest.xml index 6d5edf320..9ec6b88a5 100644 --- a/Tests/Contoso.Test.Functional.Droid/Properties/AndroidManifest.xml +++ b/Tests/Contoso.Test.Functional.Droid/Properties/AndroidManifest.xml @@ -1,5 +1,5 @@ - + diff --git a/Tests/Contoso.Test.Functional.Droid/Properties/AssemblyInfo.cs b/Tests/Contoso.Test.Functional.Droid/Properties/AssemblyInfo.cs index d25df83c9..00c42a57c 100644 --- a/Tests/Contoso.Test.Functional.Droid/Properties/AssemblyInfo.cs +++ b/Tests/Contoso.Test.Functional.Droid/Properties/AssemblyInfo.cs @@ -22,8 +22,8 @@ // and "{Major}.{Minor}.{Build}.*" will update just the revision. [assembly: AssemblyVersion("0.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] -[assembly: AssemblyInformationalVersion("4.0.0-SNAPSHOT")] +[assembly: AssemblyFileVersion("4.1.0.0")] +[assembly: AssemblyInformationalVersion("4.1.0-SNAPSHOT")] // The following attributes are used to specify the signing key for the assembly, // if desired. See the Mono documentation for more information about signing. diff --git a/Tests/Contoso.Test.Functional.iOS/Info.plist b/Tests/Contoso.Test.Functional.iOS/Info.plist index 165097146..c61479930 100644 --- a/Tests/Contoso.Test.Functional.iOS/Info.plist +++ b/Tests/Contoso.Test.Functional.iOS/Info.plist @@ -7,9 +7,9 @@ CFBundleIdentifier com.contoso.test.functional CFBundleShortVersionString - 4.0.0 + 4.1.0 CFBundleVersion - 4.0.0 + 4.1.0 LSRequiresIPhoneOS MinimumOSVersion diff --git a/Tests/Contoso.Test.Functional.iOS/Properties/AssemblyInfo.cs b/Tests/Contoso.Test.Functional.iOS/Properties/AssemblyInfo.cs index f9f8ecbba..bbe99342b 100644 --- a/Tests/Contoso.Test.Functional.iOS/Properties/AssemblyInfo.cs +++ b/Tests/Contoso.Test.Functional.iOS/Properties/AssemblyInfo.cs @@ -37,5 +37,5 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("0.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] -[assembly: AssemblyInformationalVersion("4.0.0-SNAPSHOT")] +[assembly: AssemblyFileVersion("4.1.0.0")] +[assembly: AssemblyInformationalVersion("4.1.0-SNAPSHOT")] diff --git a/Tests/Microsoft.AppCenter.Analytics.NET/Properties/AssemblyInfo.cs b/Tests/Microsoft.AppCenter.Analytics.NET/Properties/AssemblyInfo.cs index 37a7dd7fc..597418315 100644 --- a/Tests/Microsoft.AppCenter.Analytics.NET/Properties/AssemblyInfo.cs +++ b/Tests/Microsoft.AppCenter.Analytics.NET/Properties/AssemblyInfo.cs @@ -36,7 +36,7 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("0.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] -[assembly: AssemblyInformationalVersion("4.0.0-SNAPSHOT")] +[assembly: AssemblyFileVersion("4.1.0.0")] +[assembly: AssemblyInformationalVersion("4.1.0-SNAPSHOT")] [assembly: InternalsVisibleTo("Microsoft.AppCenter.Analytics.Test.Windows")] \ No newline at end of file diff --git a/Tests/Microsoft.AppCenter.Analytics.Test.Windows/Properties/AssemblyInfo.cs b/Tests/Microsoft.AppCenter.Analytics.Test.Windows/Properties/AssemblyInfo.cs index dd125893f..5011adf73 100644 --- a/Tests/Microsoft.AppCenter.Analytics.Test.Windows/Properties/AssemblyInfo.cs +++ b/Tests/Microsoft.AppCenter.Analytics.Test.Windows/Properties/AssemblyInfo.cs @@ -19,5 +19,5 @@ // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("0.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] -[assembly: AssemblyInformationalVersion("4.0.0-SNAPSHOT")] +[assembly: AssemblyFileVersion("4.1.0.0")] +[assembly: AssemblyInformationalVersion("4.1.0-SNAPSHOT")] diff --git a/Tests/Microsoft.AppCenter.Crashes.Test.Windows/Properties/AssemblyInfo.cs b/Tests/Microsoft.AppCenter.Crashes.Test.Windows/Properties/AssemblyInfo.cs index 32c26f387..b30c1cad1 100644 --- a/Tests/Microsoft.AppCenter.Crashes.Test.Windows/Properties/AssemblyInfo.cs +++ b/Tests/Microsoft.AppCenter.Crashes.Test.Windows/Properties/AssemblyInfo.cs @@ -19,5 +19,5 @@ // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("0.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] -[assembly: AssemblyInformationalVersion("4.0.0-SNAPSHOT")] \ No newline at end of file +[assembly: AssemblyFileVersion("4.1.0.0")] +[assembly: AssemblyInformationalVersion("4.1.0-SNAPSHOT")] \ No newline at end of file diff --git a/Tests/Microsoft.AppCenter.NET/Properties/AssemblyInfo.cs b/Tests/Microsoft.AppCenter.NET/Properties/AssemblyInfo.cs index 6f41237ce..2215e4fce 100644 --- a/Tests/Microsoft.AppCenter.NET/Properties/AssemblyInfo.cs +++ b/Tests/Microsoft.AppCenter.NET/Properties/AssemblyInfo.cs @@ -36,8 +36,8 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("0.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] -[assembly: AssemblyInformationalVersion("4.0.0-SNAPSHOT")] +[assembly: AssemblyFileVersion("4.1.0.0")] +[assembly: AssemblyInformationalVersion("4.1.0-SNAPSHOT")] [assembly: InternalsVisibleTo("Microsoft.AppCenter.Test.Windows")] [assembly: InternalsVisibleTo("Microsoft.AppCenter.Analytics.Test.Windows")] diff --git a/Tests/Microsoft.AppCenter.Test.UWP/Package.appxmanifest b/Tests/Microsoft.AppCenter.Test.UWP/Package.appxmanifest index 43509b833..4933a13be 100644 --- a/Tests/Microsoft.AppCenter.Test.UWP/Package.appxmanifest +++ b/Tests/Microsoft.AppCenter.Test.UWP/Package.appxmanifest @@ -7,7 +7,7 @@ + Version="4.1.0.0" /> diff --git a/Tests/Microsoft.AppCenter.Test.UWP/Properties/AssemblyInfo.cs b/Tests/Microsoft.AppCenter.Test.UWP/Properties/AssemblyInfo.cs index 181ea57aa..c7313d615 100644 --- a/Tests/Microsoft.AppCenter.Test.UWP/Properties/AssemblyInfo.cs +++ b/Tests/Microsoft.AppCenter.Test.UWP/Properties/AssemblyInfo.cs @@ -17,5 +17,5 @@ // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] +[assembly: AssemblyFileVersion("4.1.0.0")] [assembly: ComVisible(false)] \ No newline at end of file diff --git a/Tests/Microsoft.AppCenter.Test.Windows/AppCenterTest.cs b/Tests/Microsoft.AppCenter.Test.Windows/AppCenterTest.cs index 946e235c8..27da23ba9 100644 --- a/Tests/Microsoft.AppCenter.Test.Windows/AppCenterTest.cs +++ b/Tests/Microsoft.AppCenter.Test.Windows/AppCenterTest.cs @@ -74,7 +74,6 @@ public void SetLogLevels() public void GetLogLevelBeforeConfigure() { AppCenterLog.Level = LogLevel.Info; - Assert.AreEqual(LogLevel.Info, AppCenter.LogLevel); } @@ -87,7 +86,6 @@ public void SetLogLevelBeforeConfigure() AppCenterLog.Level = LogLevel.Info; AppCenter.LogLevel = LogLevel.Assert; AppCenter.Configure("appsecret"); - Assert.AreEqual(LogLevel.Assert, AppCenter.LogLevel); } @@ -848,6 +846,91 @@ public void SetWrapperSdk() Assert.AreEqual(updatePackageHash, device.LiveUpdatePackageHash); Assert.AreEqual(runtimeVersion, device.WrapperRuntimeVersion); } + + /// + /// Verify that AppCenter forwards SetMaxStorageSize to its channelGroup. + /// + [TestMethod] + public async Task SetMaxStorageSize() + { + // Set max storage size. + var dbSize = 2 * 1024 * 1024; + var storageTask = AppCenter.SetMaxStorageSizeAsync(dbSize); + AppCenter.Start("appsecret", typeof(MockAppCenterService)); + await storageTask; + + // Verify. + _channelGroupMock.Verify(channelGroup => channelGroup.SetMaxStorageSizeAsync(dbSize), Times.Once()); + } + + /// + /// Verify default value for max storage size is not set on AppCenter start. + /// + [TestMethod] + public void SetDefaultMaxStorageSizeOnStart() + { + // On Start max storage default size should be set. + AppCenter.Start("appsecret", typeof(MockAppCenterService)); + + // Verify. + _channelGroupMock.Verify(channelGroup => channelGroup.SetMaxStorageSizeAsync(10 * 1024 * 1024), Times.Once()); + } + + /// + /// Verify SetMaxStorageSize can only be called before the AppCenter start. + /// + [TestMethod] + public async Task CannotSetMaxStorageSizeAfterStart() + { + // Set max storage default size. + AppCenter.Start("appsecret", typeof(MockAppCenterService)); + var dbSize = 2 * 1024 * 1024; + + // Try to reset max storage size. + await AppCenter.SetMaxStorageSizeAsync(dbSize); + + // Verify. + _channelGroupMock.Verify(channelGroup => channelGroup.SetMaxStorageSizeAsync(dbSize), Times.Never()); + } + + /// + /// Verify that SetMaxStorageSize can only be called once. + /// + [TestMethod] + public async Task CannotSetMaxStorageSizeMultipleTimes() + { + // Set max storage size first time before AppCenter start. + var dbSize = 2 * 1024 * 1024; + var taskFirst = AppCenter.SetMaxStorageSizeAsync(dbSize); + + // Try to set max storage size second time before AppCenter start. + var taskSecond = AppCenter.SetMaxStorageSizeAsync(dbSize + 1); + + // Start AppCenter and set storage max size. + AppCenter.Start("appsecret", typeof(MockAppCenterService)); + await taskFirst; + + // Verify that method was called with expected parameter. + _channelGroupMock.Verify(channelGroup => channelGroup.SetMaxStorageSizeAsync(dbSize), Times.Once()); + await taskSecond; + + // Verify that method was not called in a second time. + _channelGroupMock.Verify(channelGroup => channelGroup.SetMaxStorageSizeAsync(dbSize + 1), Times.Never()); + } + + /// + /// Verify that dbSize param in SetMaxStorageSize should be greated than max log size. + /// + [TestMethod] + public async Task CannotSetMaxStorageSizeBelowMaxLogSize() + { + // Try to set max storage size without AppCenter start. + var dbSize = 10; + await AppCenter.SetMaxStorageSizeAsync(dbSize); + + // Verify. + _channelGroupMock.Verify(channelGroup => channelGroup.SetMaxStorageSizeAsync(dbSize), Times.Never()); + } } public class NullInstanceAppCenterService : IAppCenterService diff --git a/Tests/Microsoft.AppCenter.Test.Windows/Microsoft.AppCenter.Test.Windows.csproj b/Tests/Microsoft.AppCenter.Test.Windows/Microsoft.AppCenter.Test.Windows.csproj index 6b8a011be..118ec1624 100644 --- a/Tests/Microsoft.AppCenter.Test.Windows/Microsoft.AppCenter.Test.Windows.csproj +++ b/Tests/Microsoft.AppCenter.Test.Windows/Microsoft.AppCenter.Test.Windows.csproj @@ -82,6 +82,7 @@ + diff --git a/Tests/Microsoft.AppCenter.Test.Windows/Properties/AssemblyInfo.cs b/Tests/Microsoft.AppCenter.Test.Windows/Properties/AssemblyInfo.cs index 9753d8826..50ae4c377 100644 --- a/Tests/Microsoft.AppCenter.Test.Windows/Properties/AssemblyInfo.cs +++ b/Tests/Microsoft.AppCenter.Test.Windows/Properties/AssemblyInfo.cs @@ -19,5 +19,5 @@ // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("0.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] -[assembly: AssemblyInformationalVersion("4.0.0-SNAPSHOT")] +[assembly: AssemblyFileVersion("4.1.0.0")] +[assembly: AssemblyInformationalVersion("4.1.0-SNAPSHOT")] diff --git a/Tests/Microsoft.AppCenter.Test.Windows/Storage/FakeStorageTest.cs b/Tests/Microsoft.AppCenter.Test.Windows/Storage/FakeStorageTest.cs index c9e1ba401..43fded87b 100644 --- a/Tests/Microsoft.AppCenter.Test.Windows/Storage/FakeStorageTest.cs +++ b/Tests/Microsoft.AppCenter.Test.Windows/Storage/FakeStorageTest.cs @@ -83,7 +83,7 @@ public void GetLogsQueryError() { var mockAdapter = new Mock(); mockAdapter.Setup( - a => a.Select(TableName, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + a => a.Select(TableName, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Throws(new StorageException()); var fakeStorage = new Microsoft.AppCenter.Storage.Storage(mockAdapter.Object, It.IsAny()); var logs = new List(); @@ -99,7 +99,7 @@ public void StorageThrowsStorageException() { var mockAdapter = new Mock(); mockAdapter.Setup( - a => a.Select(TableName, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + a => a.Select(TableName, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Throws(new StorageException()); mockAdapter.Setup(c => c.Insert(TableName, It.IsAny(), It.IsAny>())) .Throws(new StorageException()); diff --git a/Tests/Microsoft.AppCenter.Test.Windows/Storage/MockStorage.cs b/Tests/Microsoft.AppCenter.Test.Windows/Storage/MockStorage.cs index 5a8f39afb..937484fd7 100644 --- a/Tests/Microsoft.AppCenter.Test.Windows/Storage/MockStorage.cs +++ b/Tests/Microsoft.AppCenter.Test.Windows/Storage/MockStorage.cs @@ -111,6 +111,14 @@ public Task ShutdownAsync(TimeSpan timeout) } } + public Task SetMaxStorageSizeAsync(long sizeInBytes) + { + lock (this) + { + return TaskExtension.GetCompletedTask(true); + } + } + public void Dispose() { } diff --git a/Tests/Microsoft.AppCenter.Test.Windows/Storage/StorageAdapterTest.cs b/Tests/Microsoft.AppCenter.Test.Windows/Storage/StorageAdapterTest.cs index 962665911..1f94794ae 100644 --- a/Tests/Microsoft.AppCenter.Test.Windows/Storage/StorageAdapterTest.cs +++ b/Tests/Microsoft.AppCenter.Test.Windows/Storage/StorageAdapterTest.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using Microsoft.AppCenter.Storage; +using Microsoft.AppCenter.Test.Windows.Utils; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; @@ -14,6 +15,8 @@ public class StorageAdapterTest { private StorageAdapter _adapter; + private StorageTestUtils _storageUtils; + // Constants data mocks. private const string StorageTestChannelName = "storageTestChannelName"; private const string TableName = "LogEntry"; @@ -35,6 +38,7 @@ public void TestInitialize() // Db file might not exist or might fail to be deleted. } _adapter = new StorageAdapter(); + _storageUtils = new StorageTestUtils(DatabasePath); } [TestCleanup] @@ -178,13 +182,64 @@ public void CreateTableAndInsertWorking() Assert.AreEqual(0, count); } + /// + /// Verify SetMaxStorageSize sets db size. + /// + [TestMethod] + public void SetMaxStorageSize() + { + // Prepare data. + InitializeStorageAdapter(); + + // Set storage max size and verify. + var dbSize = 2 * 1024 * 1024; + var success = _adapter.SetMaxStorageSize(dbSize); + Assert.IsTrue(success); + } + + /// + /// Verify that SetMaxStorageSize fails if it was attempted to shrink the database. + /// + [TestMethod] + public void CannotSetMaxStorageSizeWhenSizeIsBelowTheCurrentDataSize() + { + // Create test data and fill databse. + var minDataSize = 20 * 1024; + _storageUtils.FillStorageWithTestData(minDataSize); + _adapter.Initialize(DatabasePath); + + // Try to set max storage size and verify. + var dataSizeInBytes = _storageUtils.GetDataLengthInBytes(); + var dbSize = dataSizeInBytes - 12 * 1024; + var success = _adapter.SetMaxStorageSize(dbSize); + Assert.IsFalse(success); + } + + /// + /// Verify that SetMaxStorageSize succeeds if the parameter value is greater than current data size. + /// + [TestMethod] + public void CanSetMaxStorageSizeWhenSizeIsGreaterThanCurrentDataSize() + { + // Create test data and fill databse. + var minDataSize = 20 * 1024; + _storageUtils.FillStorageWithTestData(minDataSize); + _adapter.Initialize(DatabasePath); + + // Try to set max storage size and verify. + var dataSizeInBytes = _storageUtils.GetDataLengthInBytes(); + var dbSize = dataSizeInBytes + 12 * 1024; + var success = _adapter.SetMaxStorageSize(dbSize); + Assert.IsTrue(success); + } + #region Helper methods private void CreateTable() { - var tables = new[] { ColumnIdName, ColumnChannelName, ColumnLogName }; + var columns = new[] { ColumnIdName, ColumnChannelName, ColumnLogName }; var types = new[] { "INTEGER PRIMARY KEY AUTOINCREMENT", "TEXT NOT NULL", "TEXT NOT NULL" }; - _adapter.CreateTable(TableName, tables, types); + _adapter.CreateTable(TableName, columns, types); } private void InsertMockDataToTable() diff --git a/Tests/Microsoft.AppCenter.Test.Windows/Storage/StorageTest.cs b/Tests/Microsoft.AppCenter.Test.Windows/Storage/StorageTest.cs index 0bb4f35aa..22ca4fd64 100644 --- a/Tests/Microsoft.AppCenter.Test.Windows/Storage/StorageTest.cs +++ b/Tests/Microsoft.AppCenter.Test.Windows/Storage/StorageTest.cs @@ -2,11 +2,14 @@ // Licensed under the MIT License. using Microsoft.AppCenter.Ingestion.Models; +using Microsoft.AppCenter.Ingestion.Models.Serialization; using Microsoft.AppCenter.Storage; +using Microsoft.AppCenter.Test.Windows.Utils; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using System; using System.Collections.Generic; +using System.Text; using System.Threading.Tasks; namespace Microsoft.AppCenter.Test.Windows.Storage @@ -17,6 +20,8 @@ public class StorageTest private IStorage _storage; private string _databasePath; + private StorageTestUtils _storageUtils; + // Const for storage data. private const string StorageTestChannelName = "storageTestChannelName"; private const string TableName = "LogEntry"; @@ -29,6 +34,7 @@ public class StorageTest public void TestInitialize() { _databasePath = $"{Guid.NewGuid()}.db"; + _storageUtils = new StorageTestUtils(_databasePath); Microsoft.AppCenter.Utils.Constants.AppCenterDatabasePath = _databasePath; Microsoft.AppCenter.Utils.Constants.AppCenterFilesDirectoryPath = Environment.CurrentDirectory; _storage = new Microsoft.AppCenter.Storage.Storage(); @@ -341,8 +347,12 @@ public async Task RecreateCorruptedDatabaseOnInnerCorruptException() using (var storage = new Microsoft.AppCenter.Storage.Storage(mockStorageAdapter, _databasePath)) { var exception = new StorageCorruptedException("Mock exception"); + var testLog = TestLog.CreateTestLog(); + var testLogString = LogSerializer.Serialize(testLog); + var testLogSize = Encoding.UTF8.GetBytes(testLogString).Length; + Mock.Get(mockStorageAdapter).Setup(adapter => adapter.GetMaxStorageSize()).Returns(testLogSize + 1); Mock.Get(mockStorageAdapter).Setup(adapter => adapter.Insert(TableName, It.IsAny(), It.IsAny>())).Throws(exception); - await Assert.ThrowsExceptionAsync(() => storage.PutLog(StorageTestChannelName, TestLog.CreateTestLog())); + await Assert.ThrowsExceptionAsync(() => storage.PutLog(StorageTestChannelName, testLog)); Mock.Get(mockStorageAdapter).Verify(adapter => adapter.Dispose()); Mock.Get(mockStorageAdapter).Verify(adapter => adapter.Initialize(It.IsAny()), Times.Exactly(2)); } @@ -365,6 +375,138 @@ public async Task DoNotRecreateCorruptedDatabaseOnNotCorruptException() } } + /// + /// Verify that Storage forwards SetMaxStorageSize to its storageAdapter. + /// + [TestMethod] + public async Task SetMaxStorageSize() + { + var mockStorageAdapter = Mock.Of(); + using (var storage = new Microsoft.AppCenter.Storage.Storage(mockStorageAdapter, _databasePath)) + { + var dbSize = 2 * 1024 * 1024; + _ = await storage.SetMaxStorageSizeAsync(dbSize); + Mock.Get(mockStorageAdapter).Verify(adapter => adapter.SetMaxStorageSize(dbSize), Times.Once()); + } + } + + /// + /// Verify that new Logs are stored if storage is not full. + /// + [TestMethod] + public async Task AddLogsWhenBelowStorageCapacity() + { + // Set storage max size. + var capacity = 24 * 1024; + var logsDataSize = 12 * 1024; + var setMaxStorageSizeResult = await _storage.SetMaxStorageSizeAsync(logsDataSize); + Assert.IsTrue(setMaxStorageSizeResult); + + // Fill databse with logs. + await FillDatabaseWithLogs(logsDataSize); + + // Change storage max size. + setMaxStorageSizeResult = await _storage.SetMaxStorageSizeAsync(capacity); + Assert.IsTrue(setMaxStorageSizeResult); + + // Create a new logs and verify that they could be received. + var numLogsToAdd = 10; + var limit = numLogsToAdd; + var addedLogs = PutNLogs(numLogsToAdd); + var retrievedLogs = new List(); + await _storage.GetLogsAsync(StorageTestChannelName, int.MaxValue, retrievedLogs); + CollectionAssert.IsSubsetOf(addedLogs, retrievedLogs); + var dataSizeInBytes = _storageUtils.GetDataLengthInBytes(); + Assert.IsTrue(dataSizeInBytes > logsDataSize); + } + + /// + /// Verify that saving new logs does not exceed DB capacity. + /// + [TestMethod] + public async Task AddLogsDoesNotExceedCapacity() + { + // Set storage max size. + var capacity = 12 * 1024; + var setMaxStorageSizeResult = await _storage.SetMaxStorageSizeAsync(capacity); + Assert.IsTrue(setMaxStorageSizeResult); + + // Fill databse with logs. + await FillDatabaseWithLogs(capacity); + + // Put new logs and verify that database size can't be more then max size. + var numLogsToAdd = 10; + var limit = numLogsToAdd; + var addedLogs = PutNLogs(numLogsToAdd); + var dataSizeInBytes = _storageUtils.GetDataLengthInBytes(); + Assert.IsTrue(dataSizeInBytes <= capacity); + var retrievedLogs = new List(); + + // Verify that added logs could be received. + await _storage.GetLogsAsync(StorageTestChannelName, int.MaxValue, retrievedLogs); + CollectionAssert.IsSubsetOf(addedLogs, retrievedLogs); + } + + /// + /// Verify that new logs purge old ones. + /// + [TestMethod] + public async Task SaveLogPurgesOldestLogsWhenStorageIsFull() + { + // Set storage max size. + var capacity = 12 * 1024; + var setMaxStorageSizeResult = await _storage.SetMaxStorageSizeAsync(capacity); + Assert.IsTrue(setMaxStorageSizeResult); + + // Fill databse with logs. + var initialLogs = await FillDatabaseWithLogs(capacity); + var firstLog = initialLogs[0]; + + // Create a new log. Vverify that new log was added and old was deleted. + var newLog = TestLog.CreateTestLog(); + await _storage.PutLog(StorageTestChannelName, newLog); + var retrievedLogs = new List(); + await _storage.GetLogsAsync(StorageTestChannelName, int.MaxValue, retrievedLogs); + CollectionAssert.Contains(retrievedLogs, newLog); + CollectionAssert.DoesNotContain(retrievedLogs, firstLog); + } + + /// + /// Verify that new large log does not purge old ones. + /// + [TestMethod] + public async Task SaveLargeLogDoesNotPurgeOtherLogsWhenStorageIsFull() + { + // Set storage max size. + var capacity = 12 * 1024; + var setMaxStorageSizeResult = await _storage.SetMaxStorageSizeAsync(capacity); + Assert.IsTrue(setMaxStorageSizeResult); + + // Put 2 logs into databse. + var logs = PutNLogs(2); + + // Try to put large log into databse and verify that StorageException exception was trown. + var largeLogSize = capacity + 1; + var largeLog = CreateLogWithSize(largeLogSize); + try + { + await _storage.PutLog(StorageTestChannelName, largeLog); + } + catch (StorageException e) + { + var logJsonString = LogSerializer.Serialize(largeLog); + var logSize = Encoding.UTF8.GetBytes(logJsonString).Length; + Assert.AreEqual(e.Message, $"Log is too large ({logSize} bytes) to store in database. Current maximum database size is {capacity} bytes."); + } + + // Verify that logs in databse were not deleted. + var retrievedLogs = new List(); + await _storage.GetLogsAsync(StorageTestChannelName, int.MaxValue, retrievedLogs); + CollectionAssert.Contains(retrievedLogs, logs[0]); + CollectionAssert.Contains(retrievedLogs, logs[1]); + CollectionAssert.DoesNotContain(retrievedLogs, largeLog); + } + #region Helper methods private List PutNLogs(int n) @@ -380,6 +522,31 @@ private List PutNLogs(int n) return addedLogs; } + private TestLog CreateLogWithSize(int size) + { + var testLog = TestLog.CreateTestLog(); + + // There is a limitation for string size, so we tear into pieces. + var propsNum = size / 1000 + 1; + for (var i = 0; i < propsNum; i++) + { + testLog.Properties.Add($"largeProp{i}", new string('.', size)); + } + return testLog; + } + + private async Task> FillDatabaseWithLogs(int storageCapacity) + { + var log = TestLog.CreateTestLog(); + var logJsonString = LogSerializer.Serialize(log); + var logSize = Encoding.UTF8.GetBytes(logJsonString).Length; + var logsCount = storageCapacity / logSize; + var logs = PutNLogs(logsCount - 1); + logs.Add(log); + await _storage.PutLog(StorageTestChannelName, log); + return logs; + } + #endregion } } diff --git a/Tests/Microsoft.AppCenter.Test.Windows/Utils/StorageTestUtils.cs b/Tests/Microsoft.AppCenter.Test.Windows/Utils/StorageTestUtils.cs new file mode 100644 index 000000000..90969cf5b --- /dev/null +++ b/Tests/Microsoft.AppCenter.Test.Windows/Utils/StorageTestUtils.cs @@ -0,0 +1,80 @@ +using System; +using System.Linq; +using SQLitePCL; + +namespace Microsoft.AppCenter.Test.Windows.Utils +{ + class StorageTestUtils + { + private const string TableName = "TestTable"; + private const string ColumnIdName = "ID"; + private const string Column1Name = "TestCol1Name"; + private const string Column2Name = "TestCol2Name"; + + private string dbPath; + + public StorageTestUtils(string dbPath) + { + this.dbPath = dbPath; + } + + /// + /// Get current storage size in bytes. + /// + /// Current storage size in bytes. + public long GetDataLengthInBytes() + { + raw.sqlite3_open_v2(dbPath, out sqlite3 db, raw.SQLITE_OPEN_READONLY, null); + raw.sqlite3_prepare_v2(db, "PRAGMA page_count;", out var stmt); + raw.sqlite3_step(stmt); + var pageCount = raw.sqlite3_column_int(stmt, 0); + raw.sqlite3_finalize(stmt); + raw.sqlite3_prepare_v2(db, "PRAGMA page_size;", out stmt); + raw.sqlite3_step(stmt); + var pageSize = raw.sqlite3_column_int(stmt, 0); + raw.sqlite3_finalize(stmt); + raw.sqlite3_close(db); + return (long)pageCount * pageSize; + } + + /// + /// Fill storage with a test logs. + /// + /// Storage capacity. + public void FillStorageWithTestData(long dataSize) + { + var db = OpenDatabaseAndCreateTable(); + while (GetDataLengthInBytes() < dataSize) + { + AddTestDataToStorage(1000, db); + } + raw.sqlite3_close(db); + } + + private void AddTestDataToStorage(int count, sqlite3 db) + { + for (int i = 0; i < count; i++) + { + var query = $"INSERT INTO {TableName} ({ColumnIdName}, {Column1Name}, {Column2Name}) VALUES ({i}, 'col1-{i}', 'col2-{i}')"; + var insertResult = raw.sqlite3_exec(db, query); + } + } + + private sqlite3 OpenDatabase() + { + raw.sqlite3_open(dbPath, out sqlite3 db); + return db; + } + + private sqlite3 OpenDatabaseAndCreateTable() + { + var db = OpenDatabase(); + var columnNames = new[] { ColumnIdName, Column1Name, Column2Name }; + var columnTypes = new[] { "INTEGER PRIMARY KEY AUTOINCREMENT", "TEXT NOT NULL", "TEXT NOT NULL" }; + var cols = string.Join(",", Enumerable.Range(0, columnNames.Length).Select(i => $"{columnNames[i]} {columnTypes[i]}")); + var createResult = raw.sqlite3_exec(db, $"CREATE TABLE IF NOT EXISTS {TableName} ({cols});"); + Console.WriteLine($"created : {createResult == raw.SQLITE_OK}"); + return db; + } + } +} diff --git a/Tests/Microsoft.AppCenter.Test.WindowsDesktop/Properties/AssemblyInfo.cs b/Tests/Microsoft.AppCenter.Test.WindowsDesktop/Properties/AssemblyInfo.cs index 1b6063d3f..d680afc21 100644 --- a/Tests/Microsoft.AppCenter.Test.WindowsDesktop/Properties/AssemblyInfo.cs +++ b/Tests/Microsoft.AppCenter.Test.WindowsDesktop/Properties/AssemblyInfo.cs @@ -20,4 +20,4 @@ // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] +[assembly: AssemblyFileVersion("4.1.0.0")] diff --git a/cgmanifest.json b/cgmanifest.json index 574488b05..71d7fb36e 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -5,7 +5,7 @@ "type": "git", "git": { "repositoryUrl": "https://github.com/microsoft/appcenter-sdk-android.git", - "commitHash": "18c5eeea1b24ee9e04294d2f9cee8e14ddd64445" + "commitHash": "d01d7fec1b41e4280ccec54806543cee6f7587d6" } } }, @@ -14,7 +14,7 @@ "type": "git", "git": { "repositoryUrl": "https://github.com/microsoft/appcenter-sdk-apple.git", - "commitHash": "79d99eaff599d1b38adf81884fe220c75fd38867" + "commitHash": "484dead179e6d7edd534d0d7defdf65c14a5b426" } } } diff --git a/scripts/configuration/ac-build-config.xml b/scripts/configuration/ac-build-config.xml index 1133ec65f..7dd762d9b 100644 --- a/scripts/configuration/ac-build-config.xml +++ b/scripts/configuration/ac-build-config.xml @@ -1,9 +1,9 @@ - 4.0.0-SNAPSHOT - 4.0.0 - 4.0.0 + 4.1.0-SNAPSHOT + 4.1.0 + 4.1.0