Example #1
0
        private void HandleSendingFailure(State state, string batchId, Exception exception)
        {
            IList <Log> removedLogs;

            using (_mutex.GetLock(state))
            {
                removedLogs = _sendingBatches[batchId];
                _sendingBatches.Remove(batchId);
            }
            if (FailedToSendLog != null)
            {
                foreach (var log in removedLogs)
                {
                    AppCenterLog.Debug(AppCenterLog.LogTag, $"Invoke FailedToSendLog event for channel '{Name}'");
                    FailedToSendLog?.Invoke(this, new FailedToSendLogEventArgs(log, exception));
                }
            }
            try
            {
                _storage.DeleteLogs(Name, batchId);
            }
            catch (StorageException e)
            {
                AppCenterLog.Warn(AppCenterLog.LogTag, $"Could not delete logs for batch {batchId}", e);
            }
        }
Example #2
0
        bool ConfirmationHandler()
        {
            XamarinDevice.BeginInvokeOnMainThread(() =>
            {
                Current.MainPage.DisplayActionSheet("Crash detected. Send anonymous crash report?", null, null, "Send", "Always Send", "Don't Send").ContinueWith((arg) =>
                {
                    var answer = arg.Result;
                    UserConfirmation userConfirmationSelection;
                    if (answer == "Send")
                    {
                        userConfirmationSelection = UserConfirmation.Send;
                    }
                    else if (answer == "Always Send")
                    {
                        userConfirmationSelection = UserConfirmation.AlwaysSend;
                    }
                    else
                    {
                        userConfirmationSelection = UserConfirmation.DontSend;
                    }
                    AppCenterLog.Debug(LogTag, "User selected confirmation option: \"" + answer + "\"");
                    Crashes.NotifyUserConfirmation(userConfirmationSelection);
                });
            });

            return(true);
        }
Example #3
0
 private void SaveExceptionFile(Directory directory, string fileName, Exception exception)
 {
     try
     {
         using (var fileStream = NewFileStream(Path.Combine(directory.FullName, fileName), FileMode.Create))
         {
             var formatter = new BinaryFormatter();
             formatter.Serialize(fileStream, exception);
         }
         AppCenterLog.Debug(Crashes.LogTag, $"Saved exception in directory {ErrorStorageDirectoryName} with name {fileName}.");
     }
     catch (Exception e)
     {
         if (!exception.GetType().IsSerializable)
         {
             // Note that this still saves an empty file which acts as a marker for error report life cycle. Same as Android SDK.
             AppCenterLog.Warn(Crashes.LogTag, $"Cannot serialize {exception.GetType().FullName} exception for client side inspection. " +
                               "If you want to have access to the exception in the callbacks, please add a Serializable attribute " +
                               "and a deserialization constructor to the exception class.");
         }
         else
         {
             AppCenterLog.Warn(Crashes.LogTag, "Failed to serialize exception for client side inspection.", e);
         }
     }
 }
        // Determines whether the application has started already and is not suspended,
        // but ApplicationLifecycleHelper has not yet fired an initial "resume" event.
        private static async Task <bool> HasStartedAndNeedsResume()
        {
            var needsResume = false;

            try
            {
                // Don't use CurrentSynchronizationContext as that seems to cause an error in Unity applications.
                var asyncAction = CoreApplication.MainView?.CoreWindow?.Dispatcher.RunAsync(
                    CoreDispatcherPriority.Normal, () =>
                {
                    // If started already, a resume has already occurred.
                    if (_started)
                    {
                        return;
                    }
                    if (CoreApplication.Views.Any(view => view.CoreWindow != null &&
                                                  view.CoreWindow.Visible))
                    {
                        needsResume = true;
                    }
                });
                if (asyncAction != null)
                {
                    await asyncAction;
                }
            }
            catch (Exception e) when(e is COMException || e is InvalidOperationException)
            {
                // If MainView can't be accessed, a COMException or InvalidOperationException is thrown. It means that the
                // MainView hasn't been created, and thus the UI hasn't appeared yet.
                AppCenterLog.Debug(AppCenterLog.LogTag,
                                   "Not invoking resume immediately because UI is not ready.");
            }
            return(needsResume);
        }
