diff options
Diffstat (limited to 'spike/settings/source/SpikeReport.mc')
| -rw-r--r-- | spike/settings/source/SpikeReport.mc | 144 |
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"); + } +} |
