swiftbar / SwiftBar

Powerful macOS menu bar customization tool
https://swiftbar.app
MIT License
2.87k stars 91 forks source link

Request: allow streaming-style reset for all plugins #355

Closed dchevell closed 12 months ago

dchevell commented 1 year ago

The streamable feature to reset a menu via ~~~ is great, but at present it doesn't seem to be compatible with "regular" style plugins that run on an interval. In my testing, once streamable is set on a script, it ceases to be run at intervals (whether set via the filename, or via a cron-style property)

My use case here is simple: even on scripts that are not streaming "forever", it's useful to be able to reset the menu. For example, I have my own in-menu calendar with some shell functions that change settings and reload the plugin - and since load time is a little slow, I cache some expensive data and rebuild the menu from that, then subsequently fetch the latest data and rebuild again once it's available, but the script is still better suited to be run on a schedule. Right now my workaround is simply running it in a while True loop and sleeping between runs in the script.

I don't think there's any functional reason preventing ~~~ to operate as a reset operator in all scripts, vs. having to mark them as streamable. The only danger I might foresee would be if users with traditional long-running streaming scripts were inadvertently setting a schedule for them as well, and causing multiple concurrent instances to run - but I'd suggest that it's not worth trying to build all possible guardrails to prevent users shooting themselves in the foot, at least not unless it becomes a problem.

dchevell commented 1 year ago

(by the way: I LOVE this app ❤️)

melonamin commented 1 year ago

I'm wrapping my head around this request. Let me think out loud.

Default\Regular Plugins

Streamable Plugins

There is no reason for the Stremable plugin to react to scheduling events, because it is always running, it is essentially a long poll, it has a state. At the same time, there is no reason for Default\Regular Plugin to react to ~~~, because each plugin refresh(manual or scheduled) completely replaces the content of the menu bar item, it is stateless.

I don't know all the details of your use case, but I'd suggest either:

dchevell commented 1 year ago

emulate streamable plugin with while true and sleep, and let SwiftBar manage the state

The problem I run into here is that suddenly a simple script now has to be a resilient long running process. If my script happens to error out during one of its loops, that's it: the menu bar item will just remain at its last state until I notice or happen to refresh all plugins. In my experience SwiftBar doesn't restart a streamable script on error (I think this is expected behaviour), and doesn't report any errors if previous states had been rendered successfully.

If SwiftBar is meant to manage these scripts like a daemon or process manager, keeping them running, restarting them on failure, etc. then I suppose implementing my own "scheduling" (though this feels like I'm just hacking to replace a feature that already exists), but short of that then it feels like a very brittle approach.

The problem with caching the results of expensive queries is that non-streamable scripts don't render until the entire script finishes executing, even if you have explicitly flushed to stdout. So doing something smart like reading from a cache, rendering the menu, then running the expensive function to save its results for next time, doesn't work, because it leads to very long load times.

I guess my overall point here is that an app of this category is by nature nearly infinite in its possible use cases - resetting the menu is a great feature, but having to implement a failure-resistant long running script just to do so seems a bit arbitrary to me.

melonamin commented 1 year ago

If my script happens to error out during one of its loops, that's it: the menu bar item will just remain at its last state until I notice or happen to refresh all plugins.

I see this an opportunity for improvement, on the first glance nothing prevents me from at least surfacing the error.

What I still don't understand is how you expect ~~~ to behave in regular plugin, maybe you can illustrate this for me somehow. For me it just doesn't make sense.

dchevell commented 1 year ago

I see this an opportunity for improvement, on the first glance nothing prevents me from at least surfacing the error. Awesome, that would be great!

What I still don't understand is how you expect ~~~ to behave in regular plugin Exactly the same as a streamable plugin. A simple (useless) example:


#!/usr/bin/env bash

echo "hello" echo "---" echo "option 1" echo "option 2" echo "option 3" sleep 3

echo "~~~" echo "goodbye" echo "---" echo "option 1" echo "option 2" echo "option 3"



I'm wondering if the disconnect between my thinking and yours here is because you have a concrete mental model for how things are actually implemented, whereas I'm just looking at features in abstract. 

If, for example, you're executing non-streamable scripts and waiting for them to complete _before_ parsing the output, then of course this request and my examples make no sense. Whereas in my mind, I'm in control of what gets printed to stdout and when, so it feels weird to me that I can't use the reset operator when I want to unless I turn the entire thing into some infinite loop when I'd much prefer to let Swiftbar keep managing the cron schedule instead of trying to write my own loop and - even if you did surface errors - having to deal with the possibility that it might just stop until i detect the error and restart it. 

I don't want to go too far in making assumptions about how streamable vs non streamable is actually implemented, but if I'm right in thinking that it's broadly "execute, wait to finish, then parse output" vs "execute, attach to stdout, parse live" then I guess what I'm suggesting here is that the latter should just be the standard behaviour, and rather than users flagging "how should stdout be parsed?" we should just toggle "do you want swiftbar to run this on a schedule, or do you want it to just tell swiftbar this script is expected to run forever?"
melonamin commented 1 year ago

broadly "execute, wait to finish, then parse output" vs "execute, attach to stdout, parse live"

Yes, this is exactly how it works right now. SwiftBar started as a rewrite of BitBar and this behaviour was inherited. I can't change it, it will affect to many of existing plugins. BitBar\xbar has still a lot of users, I think the split is between SwiftBar and xbar is roughly 50\50, but xbar is stale at the moment and I can't expect them to change something with me in unison.

So overall, I think the risk/reward ration is not good for implementing this feature, at least until SwiftBar can "dictate" how the things should be.

Let's keep this issue hanging, I'll think about it in the background.

melonamin commented 12 months ago

Hey @dchevell, this issue been here for a while... :)

In my thinking about it I still feel like the model "execute, wait to finish, then parse output" is better suited for regular plugins.

Tell me about your usecase, did you implemented any changes that made your life easier or still "struggling" with this issues? I don't think I'll change the foundational behaviour of regular plugins, but maybe I can tweak something in streaming implementation?

dchevell commented 12 months ago

That's fair @melonamin, you're welcome to close this one. The things I was trying to solve for were:

  1. Caching - some of my plugins are expensive, and rather than having them stuck for 5-10 seconds on first load I'm currently caching the most recent version myself, printing it, then resetting and printing the latest result once it's ready. These plugins would otherwise be better off as non-streamable plugins, because I don't want to manage my own cron scheduling etc, but I have to in order to leverage the reset operator as a way to enable caching
  2. Keep alive - streamable scripts need to run forever, which means they suddenly need to be way more robust against any possible error. A normal script that runs once and prints a menu either works or it doesn't - showing that it crashed with an error makes sense. A streamable script might've been running fine for weeks, and solving for those edge cases might not be worth it for me. An off-by-default option to keep alive / restart-if-stopped would be great

I'll close this myself, hopefully the above are useful ideas ;)