Example #5
0
 private void HandleSendingFailure(State state, string batchId, IngestionException e)
 {
     AppCenterLog.Error(AppCenterLog.LogTag, $"Sending logs for channel '{Name}', batch '{batchId}' failed: {e?.Message}");
     try
     {
         var        isRecoverable = e?.IsRecoverable ?? false;
         List <Log> removedLogs;
         using (_mutex.GetLock(state))
         {
             removedLogs = _sendingBatches[batchId];
             _sendingBatches.Remove(batchId);
             if (isRecoverable)
             {
                 _pendingLogCount += removedLogs.Count;
             }
         }
         if (!isRecoverable && FailedToSendLog != null)
         {
             foreach (var log in removedLogs)
             {
                 FailedToSendLog?.Invoke(this, new FailedToSendLogEventArgs(log, e));
             }
         }
         Suspend(state, !isRecoverable, e);
     }
     catch (StatefulMutexException)
     {
         AppCenterLog.Debug(AppCenterLog.LogTag, "Handle sending failure operation has been canceled. Callbacks were invoked when channel suspended.");
     }
 }
Example #6
0
        private void CheckPendingLogs(State state)
        {
            if (!_enabled)
            {
                AppCenterLog.Info(AppCenterLog.LogTag, "The service has been disabled. Stop processing logs.");
                return;
            }

            AppCenterLog.Debug(AppCenterLog.LogTag, $"CheckPendingLogs({Name}) pending log count: {_pendingLogCount}");
            using (_mutex.GetLock())
            {
                if (_pendingLogCount >= _maxLogsPerBatch)
                {
                    _batchScheduled = true;
                    Task.Run(async() =>
                    {
                        await TriggerIngestionAsync(state).ConfigureAwait(false);
                    });
                }
                else if (_pendingLogCount > 0 && !_batchScheduled)
                {
                    _batchScheduled = true;

                    // No need wait _batchTimeInterval here.
                    Task.Run(async() =>
                    {
                        await Task.Delay((int)_batchTimeInterval.TotalMilliseconds).ConfigureAwait(false);
                        if (_batchScheduled)
                        {
                            await TriggerIngestionAsync(_mutex.State).ConfigureAwait(false);
                        }
                    });
                }
            }
        }
Example #7
0
        /// <summary>
        /// Asynchronously deletes all logs for a particular channel
        /// </summary>
        /// <param name="channelName">Name of the channel to delete logs for</param>
        /// <exception cref="StorageException"/>
        public Task DeleteLogs(string channelName)
        {
            var task = new Task(() =>
            {
                try
                {
                    AppCenterLog.Debug(AppCenterLog.LogTag,
                                       $"Deleting all logs from storage for channel '{channelName}'");
                    ClearPendingLogStateWithoutEnqueue(channelName);
                    _storageAdapter.DeleteAsync <LogEntry>(entry => entry.Channel == channelName)
                    .Wait();
                }
                catch (KeyNotFoundException e)
                {
                    throw new StorageException(e);
                }
            });

            try
            {
                _queue.Add(task);
            }
            catch (InvalidOperationException)
            {
                throw new StorageException("The operation has been cancelled");
            }
            _flushSemaphore.Release();
            return(task);
        }
Example #8
0
 /// <summary>
 /// Asynchronously deletes all logs in a particular batch
 /// </summary>
 /// <param name="channelName">The name of the channel associated with the batch</param>
 /// <param name="batchId">The batch identifier</param>
 /// <exception cref="StorageException"/>
 public Task DeleteLogs(string channelName, string batchId)
 {
     return(AddTaskToQueue(() =>
     {
         try
         {
             AppCenterLog.Debug(AppCenterLog.LogTag,
                                $"Deleting logs from storage for channel '{channelName}' with batch id '{batchId}'");
             var identifiers = _pendingDbIdentifierGroups[GetFullIdentifier(channelName, batchId)];
             _pendingDbIdentifierGroups.Remove(GetFullIdentifier(channelName, batchId));
             var deletedIdsMessage = "The IDs for deleting log(s) is/ are:";
             foreach (var id in identifiers)
             {
                 deletedIdsMessage += "\n\t" + id;
                 _pendingDbIdentifiers.Remove(id);
             }
             AppCenterLog.Debug(AppCenterLog.LogTag, deletedIdsMessage);
             foreach (var id in identifiers)
             {
                 _storageAdapter
                 .DeleteAsync <LogEntry>(entry => entry.Channel == channelName && entry.Id == id)
                 .GetAwaiter().GetResult();
             }
         }
         catch (KeyNotFoundException e)
         {
             throw new StorageException(e);
         }
     }));
 }
        public async Task ShutdownAsync()
        {
            ThrowIfDisposed();
            var tasks = new List <Task>();

            lock (_channelGroupLock)
            {
                if (_isShutdown)
                {
                    AppCenterLog.Warn(AppCenterLog.LogTag, "Attempted to shutdown channel multiple times.");
                    return;
                }
                _isShutdown = true;
                _ingestion.Close();
                foreach (var channel in _channels)
                {
                    tasks.Add(channel.ShutdownAsync());
                }
            }
            await Task.WhenAll(tasks).ConfigureAwait(false);

            AppCenterLog.Debug(AppCenterLog.LogTag, "Waiting for storage to finish operations.");
            if (!await _storage.ShutdownAsync(WaitStorageTimeout).ConfigureAwait(false))
            {
                AppCenterLog.Warn(AppCenterLog.LogTag, "Storage taking too long to finish operations; shutting down channel without waiting any longer.");
            }
        }
