/// <summary> /// Fragt alle Benachrichtigungen ab, die dem Kanal mit der angegebenen Id zugeordnet sind. /// Die Abfrage kann durch den Nachrichtennummer Parameter eingeschränkt werden, so dass nur Benachrichtigungen mit /// einer höhreren Nachrichtennummer als der angegebenen abgerufen werden. /// </summary> /// <param name="channelId">Die Id des Kanals, für den die Benachrichtigungen abgefragt werden sollen.</param> /// <param name="messageNr">Die Nachrichtennummer, aber der die Nachrichten abgerufen werden sollen.</param> /// <returns>Eine Liste von Objekten des Typs Announcement.</returns> /// <exception cref="DatabaseException">Wirft DatabaseException, wenn Abruf fehlschlägt.</exception> public List<Announcement> GetAnnouncementsOfChannel(int channelId, int messageNr) { List<Announcement> announcements = new List<Announcement>(); // Frage das Mutex Objekt ab. Mutex mutex = DatabaseManager.GetDatabaseAccessMutexObject(); // Fordere Zugriff auf die Datenbank an. if (mutex.WaitOne(DatabaseManager.MutexTimeoutValue)) { using (SQLiteConnection conn = DatabaseManager.GetConnection()) { try { string query = @"SELECT * FROM Message AS m JOIN Announcement AS a ON m.Id=a.Message_Id WHERE Channel_Id=? AND MessageNumber > ?;"; using (var stmt = conn.Prepare(query)) { stmt.Bind(1, channelId); stmt.Bind(2, messageNr); while (stmt.Step() == SQLiteResult.ROW) { int id = Convert.ToInt32(stmt["Id"]); string text = (string)stmt["Text"]; DateTimeOffset creationDate = DatabaseManager.DateTimeFromSQLite(stmt["CreationDate"].ToString()); Priority priority = (Priority)Enum.ToObject(typeof(Priority), stmt["Priority"]); bool read = ((long)stmt["Read"] == 1) ? true : false; int messageNrDB = Convert.ToInt32(stmt["MessageNumber"]); int authorId = Convert.ToInt32(stmt["Author_Moderator_Id"]); string title = (string)stmt["Title"]; Announcement announcement = new Announcement(id, text, messageNrDB, creationDate, priority, read, channelId, authorId, title); announcements.Add(announcement); } } } catch (SQLiteException sqlEx) { Debug.WriteLine("GetAnnouncementsOfChannel: SQLiteException occurred. Msg is {0}.", sqlEx.Message); throw new DatabaseException(sqlEx.Message); } catch (Exception ex) { Debug.WriteLine("GetAnnouncementsOfChannel: Exception occurred. Msg is {0}.", ex.Message); throw new DatabaseException(ex.Message); } finally { mutex.ReleaseMutex(); } } } else { Debug.WriteLine("GetAnnouncementsOfChannel: Mutex timeout."); throw new DatabaseException("GetAnnouncementsOfChannel: Timeout: Failed to get access to DB."); } return announcements; }
/// <summary> /// Holt die angegebene Anzahl an aktuellesten Announcements aus der Datenbank. Die aktuellesten /// Announcements sind dabei diejenigen, die zeitlich gesehen zuletzt gesendet wurden. Über den Offset /// kann angegeben werden, dass diese Anzahl an Announcements übersprungen werden soll. Das ist für das /// inkrementelle Laden von älteren Announcements wichtig. /// </summary> /// <param name="channelId">Die Id des Kanals, von dem die Announcements abgerufen werden sollen.</param> /// <param name="number">Die Anzahl an Announcements, die abgerufen werden soll.</param> /// <param name="offset">Der Offset, der angibt wie viele der neusten Announcements übersprungen werden sollen.</param> /// <returns>Eine Liste von Announcement Objekten.</returns> /// <exception cref="DatbaseException">Wirft DatabaseException, wenn der Abfruf der Announcements fehlschlägt.</exception> public List<Announcement> GetLatestAnnouncements(int channelId, int number, int offset) { List<Announcement> latestAnnouncements = new List<Announcement>(); // Frage das Mutex Objekt ab. Mutex mutex = DatabaseManager.GetDatabaseAccessMutexObject(); // Fordere Zugriff auf die Datenbank an. if (mutex.WaitOne(DatabaseManager.MutexTimeoutValue)) { using (SQLiteConnection conn = DatabaseManager.GetConnection()) { try { string query = @"SELECT * FROM Message AS m JOIN Announcement AS a ON m.Id=a.Message_Id WHERE Channel_Id=? ORDER BY a.MessageNumber DESC LIMIT ? OFFSET ?;"; using (var stmt = conn.Prepare(query)) { stmt.Bind(1, channelId); stmt.Bind(2, number); stmt.Bind(3, offset); while (stmt.Step() == SQLiteResult.ROW) { int id = Convert.ToInt32(stmt["Id"]); string text = (string)stmt["Text"]; DateTimeOffset creationDate = DatabaseManager.DateTimeFromSQLite(stmt["CreationDate"].ToString()); Priority priority = (Priority)Enum.ToObject(typeof(Priority), stmt["Priority"]); bool read = ((long)stmt["Read"] == 1) ? true : false; int messageNr = Convert.ToInt32(stmt["MessageNumber"]); int authorId = Convert.ToInt32(stmt["Author_Moderator_Id"]); string title = (string)stmt["Title"]; Announcement announcement = new Announcement(id, text, messageNr, creationDate, priority, read, channelId, authorId, title); latestAnnouncements.Add(announcement); } } } catch (SQLiteException sqlEx) { Debug.WriteLine("GetLatestAnnouncements has failed. The message is: {0}.", sqlEx.Message); throw new DatabaseException(sqlEx.Message); } catch (Exception ex) { Debug.WriteLine("GetLatestAnnouncements has failed. The message is: {0} and stack trace is {1}.", ex.Message, ex.StackTrace); throw new DatabaseException(ex.Message); } finally { mutex.ReleaseMutex(); } } // Ende des using Block. } else { Debug.WriteLine("Couldn't get access to database. Time out."); throw new DatabaseException("Could not get access to the database."); } return latestAnnouncements; }
/// <summary> /// Speichere die Daten der gegebenen Announcement in der Datenbank ab. /// </summary> /// <param name="announcement">Das Announcement Objekt mit den Announcement Daten.</param> /// <exception cref="DatabaseException">Wirft eine Exception, wenn die Speicherung fehlschlägt.</exception> public void StoreAnnouncement(Announcement announcement) { if(announcement == null) { Debug.WriteLine("No valid announcement object passed to the StoreAnnouncement method."); return; } bool successful = true; // Frage das Mutex Objekt ab. Mutex mutex = DatabaseManager.GetDatabaseAccessMutexObject(); // Fordere Zugriff auf die Datenbank an. if (mutex.WaitOne(DatabaseManager.MutexTimeoutValue)) { using (SQLiteConnection conn = DatabaseManager.GetConnection()) { try { // Starte eine Transaktion. using (var statement = conn.Prepare("BEGIN TRANSACTION")) { statement.Step(); } // Speichere Daten in Message Tabelle. using (var insertMessageStmt = conn.Prepare(@"INSERT INTO Message (Id, Text, CreationDate, Priority, Read) VALUES (?,?,?,?,?);")) { insertMessageStmt.Bind(1, announcement.Id); insertMessageStmt.Bind(2, announcement.Text); insertMessageStmt.Bind(3, DatabaseManager.DateTimeToSQLite(announcement.CreationDate)); insertMessageStmt.Bind(4, (int)announcement.MessagePriority); insertMessageStmt.Bind(5, 0); // Nachricht noch nicht gelesen. if (insertMessageStmt.Step() != SQLiteResult.DONE) { Debug.WriteLine("StoreAnnouncement: Failed to store message part of announcement with " + "id {0}.", announcement.Id); successful = false; } } // Speichere Daten in Announcement Tabelle. using (var insertAnnouncementStmt = conn.Prepare(@"INSERT INTO Announcement (MessageNumber, Channel_Id, Title, Author_Moderator_Id, Message_Id) VALUES (?,?,?,?,?);")) { insertAnnouncementStmt.Bind(1, announcement.MessageNumber); insertAnnouncementStmt.Bind(2, announcement.ChannelId); insertAnnouncementStmt.Bind(3, announcement.Title); insertAnnouncementStmt.Bind(4, announcement.AuthorId); insertAnnouncementStmt.Bind(5, announcement.Id); if (insertAnnouncementStmt.Step() != SQLiteResult.DONE) { Debug.WriteLine("StoreAnnouncement: Failed to store announcement part of announcement with " + "id {0}.", announcement.Id); successful = false; } } // Commit der Transaktion. if (successful) { using (var statement = conn.Prepare("COMMIT TRANSACTION")) { statement.Step(); Debug.WriteLine("StoreAnnouncement: Announcement with id {0} stored.", announcement.Id); } } else { // Rollback der Transaktion. using (var statement = conn.Prepare("ROLLBACK TRANSACTION")) { statement.Step(); Debug.WriteLine("StoreAnnouncement: Announcement with id {0} could not be stored. " + "Rollback required.", announcement.Id); } } } catch (SQLiteException sqlEx) { Debug.WriteLine("SQLiteException has occurred in StoreAnnouncement. Exception message is: {0}.", sqlEx.Message); // Rollback der Transaktion. using (var statement = conn.Prepare("ROLLBACK TRANSACTION")) { statement.Step(); } throw new DatabaseException("Storing announcement data in database has failed."); } catch (Exception ex) { Debug.WriteLine("Exception has occurred in StoreAnnouncement. " + "Exception message is: {0}, and stack trace is {1}.", ex.Message, ex.StackTrace); // Rollback der Transaktion. using (var statement = conn.Prepare("ROLLBACK TRANSACTION")) { statement.Step(); } throw new DatabaseException("Storing announcement data in database has failed."); } finally { mutex.ReleaseMutex(); } } // Ende des using Block. } else { Debug.WriteLine("Couldn't get access to database. Time out."); throw new DatabaseException("Could not get access to the database."); } }
/// <summary> /// Führt den Befehl CreateNewAnnouncementCommand aus. Legt eine neue Announcement Nachricht an. /// Die Nachricht wird auf dem Server erzeugt und von diesem an alle Abonnenten verteilt. /// </summary> private async Task executeCreateNewAnnouncementCommand() { if (SelectedChannel != null) { displayIndeterminateProgressIndicator(); // Baue Announcement Objekt mit eingegebenen Daten. Priority messagePriority = Priority.NORMAL; if (IsMessagePriorityHighSelected) { messagePriority = Priority.HIGH; } Announcement newAnnouncement = new Announcement() { Text = AnnouncementContent, Title = AnnouncementTitle, MessagePriority = messagePriority }; try { // Starte Anlegen einer neuen Announcement. bool successful = await channelController.CreateAnnouncementAsync(SelectedChannel.Id, newAnnouncement); if (successful && _navService.CanGoBack()) { // Gehe zurück auf Detailseite. _navService.GoBack(); } } catch (ClientException ex) { displayError(ex.ErrorCode); } finally { hideIndeterminateProgressIndicator(); } } }
/// <summary> /// Erzeugt eine neue Nachricht für den Kanal mit der angegebenen Id. /// Die Announcement wird auf dem Server angelegt und dieser verteilt sie an /// alle Abonnenten. /// </summary> /// <param name="channelId">Die Id des Kanals, für den die Announcement angelegt werden soll.</param> /// <param name="newAnnouncement">Ein neues Announcement Objekt.</param> /// <returns>Liefert true, wenn die Announcement erfolgreich angelegt werden konnte, sonst false.</returns> /// <exception cref="ClientException">Wirft eine ClientException, wenn das Anlegen auf dem Server fehlgeschlagen ist.</exception> public async Task<bool> CreateAnnouncementAsync(int channelId, Announcement newAnnouncement) { if (newAnnouncement == null) return false; Moderator activeModerator = GetLocalModerator(); if (activeModerator == null) { Debug.WriteLine("Moderator not logged in."); return false; } // Führe Validierung der übergebenen Daten durch. clearValidationErrors(); newAnnouncement.ClearValidationErrors(); newAnnouncement.ValidateAll(); if (newAnnouncement.HasValidationErrors()) { // Melde Validierungsfehler und breche ab. reportValidationErrors(newAnnouncement.GetValidationErrors()); return false; } string jsonContent = jsonParser.ParseAnnouncementToJsonString(newAnnouncement); if (jsonContent == null) { Debug.WriteLine("CreateAnnouncementAsync failed, the announcement could " + " not be translated into a json document."); return false; } string serverResponse = null; try { serverResponse = await channelApi.SendCreateAnnouncementRequestAsync( activeModerator.ServerAccessToken, channelId, jsonContent); } catch (APIException ex) { if (ex.ErrorCode == ErrorCodes.ChannelNotFound) { Debug.WriteLine("Server says channel not found. Channel is probably deleted."); // Behandlung von ChannelNotFound. MarkChannelAsDeleted(channelId); } Debug.WriteLine("CreateAnnouncement failed. The message is: {0}.", ex.Message); // Bilde ab auf ClientException. throw new ClientException(ex.ErrorCode, ex.Message); } // Extrahiere Announcement aus ServerResponse. Announcement createdAnnouncement = jsonParser.ParseAnnouncementFromJsonString(serverResponse); if (createdAnnouncement != null) { // Speichere Announcement. await StoreReceivedAnnouncementAsync(createdAnnouncement); } return true; }
/// <summary> /// Speichere eine empfangen Announcement ab. Falls notwendig werden fehlende Informationene /// (z.B. über den Autor) vom Server geladen. /// </summary> /// <param name="announcement">Das Announcement Objekt der empfangenen Announcement</param> public async Task StoreReceivedAnnouncementAsync(Announcement announcement) { if(announcement == null) { return; } Debug.WriteLine("Trying to store the announcement with id {0} and messageNr {1}.", announcement.Id, announcement.MessageNumber); try { // Prüfe ob der Moderator in der Datenbank existiert, der als Autor der Nachricht eingetragen ist. if (moderatorDatabaseManager.IsModeratorStored(announcement.AuthorId)) { Debug.WriteLine("Starting to store announcement."); // Speichere die Announcement ab. channelDatabaseManager.StoreAnnouncement(announcement); } else { // Fehlerbehandlung. Debug.WriteLine("We do not have the author of the announcement in the database. Cannot store announcement without author reference."); Debug.WriteLine("The missing author has the id {0}.", announcement.AuthorId); Debug.WriteLine("Try querying the responsible moderators from the channel again."); try { List<Moderator> responsibleModerators = await GetResponsibleModeratorsAsync(announcement.ChannelId); if (responsibleModerators != null) { StoreResponsibleModeratorsForChannel(announcement.ChannelId, responsibleModerators); } // Prüfe erneut, ob Eintrag nun vorhanden ist. if (!moderatorDatabaseManager.IsModeratorStored(announcement.AuthorId)) { // Bilde auf Dummy-Moderator ab und füge die Announcement ein. Debug.WriteLine("Could not recover from missing author. Set author for announcement with id {0} to the " + "dummy moderator.", announcement.Id); announcement.AuthorId = 0; } // Speichere die Announcement ab. channelDatabaseManager.StoreAnnouncement(announcement); } catch (ClientException ex) { Debug.WriteLine("Request to retrieve missing moderators has failed. Error code is {0}.", ex.ErrorCode); // Bilde auf Dummy-Moderator ab und füge die Announcement ein. Debug.WriteLine("Could not recover from missing author. Set author for announcement with id {0} to the " + "dummy moderator.", announcement.Id); announcement.AuthorId = 0; // Speichere die Announcement ab. channelDatabaseManager.StoreAnnouncement(announcement); } catch (DatabaseException dbEx) { Debug.WriteLine("Exception occurred: " + dbEx.Message); //throw new ClientException(ErrorCodes.LocalDatabaseException, "Local database failure."); } } // Ende Fehlerbehandlung. } catch (DatabaseException ex) { // Fehler wird nicht weitergereicht, da es sich hierbei um eine Aktion handelt, // die normalerweise im Hintergrund ausgeführt wird und nicht aktiv durch den // Nutzer ausgelöst wird. Debug.WriteLine("Couldn't store the received announcement of channel. Message was {0}.", ex.Message); } }
/// <summary> /// Erstellt ein JSON Dokument aus einem Announcement Objekt. /// </summary> /// <param name="announcement">Das Objekt, das umgewandelt werden soll.</param> /// <returns>JSON-Dokument des Objekts, oder null, falls Serialisierung fehlgeschlagen ist.</returns> public string ParseAnnouncementToJsonString(Announcement announcement) { string jsonContent = null; try { jsonContent = JsonConvert.SerializeObject(announcement); } catch (JsonException jsonEx) { Debug.WriteLine("JsonParsingManager: Exception during serialization of an announcement object."); Debug.WriteLine("JsonParsingManager: Message is: {0}.", jsonEx.Message); } return jsonContent; }