mesh/merge-gate pass: builds new: modules/betterbird, sent nowhere; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request
A restart ended Betterbird with whatever a composer held; now it refuses while a Msgcompose window is open unless discard_unsent says the operator agreed, and answers within the runtime's 30 s. A preference's value could carry an address, a user%40domain@host URI, a host or the home; those are masked, recent-folder, account and cloud-file preferences and every add-on preference but the theme's id are withheld, and a request for everything is refused. A log kept the token after Bearer or Basic. The reminder test's Betterbird counted as the operator's and left its profile behind when it ended on its own; it is now left out everywhere and its unit removes the profile when it stops.
153 lines
6.5 KiB
Go
153 lines
6.5 KiB
Go
package main
|
|
|
|
// A reminder on demand, without the operator's calendar: a second Betterbird, in a profile of its own
|
|
// under the account's runtime directory, with one local calendar file holding one event whose reminder
|
|
// is due a few seconds after the start. The running Betterbird, its profile, its accounts and its
|
|
// calendars are not touched: the second one starts with --no-remote, so it neither hands over to the
|
|
// first nor is handed to.
|
|
//
|
|
// The test profile gets the module's user.js too, so the reminder is drawn with the preferences the
|
|
// module declares. A theme or another preference of the operator's profile can be carried over by
|
|
// name (carry), within the same limits as betterbird_prefs: never a credential, never an account.
|
|
//
|
|
// It lives for at most ten minutes (the unit's RuntimeMaxSec), and stop ends it early and removes the
|
|
// profile. The reminder window is then found with xorg_windows (class Calendar, role AlarmWindow) and
|
|
// looked at with xorg_screenshot.
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// testDir is the test's profile and calendar, under the account's runtime directory.
|
|
func (m *Machine) testDir() string {
|
|
return m.path(fmt.Sprintf("/run/user/%d/%s", m.UID, testUnit))
|
|
}
|
|
|
|
// carryName is a preference name the test may carry over from the operator's profile.
|
|
var carryName = regexp.MustCompile(`^[A-Za-z0-9_.\-]+$`)
|
|
|
|
// ReminderAnswer is what betterbird_reminder_test answers.
|
|
type ReminderAnswer struct {
|
|
Unit string `json:"unit"`
|
|
Profile string `json:"profile"`
|
|
Event string `json:"event"`
|
|
Due string `json:"reminder_due"`
|
|
Carried []string `json:"carried,omitempty"`
|
|
Withheld []string `json:"withheld,omitempty"`
|
|
Look string `json:"look"`
|
|
Stopped bool `json:"stopped,omitempty"`
|
|
}
|
|
|
|
// ics is the test calendar: one event, its reminder `after` from now.
|
|
func ics(now time.Time, after time.Duration, stamp string) string {
|
|
f := func(t time.Time) string { return t.UTC().Format("20060102T150405Z") }
|
|
start := now.Add(after + 10*time.Minute)
|
|
return strings.Join([]string{"BEGIN:VCALENDAR", "VERSION:2.0", "PRODID:-//novox mesh//betterbird reminder test//EN",
|
|
"BEGIN:VEVENT", "UID:mesh-reminder-test-" + stamp + "@mesh", "DTSTAMP:" + f(now), "DTSTART:" + f(start),
|
|
"DTEND:" + f(start.Add(30*time.Minute)), "SUMMARY:Mesh reminder test", "BEGIN:VALARM", "ACTION:DISPLAY",
|
|
"DESCRIPTION:Mesh reminder test", "TRIGGER:-PT10M", "END:VALARM", "END:VEVENT", "END:VCALENDAR", ""}, "\r\n")
|
|
}
|
|
|
|
// testPrefs are the test profile's own preferences: its one calendar, and no first-run questions.
|
|
func testPrefs(calendar string) string {
|
|
return strings.Join([]string{
|
|
`user_pref("calendar.registry.meshtest.type", "ics");`,
|
|
`user_pref("calendar.registry.meshtest.uri", "file://` + calendar + `");`,
|
|
`user_pref("calendar.registry.meshtest.name", "Mesh reminder test");`,
|
|
`user_pref("calendar.registry.meshtest.calendar-main-in-composite", true);`,
|
|
`user_pref("calendar.registry.meshtest.suppressAlarms", false);`,
|
|
`user_pref("calendar.list.sortOrder", "meshtest");`,
|
|
`user_pref("mail.provider.suppress_dialog_on_startup", true);`,
|
|
`user_pref("mail.shell.checkDefaultClient", false);`,
|
|
`user_pref("datareporting.policy.dataSubmissionEnabled", false);`,
|
|
`user_pref("toolkit.telemetry.reportingpolicy.firstRun", false);`,
|
|
`user_pref("app.update.auto", false);`,
|
|
`user_pref("mail.rights.version", 1);`,
|
|
"",
|
|
}, "\n")
|
|
}
|
|
|
|
// ReminderTest starts the test Betterbird, or with stop ends it and removes its profile.
|
|
func (m *Machine) ReminderTest(after time.Duration, carry []string, stop bool) (ReminderAnswer, error) {
|
|
dir := m.testDir()
|
|
a := ReminderAnswer{Unit: testUnit + ".service", Profile: strings.TrimPrefix(filepath.Join(dir, "profile"), m.Root),
|
|
Look: "xorg_windows with class Calendar answers the reminder window (role AlarmWindow, its depth and map state); " +
|
|
"xorg_screenshot with that window shows it as the screen does"}
|
|
if stop {
|
|
_ = m.cmd(5*time.Second, m.bus(), "systemctl", "--user", "stop", testUnit+".service")
|
|
if err := os.RemoveAll(dir); err != nil {
|
|
return a, err
|
|
}
|
|
a.Stopped, a.Look = true, ""
|
|
return a, nil
|
|
}
|
|
s, err := m.session()
|
|
if err != nil {
|
|
return a, err
|
|
}
|
|
var carried []string
|
|
if len(carry) > 0 {
|
|
p, err := m.profile()
|
|
if err != nil {
|
|
return a, err
|
|
}
|
|
prefsJS, _ := readBounded(filepath.Join(p.path, "prefs.js"))
|
|
have := readPrefs(prefsJS)
|
|
for _, n := range carry {
|
|
switch {
|
|
case !carryName.MatchString(n):
|
|
return a, fmt.Errorf("carry holds preference names, not %q", n)
|
|
case withheld(n) != "":
|
|
a.Withheld = append(a.Withheld, n+": "+withheld(n))
|
|
case have[n] == "":
|
|
a.Withheld = append(a.Withheld, n+": not set in the operator's profile")
|
|
default:
|
|
carried = append(carried, fmt.Sprintf("user_pref(%s, %s);", strconv.Quote(n), have[n]))
|
|
a.Carried = append(a.Carried, n)
|
|
}
|
|
}
|
|
}
|
|
_ = m.cmd(5*time.Second, s.Env(), "systemctl", "--user", "stop", testUnit+".service")
|
|
if err := os.RemoveAll(dir); err != nil {
|
|
return a, err
|
|
}
|
|
if err := os.MkdirAll(filepath.Join(dir, "profile"), 0o700); err != nil {
|
|
return a, err
|
|
}
|
|
now := m.Now()
|
|
calendar := filepath.Join(dir, "test.ics")
|
|
if err := os.WriteFile(calendar, []byte(ics(now, after, strconv.FormatInt(now.Unix(), 10))), 0o600); err != nil {
|
|
return a, err
|
|
}
|
|
user := testPrefs(strings.TrimPrefix(calendar, m.Root))
|
|
if mine, err := readBounded(m.home(declared)); err == nil && validUserJS(string(mine)) == nil {
|
|
user = string(mine) + "\n" + user
|
|
}
|
|
user += strings.Join(carried, "\n") + "\n"
|
|
if err := os.WriteFile(filepath.Join(dir, "profile", "user.js"), []byte(user), 0o600); err != nil {
|
|
return a, err
|
|
}
|
|
// The unit removes the profile when it ends, however it ends: at its 10 minutes, on stop, or when the
|
|
// test Betterbird is closed.
|
|
call := []string{"--user", "--collect", "--quiet", "--unit=" + testUnit, "-p", "RuntimeMaxSec=600",
|
|
"-p", "ExecStopPost=/usr/bin/rm -rf " + strings.TrimPrefix(dir, m.Root), "--setenv=MOZ_NO_REMOTE=1"}
|
|
for _, kv := range [][2]string{{"DISPLAY", s.Display}, {"XAUTHORITY", s.XAuthority}} {
|
|
if kv[1] != "" {
|
|
call = append(call, "--setenv="+kv[0]+"="+kv[1])
|
|
}
|
|
}
|
|
call = append(call, "--", startBin, "--no-remote", "--profile", strings.TrimPrefix(filepath.Join(dir, "profile"), m.Root))
|
|
if err := failed(m.cmd(8*time.Second, s.Env(), "systemd-run", call...), "systemd-run", call...); err != nil {
|
|
return a, err
|
|
}
|
|
a.Event = "Mesh reminder test, in a calendar file of the test profile only"
|
|
a.Due = now.Add(after).UTC().Format(time.RFC3339)
|
|
return a, nil
|
|
}
|