Example #10
0
 /// <summary>
 /// Asynchronously deletes all logs in a particular batch
 /// </summary>
 /// <param name="channelName">The name of the channel associated with the batch</param>
 /// <param name="batchId">The batch identifier</param>
 /// <exception cref="StorageException"/>
 public Task DeleteLogs(string channelName, string batchId)
 {
     return(AddTaskToQueue(() =>
     {
         try
         {
             AppCenterLog.Debug(AppCenterLog.LogTag,
                                $"Deleting logs from storage for channel '{channelName}' with batch id '{batchId}'");
             var identifiers = _pendingDbIdentifierGroups[GetFullIdentifier(channelName, batchId)];
             _pendingDbIdentifierGroups.Remove(GetFullIdentifier(channelName, batchId));
             var deletedIdsMessage = "The IDs for deleting log(s) is/are:";
             foreach (var identifier in identifiers)
             {
                 deletedIdsMessage += "\n\t" + identifier;
                 _pendingDbIdentifiers.Remove(identifier);
             }
             AppCenterLog.Debug(AppCenterLog.LogTag, deletedIdsMessage);
             _storageAdapter.Delete(TableName, ColumnIdName, identifiers.Cast <object>().ToArray());
         }
         catch (KeyNotFoundException e)
         {
             throw new StorageException(e);
         }
     }));
 }
        internal ErrorReport(AndroidErrorReport androidReport)
        {
            Id           = androidReport.Id;
            AppStartTime = DateTimeOffset.FromUnixTimeMilliseconds(androidReport.AppStartTime.Time);
            AppErrorTime = DateTimeOffset.FromUnixTimeMilliseconds(androidReport.AppErrorTime.Time);
            Device       = androidReport.Device == null ? null : new Device(androidReport.Device);
            object androidThrowable;

            try
            {
                androidThrowable = androidReport.Throwable;
            }
            catch (Exception e)
            {
                AppCenterLog.Debug(Crashes.LogTag, "Cannot read throwable from java point of view, probably a .NET exception", e);
                androidThrowable = null;
            }
            AndroidDetails = new AndroidErrorDetails(androidThrowable, androidReport.ThreadName);
            iOSDetails     = null;
            byte[] exceptionBytes = AndroidExceptionDataManager.LoadWrapperExceptionData(Java.Util.UUID.FromString(Id));
            if (exceptionBytes != null)
            {
                StackTrace = CrashesUtils.DeserializeException(exceptionBytes);
            }
        }
