Age | Commit message (Collapse) | Author |
|
Adds an archive command that moves the current message into the folder
specified in the account config entry.
Supports three layouts at this point:
- flat: puts all messages next to each other
- year: creates a folder per year
- month: same as above, plus folders per month
This also adds a "-p" argument to "cp" and "mv" that works like
"--parents" on mkdir(1). We use this to auto-create the directories
for the archive layout.
|
|
|
|
|
|
|
|
Fixes #77: When closing a tab, bring you back to the one you last had focused
|
|
|
|
|
|
|
|
|
|
|
|
This makes it so an atomic `invalid` value is used instead of an unbuffered
channel. When many invalidations kick in, a lot of values were sent to the
channel.
(Since OnInvalidate's callback can be run in any goroutine, we need to be
careful about races here.)
|
|
This commit introduces a new Aerc.Tick function that should be called to
refresh the internal state. This in turn makes each AccountView process worker
events.
The UI goroutine repeatedly refreshes the internal state before drawing a new
frame. The reason for this is that many worker messages may need to be
processed for a single frame, and drawing the UI is far slower than refreshing
the internal state. This has been confirmed in my testing (calling Aerc.Tick
only once per frame results in a slower display).
Many synchronization code has been removed. We can now write widgets without
having to care so much about races. The remaining sync users are:
- widgets/spinner: the spinner value is updated from inside an internal
goroutine
- lib/ui/invalidatable: Invalidate may be called from any goroutine
- lib/ui/grid: same
- lib/ui/ui: an internal goroutine needs read access to UI.exit
- worker/types/worker: Worker.callbacks is used for both worker and UI
callbacks
The exact goroutine requirements for Drawable have been documented.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Also moves truncation to the tab widget
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
UI.Exit can be accessed from goroutines drawing, goroutines executing
commands and goroutines waiting for events.
Write at 0x00c0002b2040 by main goroutine:
main.main.func1()
/home/simon/src/aerc2/aerc.go:76 +0x33d
git.sr.ht/~sircmpwn/aerc2/widgets.(*Aerc).BeginExCommand.func1()
/home/simon/src/aerc2/widgets/aerc.go:245 +0x89
git.sr.ht/~sircmpwn/aerc2/widgets.(*ExLine).Event()
/home/simon/src/aerc2/widgets/exline.go:131 +0x442
git.sr.ht/~sircmpwn/aerc2/widgets.(*Aerc).Event()
/home/simon/src/aerc2/widgets/aerc.go:116 +0x83c
git.sr.ht/~sircmpwn/aerc2/widgets.(*Aerc).simulate()
/home/simon/src/aerc2/widgets/aerc.go:109 +0x12a
git.sr.ht/~sircmpwn/aerc2/widgets.(*Aerc).Event()
/home/simon/src/aerc2/widgets/aerc.go:142 +0x722
git.sr.ht/~sircmpwn/aerc2/lib/ui.(*UI).Tick()
/home/simon/src/aerc2/lib/ui/ui.go:75 +0x33f
main.main()
/home/simon/src/aerc2/aerc.go:94 +0x497
Previous read at 0x00c0002b2040 by goroutine 19:
git.sr.ht/~sircmpwn/aerc2/lib/ui.Initialize.func1()
/home/simon/src/aerc2/lib/ui/ui.go:45 +0x97
Goroutine 19 (running) created at:
git.sr.ht/~sircmpwn/aerc2/lib/ui.Initialize()
/home/simon/src/aerc2/lib/ui/ui.go:44 +0x372
main.main()
/home/simon/src/aerc2/aerc.go:87 +0x3a9
|
|
This was is more complicated than others. The cells list is accessed by
multiple goroutines:
- Some change the Grid's contents via AddChild/RemoveChild
- Some call Draw
- Some invalidate the grid via Invalidate
Invalidate calls are tricky to handle because they will also invalidate all
child cells. This will inturn trigger the cellInvalidated callback, which needs
to read the list of cells.
For this reason, we use a sync.RWLock which allows multiple concurrent reads.
Below is the race fixed by this commit.
Read at 0x00c0000bc3d0 by goroutine 7:
git.sr.ht/~sircmpwn/aerc2/lib/ui.(*Grid).cellInvalidated()
/home/simon/src/aerc2/lib/ui/grid.go:181 +0x45
git.sr.ht/~sircmpwn/aerc2/lib/ui.(*Grid).cellInvalidated-fm()
/home/simon/src/aerc2/lib/ui/grid.go:179 +0x55
git.sr.ht/~sircmpwn/aerc2/lib/ui.(*Invalidatable).DoInvalidate()
/home/simon/src/aerc2/lib/ui/invalidatable.go:22 +0x85
git.sr.ht/~sircmpwn/aerc2/lib/ui.(*Bordered).contentInvalidated-fm()
/home/simon/src/aerc2/lib/ui/borders.go:39 +0x56
git.sr.ht/~sircmpwn/aerc2/lib/ui.(*Invalidatable).DoInvalidate()
/home/simon/src/aerc2/lib/ui/invalidatable.go:22 +0x85
git.sr.ht/~sircmpwn/aerc2/widgets.NewDirectoryList.func1()
/home/simon/src/aerc2/widgets/dirlist.go:81 +0x55
git.sr.ht/~sircmpwn/aerc2/lib/ui.(*Invalidatable).DoInvalidate()
/home/simon/src/aerc2/lib/ui/invalidatable.go:22 +0x85
git.sr.ht/~sircmpwn/aerc2/widgets.(*Spinner).Start.func1()
/home/simon/src/aerc2/widgets/spinner.go:88 +0x82
Previous write at 0x00c0000bc3d0 by main goroutine:
[failed to restore the stack]
Goroutine 7 (running) created at:
git.sr.ht/~sircmpwn/aerc2/widgets.(*Spinner).Start()
/home/simon/src/aerc2/widgets/spinner.go:46 +0x98
git.sr.ht/~sircmpwn/aerc2/widgets.NewDirectoryList()
/home/simon/src/aerc2/widgets/dirlist.go:37 +0x28b
git.sr.ht/~sircmpwn/aerc2/widgets.NewAccountView()
/home/simon/src/aerc2/widgets/account.go:49 +0x5ca
git.sr.ht/~sircmpwn/aerc2/widgets.NewAerc()
/home/simon/src/aerc2/widgets/aerc.go:60 +0x807
main.main()
/home/simon/src/aerc2/aerc.go:65 +0x33e
|
|
MessageStore has a lot of exported fields that can be read from the outside.
Each read must be protected, because a call from Update could happen at any
time.
|
|
This is read/written from different goroutines.
Write at 0x00c00009c6f0 by goroutine 7:
git.sr.ht/~sircmpwn/aerc2/lib/ui.(*Grid).cellInvalidated()
/home/simon/src/aerc2/lib/ui/grid.go:189 +0x122
git.sr.ht/~sircmpwn/aerc2/lib/ui.(*Grid).cellInvalidated-fm()
/home/simon/src/aerc2/lib/ui/grid.go:178 +0x55
git.sr.ht/~sircmpwn/aerc2/lib/ui.(*Invalidatable).DoInvalidate()
/home/simon/src/aerc2/lib/ui/invalidatable.go:22 +0x85
git.sr.ht/~sircmpwn/aerc2/lib/ui.(*Bordered).contentInvalidated-fm()
/home/simon/src/aerc2/lib/ui/borders.go:39 +0x56
git.sr.ht/~sircmpwn/aerc2/lib/ui.(*Invalidatable).DoInvalidate()
/home/simon/src/aerc2/lib/ui/invalidatable.go:22 +0x85
git.sr.ht/~sircmpwn/aerc2/widgets.NewDirectoryList.func1()
/home/simon/src/aerc2/widgets/dirlist.go:81 +0x55
git.sr.ht/~sircmpwn/aerc2/lib/ui.(*Invalidatable).DoInvalidate()
/home/simon/src/aerc2/lib/ui/invalidatable.go:22 +0x85
git.sr.ht/~sircmpwn/aerc2/widgets.(*Spinner).Start.func1()
/home/simon/src/aerc2/widgets/spinner.go:88 +0x82
Previous write at 0x00c00009c6f0 by main goroutine:
[failed to restore the stack]
Goroutine 7 (running) created at:
git.sr.ht/~sircmpwn/aerc2/widgets.(*Spinner).Start()
/home/simon/src/aerc2/widgets/spinner.go:46 +0x98
git.sr.ht/~sircmpwn/aerc2/widgets.NewDirectoryList()
/home/simon/src/aerc2/widgets/dirlist.go:37 +0x28b
git.sr.ht/~sircmpwn/aerc2/widgets.NewAccountView()
/home/simon/src/aerc2/widgets/account.go:49 +0x5ca
git.sr.ht/~sircmpwn/aerc2/widgets.NewAerc()
/home/simon/src/aerc2/widgets/aerc.go:60 +0x807
main.main()
/home/simon/src/aerc2/aerc.go:65 +0x33e
|
|
Many Drawable implementations have their own Invalidate and OnInvalidate
functions, with an unexported onInvalidate field. However OnInvalidate and
Invalidate are usually not called in the same goroutine. This results in a race
on this field, e.g.:
Read at 0x00c000094748 by goroutine 7:
git.sr.ht/~sircmpwn/aerc2/widgets.NewDirectoryList.func1()
/home/simon/src/aerc2/widgets/dirlist.go:85 +0x56
git.sr.ht/~sircmpwn/aerc2/widgets.(*Spinner).Start.func1()
/home/simon/src/aerc2/widgets/spinner.go:93 +0x1bb
Previous write at 0x00c000094748 by main goroutine:
[failed to restore the stack]
Goroutine 7 (running) created at:
git.sr.ht/~sircmpwn/aerc2/widgets.(*Spinner).Start()
/home/simon/src/aerc2/widgets/spinner.go:46 +0x8f
git.sr.ht/~sircmpwn/aerc2/widgets.NewDirectoryList()
/home/simon/src/aerc2/widgets/dirlist.go:37 +0x286
git.sr.ht/~sircmpwn/aerc2/widgets.NewAccountView()
/home/simon/src/aerc2/widgets/account.go:50 +0x5ca
git.sr.ht/~sircmpwn/aerc2/widgets.NewAerc()
/home/simon/src/aerc2/widgets/aerc.go:60 +0x800
main.main()
/home/simon/src/aerc2/aerc.go:65 +0x33e
To fix this, introduce a new type, Invalidatable, which protects the field.
Unfortunately the Drawable must be passed to the callback function in
Invalidate, so we still need to re-implement this in each Invalidatable user.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TODO: Don't let the user select or interact with deleted messages
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|