aboutsummaryrefslogtreecommitdiff
path: root/spike/settings/source/SpikeReport.mc
diff options
context:
space:
mode:
Diffstat (limited to 'spike/settings/source/SpikeReport.mc')
-rw-r--r--spike/settings/source/SpikeReport.mc144
1 files changed, 144 insertions, 0 deletions
diff --git a/spike/settings/source/SpikeReport.mc b/spike/settings/source/SpikeReport.mc
new file mode 100644
index 0000000..09f27fa
--- /dev/null
+++ b/spike/settings/source/SpikeReport.mc
@@ -0,0 +1,144 @@
+// Author: cirakg & claude ai
+// Summary: Reads every spike setting, measures lengths and characters, writes the status back, builds the screen lines.
+//
+// Per text field it reports: length in characters and UTF-8 bytes, the first
+// 12 and last 8 characters (the test strings start with "N=<length>|" and
+// end with "END", so truncation is obvious), and which of the characters
+// & < > \ " { } % and non-ASCII it contains.
+//
+// Write-back test: on every settings change the watch writes a status line
+// into the read-only "statusField" and clears "secretField". Opening the
+// settings on the phone afterwards shows whether the phone displays values
+// the watch wrote.
+
+import Toybox.Application;
+import Toybox.Application.Properties;
+import Toybox.Lang;
+import Toybox.System;
+
+module SpikeKeys {
+ const TEXT_FIELDS = ["text1", "text2", "text3", "text4", "text5", "text6", "text7", "text8"];
+ const SECRET = "secretField";
+ const STATUS = "statusField";
+ const OTHER_FIELDS = ["switchField", "listField", "numberField", "longField"];
+ const HEAD_CHARS = 12;
+ const TAIL_CHARS = 8;
+ const ASCII_BACKSLASH = 92;
+ const ASCII_QUOTE = 34;
+}
+
+class SpikeReport {
+
+ // Lines for SpikeView, rebuilt by analyse().
+ var lines as Array<String> = [] as Array<String>;
+ // onSettingsChanged calls since the app started, and the time of the last.
+ var changeCount as Number = 0;
+ var lastChange as String = "never";
+ // Totals over the eight text fields, from the latest analysis.
+ var totalChars as Number = 0;
+ var totalBytes as Number = 0;
+ // Length of the password field when it was last read (before clearing).
+ var passwordReceived as Number = 0;
+ // The status line the watch wrote last ("" before the first change).
+ var lastWriteBack as String = "";
+
+ function initialize() {
+ }
+
+ // On start: no write-back, just show what the last sync delivered.
+ function analyseOnStart() as Void {
+ passwordReceived = readString(SpikeKeys.SECRET).length();
+ analyse();
+ }
+
+ // Settings were saved on the phone: measure, write back, show.
+ function settingsChanged() as Void {
+ changeCount++;
+ lastChange = clockText();
+ passwordReceived = readString(SpikeKeys.SECRET).length();
+ analyse();
+ lastWriteBack = "seen " + totalChars + " chars at " + lastChange + ", change #" + changeCount;
+ Properties.setValue(SpikeKeys.STATUS, lastWriteBack);
+ Properties.setValue(SpikeKeys.SECRET, "");
+ analyse();
+ }
+
+ // Rebuild every screen line from the current property values.
+ function analyse() as Void {
+ lines = [] as Array<String>;
+ totalChars = 0;
+ totalBytes = 0;
+ var fieldLines = [] as Array<String>;
+ for (var i = 0; i < SpikeKeys.TEXT_FIELDS.size(); i++) {
+ describeText(fieldLines, "T" + (i + 1), readString(SpikeKeys.TEXT_FIELDS[i]));
+ }
+ lines.add("Settings spike");
+ lines.add("Changes seen: " + changeCount);
+ lines.add("Last change: " + lastChange);
+ lines.add("Total: " + totalChars + " chars, " + totalBytes + " B");
+ lines.addAll(fieldLines);
+ lines.add("Password: got " + passwordReceived + " chars,");
+ lines.add(" now " + readString(SpikeKeys.SECRET).length() + " (watch clears it)");
+ for (var i = 0; i < SpikeKeys.OTHER_FIELDS.size(); i++) {
+ lines.add(SpikeKeys.OTHER_FIELDS[i] + ": " + valueText(SpikeKeys.OTHER_FIELDS[i]));
+ }
+ lines.add("Status now: " + readString(SpikeKeys.STATUS));
+ lines.add("Wrote: " + (lastWriteBack.equals("") ? "nothing yet" : lastWriteBack));
+ }
+
+ // Length, ends and special characters of one text field.
+ private function describeText(out as Array<String>, label as String, value as String) as Void {
+ var chars = value.length();
+ var bytes = value.toUtf8Array().size();
+ totalChars += chars;
+ totalBytes += bytes;
+ out.add(label + ": " + chars + " chars, " + bytes + " B");
+ if (chars == 0) {
+ return;
+ }
+ var head = part(value, 0, chars < SpikeKeys.HEAD_CHARS ? chars : SpikeKeys.HEAD_CHARS);
+ var tailStart = chars > SpikeKeys.TAIL_CHARS ? chars - SpikeKeys.TAIL_CHARS : 0;
+ out.add(" " + head + " .. " + part(value, tailStart, chars));
+ out.add(" has: " + specialsIn(value, chars != bytes));
+ }
+
+ // Which of the characters the plan worries about occur in the value.
+ private function specialsIn(value as String, nonAscii as Boolean) as String {
+ var probes = [
+ "&", "<", ">", SpikeKeys.ASCII_BACKSLASH.toChar().toString(),
+ SpikeKeys.ASCII_QUOTE.toChar().toString(), "{", "}", "%"
+ ];
+ var found = "";
+ for (var i = 0; i < probes.size(); i++) {
+ if (value.find(probes[i]) != null) {
+ found += probes[i] + " ";
+ }
+ }
+ if (nonAscii) {
+ found += "non-ASCII";
+ }
+ return found.equals("") ? "none" : found;
+ }
+
+ // A property as a String; anything missing or of another type reads as "".
+ private function readString(key as String) as String {
+ var value = Properties.getValue(key);
+ return value instanceof String ? value : "";
+ }
+
+ // Any property as display text.
+ private function valueText(key as String) as String {
+ // getValue throws for unknown keys and never returns null for declared ones.
+ return Properties.getValue(key).toString() as String;
+ }
+
+ private function part(value as String, startIndex as Number, endIndex as Number) as String {
+ var result = value.substring(startIndex, endIndex);
+ return result == null ? "" : result;
+ }
+
+ private function clockText() as String {
+ var time = System.getClockTime();
+ return time.hour.format("%02d") + ":" + time.min.format("%02d") + ":" + time.sec.format("%02d");
+ }
+}