Array über Programmdauer hinweg speichern

  • Hey,

    immer, wenn ich bisher in Programmen Werte speichern wollte, habe ich das via INI gemacht oder auch via Registry. Jetzt habe ich den Fall, dass ich auch ganz lange Strings abspeichern will und da kommen mir beide Lösungen nicht so gut vor?! Im Prinzip lese ich meine Mails aus und will den Inhalt auswerten (nach Absender, Datum, Titel, Inhalt, etc.) und die Ergebnisse speichern, plus den HTML-Code (ca. 20k Zeichen, sofern ich nicht noch zusätzlich cutte), um später ggf. noch andere Dinge auswerten zu können.

    Ich habe ein Array für die ganze Mailbox und jede Mail ist eine Map mit ein paar der genannten Eigenschaften.

    Auf die Daten muss ich nicht viel zugreifen, es kommt halt immer mal wieder eine Mail rein oder ich werte auch nochmal nachträglich eine Message aus und fülle eine weitere Eigenschaft, aber im Prinzip arbeite ich nur mit dem Array. Falls das Script abstürzt oder der PC mal neustartet, wollte ich die Speicherung parallel zur Veränderung des Arrays halten. An sich könnte ich neue Mails ja auch nachträglich, wenn das Programm wieder läuft, aus dem Postfach laden und ich "verliere" die Daten ja nicht wirklich. Aber es "fühlt" sich irgendwie falsch an, Daten, die ich schon geholt / ausgewertet habe, dann nicht zu behalten. ^^

    Da prüfen, ob ein Eintrag existiert oder existiert, aber noch weniger Informationen hat und entsprechend dann den Eintrag erstellen oder ergänzen mir in SQLite schwierig vorkommt (und ich mit AutoIt echt wenig an Beispielen / Tutorials finde), wollte ich dann die Datenbank bei jeder Anpassung des Arrays einfach löschen und neu erstellen, weil ich im Prinzip ja nur das Array sichern will (auch, wenn das irgendwann natürlich schon sehr groß wird).

    Hatte auch etwas Code, um das grundsätzlich auszuprobieren und mit SQLite "warm zu werden" (also kein schöner Code, ist zum Testen, bevor ich es verwende^^):

    Spoiler anzeigen


    Jetzt stehe ich vor ein paar Fragen:

    • Ist es für den Use Case überhaupt notwendig / sinnvoll, SQLite zu verwenden (v. a. nur wegen der Stringlänge vom HTML-Code)? Oder geht das doch "smarter"? ^^
    • Oder sollte ich sogar grundsätzlich, wenn davon auszugehen ist, dass es irgendwann 1000+ Mails sind, weg von einem Array und direkt auf der Datenbank arbeiten? (ich stelle den Inhalt des Arrays auch in einem Listview dar, wobei ich das vllt. aber später noch auf die 100 letzten oder so begrenze)
    • Kennt ihr Beispiele, mit denen ich mir für SQLite aneigenen kann? Vor allem halt das Thema => Ist ein Eintrag mit einer bestimmten ID da? Wenn ja, hat er auch z. B. schon "analyzed = True"? Und je nachdem, was im Array steht, wird er hinzugefügt, es passiert nichts oder es werden nur die Ergebnisse der Auswertung hinzugefügt (im ersten Zuge hole ich immer nur Status, Title, Datum und ID aus der Mailbox-Übersicht)


    Danke für euren Input! :)

    *edit*
    Ein Gedanke kam mir noch: Vielleicht auch wie gewohnt die ganzen Informationen einfach in die Registry und dann nur für das HTML eine eigene Datenbank... Dann wäre das Handling weiter einfach und die großen Strings liegen woanders. Es braucht dann ja auch keine Anpassungen in der Datenbank, es kommen nur neue Einträge dazu und den einfachen Check, ob schon da oder nicht, das sollte mit WHERE und der ID dann ja klappen... Dann könnte ich auch den ganzen HTML-Kram aus meinem Speicher / dem Array rauslassen und nur bei Bedarf holen *hmmm* ...

    3 Mal editiert, zuletzt von aSeCa (22. März 2024 um 14:01)

  • Ich weiß nicht ob dir das hilft, aber vor urzeiten (10 Jahre her) habe ich mal eine UDF geschrieben um Arrays auf die Platte zu schreiben (als einzelner String). Damals war der Map-Datentyp noch sehr experimentell, weshalb er nicht supported wird.

    Ich kann leider keine Garantie dafür geben, dass alles so funktioniert wie es soll, da sich in der langen Zeit wohl irgendwas in der AutoIt-Internen Handhabung von "Binary" geändert hat (musste da eben ein paar Sachen fixen, vermutlich sind aber immernoch irgendwelche Fehler drin).

    Was die UDF macht (bzw. machen soll):

    - Nimm ein beliebiges 1D oder 2D Array (auch verschachtelt) und mache "einen einzigen String" daraus. (Es gibt Optionen dafür um GDI+Bitmaps, oder "Dateien" nicht als Pointer/String, sondern als "Bitmap" oder "Datei" zu interpretieren. In dem Fall werden die Bitmapdaten (mit .png Kompression, also nicht als Rohdaten) bzw. die komplette Datei eingefügt. Damit lässt sich die UDF auch als Container für Bitmaps und Dateien verwenden)

    - Rekonstruiere dieses Array (incl aller Datentypen) wieder aus diesem String. Importierte Dateien werden NICHT automatisch irgendwo niedergeschrieben, sie sind als String im resultierenden Array. Importierte Bitmaps werden automatisch geladen und ein Pointer im Array hinterlegt.

    - Dieser String lässt sich auch auf der Platte speichern (als Datei) und zu jedem späteren Zeitpunkt wieder auslesen.

    Zur Sicherheit empfehle ich folgendes: Wandle dein Array in einen String um, speichere es als temporäre Datei. Lies diese Datei und mache wieder ein Array daraus. Vergleiche die Arrays vollständig. Nur wenn dieser Vergleich erfolgreich war ist das was auf der Platte liegt wirklich "das was du haben willst". Fall nicht -> Irgendwo ist ein bug den du mir gerne mitteilen kannst, dann schaue ich in der UDF und fixe das.

    lg

    M

  • Ich hab mal ein kleines Beispiel für eine Datenbankanwendung geschrieben, da sollte das wichtigste drin sein. Für weitere Details solltest du die Hilfe und die SQLite Dokumentation zurate ziehen. Es ist einfach zu umfangreich, alles unterzubringen.

    Dabei findest du auch die _generateUUID Funktion. Die hab ich dazugenommen, damit du deine Emails als Text-Datei mit der UUID als Namen speichern kannst und in der Datenbank dann nur diese UUID speichern musst.

    Das wäre auch meine Methode, große Mengen an Text (HTML/Email/...) zu speichern. Den Inhalt in eine Textdatei mit UUID als Namen speichern und in einer Datenbank, in der alle anderen Infos gespeichert werden, diesen Dateinamen/UUID mitzuspeichern.

    Auf diese Weise kannst du auch größere Mengen an Emails verarbeiten, da nur die groben Infos enthalten sind und den Inhalt, der ja ggf. recht groß sein kann, würde ich nur nachladen, wenn er wirklich benötigt wird.

  • Hi aSeCa 👋 ,

    für mich hört sich dein Anwendungsfall sehr nach Datenbankverwendung an. Wenn du schnell Daten filtern möchtest, wenn du schnell analysieren möchtest und dies über viel Text hinweg, ist SQL eine gute Variante. Allerdings kommt es hier mehr auf das richtige Anlegen des Schemas an, als auf die Verwendung mit SQLite. Die paar Funktionen und SELECTs am Ende, die du über SQLite machen wirst, bekommst du sicherlich gut hin, denke ich 🤞 .

    💡 Daher meine Empfehlung, beschäftige dich mit SQL und dann mit SQLite. Du findest in meiner Signatur auch ein kleines Beispiel "Umgang mit SQLite" für die Verwendung von SQLite. Ist zwar schon ziemlich alt und ich würde es wahrscheinlich jetzt etwas anders machen (denke ich), doch das Nötigste ist dabei*.

    ----------------------

    Falls dir dies zu aufwändig ist oder du nicht die Zeit dafür hast, würde ich als Alternative mit JSON Dateien arbeiten. Dort kannst du, anders als bei INI Dateien, auch mehrzeiligen Text einfach ablegen. Die tolle JSON.au3 UDF von AspirinJunkie hilft dir sicherlich beim Einstieg.

    💡 Falls dies irgendwann auch nicht mehr gut skaliert, weil du zu viele Emails gesammelt hast, dann kannst du auch mit mehreren JSON-Dateien arbeiten. ⚠ Allerdings kann du auch dann gleich auf eine NoSQL Datenbank wie bspw. "MongoDB", "CouchDB" oder "Cassandra" gehen, da wird das unter der Haube auch ähnlich gehandhabt und du musst dich um nichts weiter kümmern.

    ----------------------

    Fazit:
    => Ich würde an deiner Stelle mit SQL und SQLite gehen, falls du die Zeit dafür hast.
    => Falls nicht, dann JSON (ist ein verbreiteter Ansatz).

    Viele Grüße
    Sven

    * Update: Kanashius SQLite Beispiel ist auch prima.

  • Danke für eure Inputs :)
    Die UDF, um als einen String abzuspeichern, das hört sich echt gut an. ^^ Sowas hatte ich im ersten Schritt gesucht :) Das werde ich auf jeden Fall mal ausprobieren und einsetzen, ich bin mir nur noch nicht sicher, ob für diesen Fall.

    Mit JSON und ich glaube einer anderen JSON-UDF habe ich auch schon gearbeitet. Das wäre auf jeden Fall leichter zu handhaben, ja. Aber wenn Datenbank "richtig" wäre, versuche ich mich damit ^^ Ich versuche gerne den richtigen Weg zu gehen und dabei dann noch was zu DBs zu lernen ist ja auch praktisch ^^

    Ich schaue mir auf jeden Fall schon mal eure Funktionen zum Umgang mit SQLite an. Sieht auf jeden Fall schon sehr nützlich aus.


    Kanashius, du würdest also jede Mail als eigene .txt ablegen? Da kommen dann schon einige zusammen, aber ist wahrscheinlich eine alles Ressourcen-schonend zu machen... Ist es denn sinnvoll, eine UUID zu generieren, wenn die Mails schon eine eindeutige ID haben, die ich auch ablege? Ich würde die einfach nutzen, oder? Und das "AUTOINCREMENT" beim Erstellen der Tabelle dann rausnehmen.


    Ich habe jetzt mal, bis auf das Abspeichern einer Datei je Message (wenn noch nicht vorhanden), den Teil mit der Datenbank mit eurem Input versucht:

    Spoiler anzeigen


    Das gefällt mir vom Handling schon ganz gut... Müsste dann noch eine Funktion bauen, um z. B. den Sender nachträglich eintragen zu können, wenn die Nachricht geöffnet und geparst wurde. Und dann das mit den Files halt.

    Hätte bislang 2 neue Fragen:

    • Muss ich das mit dem BOOLEAN so handhaben? Also im Prinzip kriege ich via AutoIt nur alles als String rein/raus und muss dann selber umwandeln? Dann könnte ich statt BOOL auch gleich TEXT nehmen, oder hat das einen Vorteil?!
    • Wenn ich das Script 2x starte und dazwischen nicht die DB lösche, kriege ich beim Eintragen natürlich einen Fehler in der Konsole: "UNIQUE constraint failed: messages.id"; das ist grundsätzlich auch gut, aber ist das das richtige Vorgehen oder sollte man irgendwie erstmal prüfen, ob schon ein Eintrag da ist? Das knallt mir dann später ja auch heftig die Konsole voll...


    Und gibt es grundsätzlich Feedback zu meinem Handling? Habe ja viel übernommen, aber auch Sinnhaftigkeit vom Enum, Erweiterung der $mDatabase, die _addMessageToDatabase(), usw...


    Danke euch! :)

  • Ich würde sagen, das schaut schon gut aus.

    Du könntest überlegen, das Hinzufügen von Nachrichten mit der $mMessage Map selber zu machen => ByRef sorgt dafür, dass die Map nicht kopiert wird, sondern als "Referenz" übergeben wird und ist damit resourcenschonender. Spart das viele schreiben der Parameter und ändern, falls du was hinzufügen willst.

    AutoIt
    _addMessageToDatabase($mMessage)
    
    Func _addMessageToDatabase(ByRef $mMessage)
    	; ...
    	$sQuery &= "'" & $mMessage.id & "'" & ...
    	; ...
    EndFunc

    Muss ich das mit dem BOOLEAN so handhaben? => AutoIt wandelt Dateitypen automatisch um, SQL aber nicht. Es macht also Sinn, den passenden Datentypen zu wählen. Hauptgründe:
    1. Boolean ist 1 bit, der String "False" sind 5 byte (40bit oder noch mehr, je nach encodierung des textes (Standard wäre UTF-8 glaub ich, also 8*4*5 => 160bits)). In deinem Anwendungsfall ggf. nicht so wichtig, je nach Menge der Datensätze, ist aber schon das 160 fache an Datenverbrauch.
    2. Du kannst die Datentypen einfacher in deinen Querys verwenden. Zum Beispiel "SELECT * FROM contacts WHERE analyzed", was deutlich schneller geht als der String-Vergleich: "SELECT * FROM contacts WHERE analyzed='true' or analyzed='True'",... und man hat auch keine Groß- und Kleinschreibung zu beachten.
    Das gilt für eigentlich alle Datentypen. Ich würde immer den richtigen verwenden.

    Wenn ich das Script 2x starte und dazwischen nicht die DB lösche,... => Naja, das ist ja ein Fall der normalerweise (außerhalb vom testen) niemals auftreten sollte, da die ID ja einzigartig ist und nicht mehrmals auftreten sollte. Generell könntest du sogar überlegen, ob du dein Array überhaupt noch brauchst, oder ob du nicht einfach SQL-Queries schicken willst um die Daten abzufragen. SQL erlaubt das selektieren mit Bedingungen und vielem mehr.
    Wenn du Fehlermeldungen beim einfügen vermeiden möchtest kannst du auch "INSERT OR IGNORE INTO bookmarks(users_id, lessoninfo_id) VALUES(123, 456)" verwenden. Dabei wird geschaut, ob das als UNIQUE makierte Feld bereits vorhanden ist und es wird nur eingefügt, wenn es nicht bereits existiert.
    Wenn du Daten ändern möchtest (z.B den Sender später hinzuzufügen) nimmt man bei SQL das UPDATE statement. Z.B.: "UPDATE contacts SET sender = 'someone' WHERE id='1234'"

  • Ohja, klar :D kA, warum ich die einzeln übergeben habe ^^ Danke

    Die Erklärung zum BOOL auch sehr aufschlussreich! :)


    Zu dem 2x starten: Also ich lese die Mails ja jedes Mal wieder ein, wenn ich das Postfach öffne. Und dann muss ich ja feststellen, welche davon neu sind?! Also wird das doch permanent passieren?!

    Ganz auf das Array zu verzichten ist auch eine Idee... Ich will halt einige von den Mails in einer ListView anzeigen lassen (letzte 100 oder so). Ich dachte mir, dass es schneller ist, wenn ich das direkt parat habe und nicht erst abrufen muss. Aber hat natürlich auch echt was für sich, die Daten nicht doppelt halten zu müssen und dann direkt die DB zu fragen.

    Aber trotzdem wird die DB ja gefüttert, indem ich das Postfach parse (immer die letzten 100 Nachrichten von der ersten Seite) und dann habe ich da ein Array mit ID, Titel und Datum und werde abgleichen müssen, welche Nachrichten neu sind. Mit jetzigem Wissen würde ich versuchen, die 100 Nachrichten einzutragen und bei 99 schlägt es dann fehl.

    Oder ich merke mir, was die letzte Nachricht war, bzw. frage mir immer die Nachricht mit dem neusten Darum ab und gucke dann, ob es neuere Nachrichten gibt... Wäre vielleicht smarter, hmm! :)


    Danke wegen der Hinweise zu IGNORE und UPDATE, schaue ich mir an! :)

  • Boolean ist 1 bit, der String "False" sind 5 byte (40bit oder noch mehr, je nach encodierung des textes (Standard wäre UTF-8 glaub ich, also 8*4*5 => 160bits)). In deinem Anwendungsfall ggf. nicht so wichtig, je nach Menge der Datensätze, ist aber schon das 160 fache an Datenverbrauch.

    Auch wenn ich der Grundaussage "Nimm die richtigen Datentypen" sehr zustimme will ich die massiven Werte hier vielleicht doch mal ein wenig runterholen.
    Boolean in Sqlite verbraucht nicht 1 Bit sondern ist intern ein Integer. Diese werden je nach Wert in 1 bis 8 Byte gespeichert. Auch wenn ich es nicht genau weiß, gehe ich bei boolean in Sqlite daher von 1 Byte aus.
    Übrigens: Wenn man in Sqlite ein Boolean-Attribut hat und diesem den Wert "True" oder "False" zuweist, dann macht der auch brav 1 oder 0 daraus und meckert nicht weil es ja kein String ist.

    Zum Thema UTF-8: Der Witz an UTF-8 ist ja eben, dass diese Kodierung keine feste Länge hat sondern zwischen 1 und 4 Byte kodiert.
    Die Zeichen, welche man für "False" benötigt, befinden sich alle im ASCII-Bereich. Und dieser Bereich braucht in UTF-8 auch nur jeweils 1 Byte pro Zeichen.
    Es sind also 5 Byte. Nimmt man noch ein Byte dazu für das Stringende als Null-Char dann wären es 6 bzw. 5 bei "True".

    Sprich: Wir hätten also eher einen 5 bis 6-fachen Datenverbrauch zu erwarten.
    Ist viel ohne Frage aber klingt schon ganz anders als 160x.

  • Ich habe jetzt den Code in ein Script eingebaut und ein paar Dinge sind dabei aufgefallen. Davon hänge ich grade an einem Punkt und wollte mal nach einem Best Practice dazu fragen:

    Die Reihenfolge des Arrays geht verloren, wenn ich es in der DB zwischenspeichere. Da ich einen Teil der Nachrichten in eine ListView packe, brauche ich die richtige Reihenfolge. Diese ist ja dem Datum entsprechend, weil in der Mailbox die neuen Nachrichten auch oben sind.

    Ich dachte erst daran, die ListView zu sortieren. Die normale Sortierfunktion ist wohl nicht ausreichend und "GUICtrlRegisterListViewSort" sieht unnötig kompliziert dafür aus.

    Darum wollte ich dann direkt das Array sortieren. ArraySort() alleine reicht auch nicht aus, weil er nicht richtig das Datum auswerten kann.

    Was wäre jetzt der einfachste Weg, die Sortierung wieder zu bekommen?

    • Doch mit laufender ID in der Datenbank arbeiten und dann absteigend der ID nach sortieren?
    • An eigene Sortierfunktion für das Array machen?
    • UDF suchen, die Daten sortieren kann und dann Datum für Input und nach Output umwandeln?
    • ...?


    Ich tendiere dazu, die laufende ID zu nehmen, aber so richtig "sauber" fühlt sich das nicht an. Habe irgendwie Sorge, dass es da "Nebenwirkungen" gibt, die ich grade nicht auf dem Schirm habe...^^


    Hier nochmal ein Mini-Datensatz mit meinem verwendeten Datums-Format:

    Code
    #include <Array.au3>
    
    Dim $aArray[3][3] = [["ABC", "", "23.03. 13:37"], ["BCD", "", "23.03. 09:41"], ["CDE", "", "15.04. 15:15"]]
    
    _ArrayDisplay($aArray)
    _ArraySort($aArray, 0, 0, 0, 2) ; reicht nicht aus
    _ArrayDisplay($aArray)
  • Hier nochmal ein Mini-Datensatz mit meinem verwendeten Datums-Format :

    Kurze Frage zum Verständnis :

    Speicherst Du [Datum Uhrzeit] wirklich im Format "TT.MM. hh:mm" , d.h. ohne Jahr und Sekunden ?

    86598-musashi-c64-png

    "Am Anfang wurde das Universum erschaffen. Das machte viele Leute sehr wütend und wurde allenthalben als Schritt in die falsche Richtung angesehen."

  • Kurze Frage zum Verständnis :

    Speicherst Du [Datum Uhrzeit] wirklich im Format "TT.MM. hh:mm" , d.h. ohne Jahr und Sekunden ?

    Ich bekomme es anders, aber ich lege es auch so ab, ja. Theoretisch könnte ich die Daten natürlich sauberer abspeichern (auch für einen sauberen Datums-Datentyp in der DB... hmmm) und dann nur für die Anzeige so bearbeiten...^^ Ich benutze die wunderschöne UDF "_Date_Time_Convert" :D


    Vllt. geht die Frage in sogar in die Richtung? Kann ich, wenn ich das als richtiges Datum in die DB lege, via Query danach sortieren? Daran hatte ich noch gar nicht gedacht... Wenn das geht, wäre das sicherlich die smarteste Variante... :)!


    *edit*

    Laut Doku gibt es wohl keinen Date-Datentypen, schade...^^ Aber gibt date- und time-Funktionen, wenn man es richtig ablegt, also als "YYYY-MM-DD HH:MM:SS.SSS". Aber wenn ich mir das Format so anschaue (vllt. ohne die Millisekunden), dann sollte auf dem Format auch eine normale Sortierfunktion das richtig hinbekommen... Das wäre auch noch eine Variante. ^^

    Einmal editiert, zuletzt von aSeCa (23. März 2024 um 18:41)

  • Kann ich, wenn ich das als richtiges Datum in die DB lege, via Query danach sortieren?

    Ja !

    Alternativ könntest Du aus dem "echten" Datum einen (Unix)-Timestamp machen und den als zusätzliches Feld in die Tabelle eintragen (nur für die SQL Abfrage) -> SELECT ... ORDER BY timestamp.

    Sofern Du die DB-Tabelle jedesmal neu anlegst und die Daten in der vorhandenen Sortierung in die DB einträgst, dann sollte eigentlich bereits die ID (SELECT x,y FROM tabelle ORDER BY id) ausreichen.

    86598-musashi-c64-png

    "Am Anfang wurde das Universum erschaffen. Das machte viele Leute sehr wütend und wurde allenthalben als Schritt in die falsche Richtung angesehen."

    Einmal editiert, zuletzt von Musashi (23. März 2024 um 18:48) aus folgendem Grund: Tippfehler : nicht SORT BY, sondern natürlich ORDER BY

  • Ja !

    Alternativ könntest Du aus dem "echten" Datum einen (Unix)-Timestamp machen und den als zusätzliches Feld in die Tabelle eintragen (nur für die SQL Abfrage) -> SELECT ... ORDER BY timestamp.

    Sofern Du die DB-Tabelle jedesmal neu anlegst und die Daten in der vorhandenen Sortierung in die DB einträgst, dann sollte eigentlich bereits die ID (SELECT x,y FROM tabelle ORDER BY id) ausreichen.

    Ich habe eine eigene ID, also keine hochlaufende. Über die Zeit fühlt sich schon besser an :)

    Unix-Timestamp ist dann im DB-Browser nicht so gut nachzuvollziehen, ich schaue mal, ob das Sortieren auch mit dem Datum als TEXT nach ISO8601 formatiert gut geht. Und sonst hast du mich halt auch auf die Idee gebracht, dass ich es auch mit einer Andersformatierung einfach so lösen kann, danke! :)
    =>

    Code
    #include <Array.au3>
    
    ;~ Dim $aArray[3][3] = [["ABC", "", "23.03. 13:37"], ["BCD", "", "23.03. 09:41"], ["CDE", "", "15.04. 15:15"]]
    Dim $aArray[6][3] = [["ABC", "", "2024-03-23 13:37:25"], ["BCD", "", "2024-03-23 09:41:15"], ["CDE", "", "2024-04-15 15:15:15"], _
    					["DEF", "", "2024-04-15 04:15:55"], ["EFG", "", "2023-12-31 15:15:15"], ["FGH", "", "2024-07-01 05:25:15"]]
    
    _ArrayDisplay($aArray)
    _ArraySort($aArray, 1, 0, 0, 2) ; reicht aus :D
    _ArrayDisplay($aArray)


    *edit*
    Aber in dem Format hat es wirklich gereicht, dann einfach meinen Call in _readMessageDatabase so anzupassen, super! :)
    _getData("SELECT * FROM " & $mDatabase.tableName & " ORDER BY date DESC")

    => läuft auch eingesetzt in mein Programm genau richtig - danke dir! :)

    2 Mal editiert, zuletzt von aSeCa (23. März 2024 um 19:11)

  • => läuft auch eingesetzt in mein Programm genau richtig - danke dir! :)

    :thumbup::)

    86598-musashi-c64-png

    "Am Anfang wurde das Universum erschaffen. Das machte viele Leute sehr wütend und wurde allenthalben als Schritt in die falsche Richtung angesehen."