Example #12
0
        private async Task SignalDeletingLogs(IList <string> sendingBatches)
        {
            var logs = new List <Log>();

            try
            {
                do
                {
                    var batchId = await _storage.GetLogsAsync(Name, ClearBatchSize, logs).ConfigureAwait(false);

                    if (sendingBatches.Contains(batchId))
                    {
                        continue;
                    }
                    foreach (var log in logs)
                    {
                        AppCenterLog.Debug(AppCenterLog.LogTag, $"Invoke SendingLog for channel '{Name}'");
                        SendingLog?.Invoke(this, new SendingLogEventArgs(log));
                        AppCenterLog.Debug(AppCenterLog.LogTag, $"Invoke FailedToSendLog event for channel '{Name}'");
                        FailedToSendLog?.Invoke(this, new FailedToSendLogEventArgs(log, new CancellationException()));
                    }
                }while (logs.Count >= ClearBatchSize);
            }
            catch
            {
                AppCenterLog.Warn(AppCenterLog.LogTag, "Failed to invoke events for logs being deleted.");
            }
        }
        private void OnPushNotificationReceivedHandler(PushNotificationChannel sender, WindowsPushNotificationReceivedEventArgs e)
        {
            XmlDocument content = null;

            if (e.NotificationType == PushNotificationType.Toast && (content = e.ToastNotification?.Content) != null)
            {
                AppCenterLog.Debug(LogTag, $"Received push notification payload: {content.GetXml()}");
                if (ApplicationLifecycleHelper.Instance.IsSuspended)
                {
                    AppCenterLog.Debug(LogTag, "Application in background. Push callback will be called when user clicks the toast notification.");
                }
                else
                {
                    var pushNotification = ParseAppCenterPush(content);
                    if (pushNotification != null)
                    {
                        e.Cancel = true;
                        PushNotificationReceived?.Invoke(sender, pushNotification);
                        AppCenterLog.Debug(LogTag, "Application in foreground. Intercept push notification and invoke push callback.");
                    }
                    else
                    {
                        AppCenterLog.Debug(LogTag, "Push ignored. It was not sent through App Center.");
                    }
                }
            }
            else
            {
                AppCenterLog.Debug(LogTag, $"Push ignored. We only handle Toast notifications but PushNotificationType is '{e.NotificationType}'.");
            }
        }
        /// <summary>
        /// Saves an error log on disk.
        /// </summary>
        /// <param name="exception">The exception that caused the crash.</param>
        /// <param name="errorLog">The error log.</param>
        public virtual void InstanceSaveErrorLogFiles(System.Exception exception, ManagedErrorLog errorLog)
        {
            try
            {
                // Serialize main log file.
                var errorLogString   = LogSerializer.Serialize(errorLog);
                var errorLogFileName = errorLog.Id + ErrorLogFileExtension;
                AppCenterLog.Debug(Crashes.LogTag, "Saving uncaught exception.");
                var directory = InstanceGetErrorStorageDirectory();
                directory.CreateFile(errorLogFileName, errorLogString);
                AppCenterLog.Debug(Crashes.LogTag, $"Saved error log in directory {ErrorStorageDirectoryName} with name {errorLogFileName}.");

                try
                {
                    // Serialize exception as raw stack trace.
                    var exceptionFileName = errorLog.Id + ExceptionFileExtension;
                    directory.CreateFile(exceptionFileName, exception.ToString());
                    AppCenterLog.Debug(Crashes.LogTag, $"Saved exception in directory {ErrorStorageDirectoryName} with name {exceptionFileName}.");
                }
                catch (System.Exception ex)
                {
                    AppCenterLog.Warn(Crashes.LogTag, "Failed to serialize exception for client side inspection.", ex);
                }
            }
            catch (System.Exception ex)
            {
                AppCenterLog.Error(Crashes.LogTag, "Failed to save error log.", ex);
            }
        }
Example #15
0
        private async Task TriggerIngestionAsync(State state)
        {
            using (await _mutex.GetLockAsync(state).ConfigureAwait(false))
            {
                if (!_enabled || !_batchScheduled)
                {
                    return;
                }
                AppCenterLog.Debug(AppCenterLog.LogTag,
                                   $"TriggerIngestion({Name}) pending log count: {_pendingLogCount}");
                _batchScheduled = false;
                if (_sendingBatches.Count >= _maxParallelBatches)
                {
                    AppCenterLog.Debug(AppCenterLog.LogTag,
                                       $"Already sending {_maxParallelBatches} batches of analytics data to the server");
                    return;
                }
            }

            // Get a batch from storage
            var logs    = new List <Log>();
            var batchId = await _storage.GetLogsAsync(Name, _maxLogsPerBatch, logs).ConfigureAwait(false);

            if (batchId != null)
            {
                using (await _mutex.GetLockAsync(state).ConfigureAwait(false))
                {
                    _sendingBatches.Add(batchId, logs);
                    _pendingLogCount -= logs.Count;
                }
                try
                {
                    // Before sending logs, trigger the sending event for this channel
                    if (SendingLog != null)
                    {
                        foreach (var log in logs)
                        {
                            AppCenterLog.Debug(AppCenterLog.LogTag, $"Invoke SendingLog event for channel '{Name}'");
                            SendingLog?.Invoke(this, new SendingLogEventArgs(log));
                        }
                    }

                    // If the optional Install ID has no value, default to using empty GUID
                    var installId = await AppCenter.GetInstallIdAsync().ConfigureAwait(false) ?? Guid.Empty;

                    var ingestionCall = _ingestion.Call(_appSecret, installId, logs);
                    using (await _mutex.GetLockAsync(state).ConfigureAwait(false))
                    {
                        _calls.Add(ingestionCall);
                    }
                    ingestionCall.ContinueWith(call => HandleSendingResult(state, batchId, call));
                    CheckPendingLogs(state);
                }
                catch (StorageException)
                {
                    AppCenterLog.Warn(AppCenterLog.LogTag, "Something went wrong sending logs to ingestion");
                }
            }
        }
Example #16
0
 /// <summary>
 /// Asynchronously clears the stored state of logs that have been retrieved
 /// </summary>
 /// <param name="channelName"></param>
 public Task ClearPendingLogState(string channelName)
 {
     return(AddTaskToQueue(() =>
     {
         ClearPendingLogStateWithoutEnqueue(channelName);
         AppCenterLog.Debug(AppCenterLog.LogTag, $"Clear pending log states for channel {channelName}");
     }));
 }
Example #17
0
 /// <summary>
 /// Creates an instance of Storage given a connection object.
 /// </summary>
 internal Storage(IStorageAdapter adapter, string databasePath)
 {
     AppCenterLog.Debug(AppCenterLog.LogTag, $"Creating database at: {databasePath}");
     _storageAdapter = adapter;
     _databasePath   = databasePath;
     _queue.Add(new Task(InitializeDatabase));
     _queueFlushTask = Task.Run(FlushQueueAsync);
 }
Example #18
0
 public Task DeleteLogs(string channelName)
 {
     lock (this)
     {
         AppCenterLog.Debug(AppCenterLog.LogTag, $"Storage.DeleteLogs for channelName={channelName}");
         _storage.Remove(channelName);
         return(TaskExtension.GetCompletedTask());
     }
 }
Example #19
0
 public Task PutLog(string channelName, Log log)
 {
     lock (this)
     {
         AppCenterLog.Debug(AppCenterLog.LogTag, $"Storage.PutLog for channelName={channelName}: {log}");
         this[channelName].Add(log);
         return(TaskExtension.GetCompletedTask());
     }
 }
Example #20
0
 internal static void EnableTls12()
 {
     // ReSharper disable once InvertIf
     if ((ServicePointManager.SecurityProtocol & SecurityProtocolType.Tls12) != SecurityProtocolType.Tls12)
     {
         ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
         AppCenterLog.Debug(AppCenterLog.LogTag, "Enabled TLS 1.2 explicitly as it was disabled.");
     }
 }
Example #21
0
 public Task DeleteLogs(string channelName, string batchId)
 {
     lock (this)
     {
         AppCenterLog.Debug(AppCenterLog.LogTag, $"Storage.DeleteLogs for channelName={channelName} and batchId={batchId}");
         var batch = _pending[batchId];
         this[channelName].RemoveAll(log => batch.Contains(log));
         return(TaskExtension.GetCompletedTask());
     }
 }
Example #22
0
        /// <summary>
        /// If enabled, register push channel and send URI to backend.
        /// Also start intercepting pushes.
        /// If disabled and previously enabled, stop listening for pushes (they will still be received though).
        /// </summary>
        private void ApplyEnabledState(bool enabled)
        {
            if (enabled)
            {
                // We expect caller of this method to lock on _mutex, we can't do it here as that lock is not recursive
                AppCenterLog.Debug(LogTag, "Getting push token...");
                var state = _mutex.State;
                CreatePushNotificationChannel(channel =>
                {
                    try
                    {
                        using (_mutex.GetLock(state))
                        {
                            LatestPushToken = channel?.Uri;
                            if (!string.IsNullOrEmpty(LatestPushToken))
                            {
                                // Save channel member
                                _channel = channel;

                                // Subscribe to UserId Change.
                                UserIdContext.UserIdUpdated -= OnUserIdUpdated;
                                UserIdContext.UserIdUpdated += OnUserIdUpdated;

                                // Subscribe to push.
                                channel.PushNotificationReceived += OnPushNotificationReceivedHandler;

                                // Send channel URI to backend
                                AppCenterLog.Debug(LogTag, $"Push token '{LatestPushToken}'");
                                var pushInstallationLog = new PushInstallationLog(null, LatestPushToken, null, Guid.NewGuid(), UserIdContext.Instance.UserId);

                                // Do not await the call to EnqueueAsync or the UI thread can be blocked!
#pragma warning disable CS4014
                                Channel.EnqueueAsync(pushInstallationLog);
#pragma warning restore
                            }
                            else
                            {
                                AppCenterLog.Error(LogTag, "Push service registering with App Center backend has failed.");
                            }
                        }
                    }
                    catch (StatefulMutexException)
                    {
                        AppCenterLog.Warn(LogTag, "Push Enabled state changed after creating channel.");
                    }
                });
            }
            else if (_channel != null)
            {
                LatestPushToken                    = null;
                UserIdContext.UserIdUpdated       -= OnUserIdUpdated;
                _channel.PushNotificationReceived -= OnPushNotificationReceivedHandler;
            }
        }
        private void CreatePushNotificationChannel(Action <PushNotificationChannel> created)
        {
            Task <PushNotificationChannel> CreatePushNotificationChannelForApplicationAsync()
            {
                try
                {
                    return(new WindowsPushNotificationChannelManager()
                           .CreatePushNotificationChannelForApplicationAsync()
                           .AsTask());
                }
                catch (Exception e)
                {
                    return(Task.FromException <PushNotificationChannel>(e));
                }
            }

            void OnNetworkStateChange(object sender, EventArgs e)
            {
                if (NetworkStateAdapter.IsConnected)
                {
                    NetworkStateAdapter.NetworkStatusChanged -= OnNetworkStateChange;
                    AppCenterLog.Debug(LogTag, "Second attempt to create notification channel...");

                    // Second attempt is the last one anyway.
                    CreatePushNotificationChannelForApplicationAsync().ContinueWith(task =>
                    {
                        if (task.IsFaulted)
                        {
                            AppCenterLog.Error(LogTag, "Unable to create notification channel.", task.Exception);
                            return;
                        }
                        created?.Invoke(task.Result);
                    });
                }
            }

            // If this isn't the first time after installation, the notification channel is created successfully even without network.
            CreatePushNotificationChannelForApplicationAsync().ContinueWith(task =>
            {
                if (task.IsFaulted && NetworkStateAdapter.IsConnected)
                {
                    AppCenterLog.Error(LogTag, "Unable to create notification channel.", task.Exception);
                    return;
                }
                if (!task.IsFaulted && task.Result != null)
                {
                    created?.Invoke(task.Result);
                    return;
                }
                AppCenterLog.Debug(LogTag, "The network isn't connected, another attempt to crate push notification channel " +
                                   "will be made after the network is available.");
                NetworkStateAdapter.NetworkStatusChanged += OnNetworkStateChange;
            });
        }
 /// <exception cref="AppCenterException">Attempted to add duplicate channel to group</exception>
 public IChannelUnit AddChannel(string name, int maxLogsPerBatch, TimeSpan batchTimeInterval, int maxParallelBatches)
 {
     ThrowIfDisposed();
     lock (_channelGroupLock)
     {
         AppCenterLog.Debug(AppCenterLog.LogTag, $"AddChannel({name})");
         var newChannel = new Channel(name, maxLogsPerBatch, batchTimeInterval, maxParallelBatches, AppSecret,
                                      _ingestion, _storage);
         AddChannel(newChannel);
         return(newChannel);
     }
 }
Example #25
0
 /// <summary>
 /// Suspend channel.
 /// </summary>
 /// <param name="state">Current state.</param>
 /// <param name="deleteLogs">Value indicating whether logs should be enabled or disabled.</param>
 /// <param name="exception">Possible error if unsuccessful.</param>
 /// <param name="needDisableChannel">Value indicating whether channel should be disabled. True by default.</param>
 private void Suspend(State state, bool deleteLogs, Exception exception, bool needDisableChannel = true)
 {
     AppCenterLog.Debug(AppCenterLog.LogTag, $"Suspend channel: '{Name}'");
     try
     {
         IList <string> sendingBatches = null;
         IList <Log>    unsentLogs     = null;
         using (_mutex.GetLock(state))
         {
             if (needDisableChannel)
             {
                 _enabled = false;
             }
             _batchScheduled = false;
             _discardLogs    = deleteLogs;
             if (deleteLogs)
             {
                 sendingBatches = _sendingBatches.Keys.ToList();
                 unsentLogs     = _sendingBatches.Values.SelectMany(batch => batch).ToList();
                 _sendingBatches.Clear();
             }
             state = _mutex.InvalidateState();
         }
         if (unsentLogs != null && FailedToSendLog != null)
         {
             foreach (var log in unsentLogs)
             {
                 AppCenterLog.Debug(AppCenterLog.LogTag, $"Invoke FailedToSendLog event for channel '{Name}'");
                 FailedToSendLog?.Invoke(this, new FailedToSendLogEventArgs(log, exception));
             }
         }
         if (deleteLogs)
         {
             IList <IServiceCall> calls;
             using (_mutex.GetLock(state))
             {
                 calls = _calls.ToList();
                 _calls.Clear();
                 _pendingLogCount = 0;
                 TriggerDeleteLogsOnSuspending(sendingBatches);
             }
             foreach (var call in calls)
             {
                 call.Cancel();
             }
         }
         _storage.ClearPendingLogState(Name);
     }
     catch (StatefulMutexException)
     {
         AppCenterLog.Warn(AppCenterLog.LogTag, "The suspend operation has been canceled");
     }
 }
Example #26
0
        /// <summary>
        /// Asynchronously retrieves logs from storage and flags them to avoid duplicate retrievals on subsequent calls
        /// </summary>
        /// <param name="channelName">Name of the channel to retrieve logs from</param>
        /// <param name="limit">The maximum number of logs to retrieve</param>
        /// <param name="logs">A list to which the retrieved logs will be added</param>
        /// <returns>A batch ID for the set of returned logs; null if no logs are found</returns>
        /// <exception cref="StorageException"/>
        public Task <string> GetLogsAsync(string channelName, int limit, List <Log> logs)
        {
            return(AddTaskToQueue(() =>
            {
                logs?.Clear();
                var retrievedLogs = new List <Log>();
                AppCenterLog.Debug(AppCenterLog.LogTag,
                                   $"Trying to get up to {limit} logs from storage for {channelName}");
                var idPairs = new List <Tuple <Guid?, long> >();
                var failedToDeserializeALog = false;
                var objectEntries = _storageAdapter.Select(TableName, ColumnChannelName, channelName, ColumnIdName, _pendingDbIdentifiers.Cast <object>().ToArray(), limit);
                var retrievedEntries = objectEntries.Select(entries =>
                                                            new LogEntry()
                {
                    Id = (long)entries[0],
                    Channel = (string)entries[1],
                    Log = (string)entries[2]
                }
                                                            ).ToList();
                foreach (var entry in retrievedEntries)
                {
                    try
                    {
                        var log = LogSerializer.DeserializeLog(entry.Log);
                        retrievedLogs.Add(log);
                        idPairs.Add(Tuple.Create(log.Sid, Convert.ToInt64(entry.Id)));
                    }
                    catch (JsonException e)
                    {
                        AppCenterLog.Error(AppCenterLog.LogTag, "Cannot deserialize a log in storage", e);
                        failedToDeserializeALog = true;
                        _storageAdapter.Delete(TableName, ColumnIdName, entry.Id);
                    }
                }
                if (failedToDeserializeALog)
                {
                    AppCenterLog.Warn(AppCenterLog.LogTag, "Deleted logs that could not be deserialized");
                }
                if (idPairs.Count == 0)
                {
                    AppCenterLog.Debug(AppCenterLog.LogTag,
                                       $"No available logs in storage for channel '{channelName}'");
                    return null;
                }

                // Process the results
                var batchId = Guid.NewGuid().ToString();
                ProcessLogIds(channelName, batchId, idPairs);
                logs?.AddRange(retrievedLogs);
                return batchId;
            }));
        }
Example #27
0
        // Internal and static so that it can be tested more easily
        internal static bool HasSessionTimedOut(long now, long lastQueuedLogTime, long lastResumedTime, long lastPausedTime)
        {
            if (lastPausedTime == 0)
            {
                return(false);
            }
            var noLogSentForLong     = lastQueuedLogTime == 0 || now - lastQueuedLogTime >= SessionTimeout;
            var wasBackgroundForLong = lastResumedTime - Math.Max(lastPausedTime, lastQueuedLogTime) >= SessionTimeout;

            AppCenterLog.Debug(Analytics.Instance.LogTag, $"noLogSentForLong={noLogSentForLong} " +
                               $"wasBackgroundForLong={wasBackgroundForLong}");
            return(noLogSentForLong && wasBackgroundForLong);
        }
Example #28
0
        /// <summary>
        /// Asynchronously retrieves logs from storage and flags them to avoid duplicate retrievals on subsequent calls
        /// </summary>
        /// <param name="channelName">Name of the channel to retrieve logs from</param>
        /// <param name="limit">The maximum number of logs to retrieve</param>
        /// <param name="logs">A list to which the retrieved logs will be added</param>
        /// <returns>A batch ID for the set of returned logs; null if no logs are found</returns>
        /// <exception cref="StorageException"/>
        public Task <string> GetLogsAsync(string channelName, int limit, List <Log> logs)
        {
            return(AddTaskToQueue(() =>
            {
                logs?.Clear();
                var retrievedLogs = new List <Log>();
                AppCenterLog.Debug(AppCenterLog.LogTag,
                                   $"Trying to get up to {limit} logs from storage for {channelName}");
                var idPairs = new List <Tuple <Guid?, long> >();
                var failedToDeserializeALog = false;
                var retrievedEntries =
                    _storageAdapter.GetAsync <LogEntry>(entry => entry.Channel == channelName, limit)
                    .GetAwaiter().GetResult();
                foreach (var entry in retrievedEntries)
                {
                    if (_pendingDbIdentifiers.Contains(entry.Id))
                    {
                        continue;
                    }
                    try
                    {
                        var log = LogSerializer.DeserializeLog(entry.Log);
                        retrievedLogs.Add(log);
                        idPairs.Add(Tuple.Create(log.Sid, Convert.ToInt64(entry.Id)));
                    }
                    catch (JsonException e)
                    {
                        AppCenterLog.Error(AppCenterLog.LogTag, "Cannot deserialize a log in storage", e);
                        failedToDeserializeALog = true;
                        _storageAdapter.DeleteAsync <LogEntry>(row => row.Id == entry.Id)
                        .GetAwaiter().GetResult();
                    }
                }
                if (failedToDeserializeALog)
                {
                    AppCenterLog.Warn(AppCenterLog.LogTag, "Deleted logs that could not be deserialized");
                }
                if (idPairs.Count == 0)
                {
                    AppCenterLog.Debug(AppCenterLog.LogTag,
                                       $"No available logs in storage for channel '{channelName}'");
                    return null;
                }

                // Process the results
                var batchId = Guid.NewGuid().ToString();
                ProcessLogIds(channelName, batchId, idPairs);
                logs?.AddRange(retrievedLogs);
                return batchId;
            }));
        }
        private async Task <PushNotificationChannel> CreatePushNotificationChannelAsync()
        {
            PushNotificationChannel channel = null;

            try
            {
                // If this isn't the first time after installation, the notification channel is created successfully even without network.
                channel = await new WindowsPushNotificationChannelManager().CreatePushNotificationChannelForApplicationAsync()
                          .AsTask().ConfigureAwait(false);
            }
            catch (Exception exception)
            {
                if (NetworkStateAdapter.IsConnected)
                {
                    AppCenterLog.Error(LogTag, "Unable to create notification channel.", exception);
                    return(null);
                }
            }
            if (channel != null)
            {
                return(channel);
            }
            AppCenterLog.Debug(LogTag, "The network isn't connected, another attempt will be made after the network is available.");
            var networkSemaphore = new SemaphoreSlim(0);

            void NetworkStateChangeHandler(object sender, EventArgs e)
            {
                if (NetworkStateAdapter.IsConnected)
                {
                    networkSemaphore.Release();
                }
            }

            NetworkStateAdapter.NetworkStatusChanged += NetworkStateChangeHandler;
            await networkSemaphore.WaitAsync().ConfigureAwait(false);

            NetworkStateAdapter.NetworkStatusChanged -= NetworkStateChangeHandler;
            AppCenterLog.Debug(LogTag, "Second attempt to create notification channel...");
            try
            {
                // Second attempt is the last one anyway.
                return(await new WindowsPushNotificationChannelManager().CreatePushNotificationChannelForApplicationAsync()
                       .AsTask().ConfigureAwait(false));
            }
            catch (Exception exception)
            {
                AppCenterLog.Error(LogTag, "Unable to create notification channel.", exception);
                return(null);
            }
        }
 public void Pause()
 {
     lock (_lockObject)
     {
         if (_currentSessionState == SessionState.Inactive)
         {
             AppCenterLog.Warn(Analytics.Instance.LogTag, "Trying to pause already inactive session.");
             return;
         }
         AppCenterLog.Debug(Analytics.Instance.LogTag, "SessionTracker.Pause");
         _lastPausedTime      = TimeHelper.CurrentTimeInMilliseconds();
         _currentSessionState = SessionState.Inactive;
     }
 }