- Added partition, key, offset, timestamp and headers to
`KafkaMessageEvent`.
- Added `group_id` to consumers configuration.
- Added `key`, `timeout` and `compression_type` to `kafka.publish`.
- Switched to dark mode (I couldn't find any decent light theme
combination that works with the Platypush UI).
- Support passing static `content` and `content-type` rather than only a
`file`.
- Pass the `with-save` property from `EditorModal` to `Editor`.
`ssl.wrap_socket()` has been removed in Python 3.12 - see
https://docs.python.org/3.12/whatsnew/3.12.html#ssl.
Instead, the integration should create its own `SSLContext` and use
the `wrap_socket()` from that object.
In Tornado there can apparently be some race condition where `open` on a
Websocket handler does a `self.close()`, but the client is still sending
some bytes.
In that case, it may happen that the extra message is still processed.
This commit prevents the race condition by raising an exception in
`open` upon authentication failure instead of doing `close()+return`.
The testing repo is only required by `py3-marshmallow`, which is not yet
included in the community repo, but it can end up breaking some builds
because of the incompatibility with the packages in the base repo.
It should suffice for the user to provide username+password when
creating a new API token, even if 2FA is enabled.
That's because user authentication has already occurred by the time that
that check is made, and the user is already logged through a valid
session or API token, so adding an 2FA code check isn't required.
This also ensures that the UI doesn't break with a 401 on
`/#settings?page=tokens&type=api` when creating a new token.
The only case where it's fine to overwrite existing Python packages with
pip versions is when Platypush is running in a virtual environment.
Otherwise, keep the system-installed version if it's available, unless
its version is explicitly incompatible with the one reported in
`requirements.txt`.
A `headers` parameter has been added both to the `http.webpage` plugin
configuration and to the `http.webpage.simplify` action.
It can be used to pass extra headers to the Mercury API (e.g.
`User-Agent` or `Cookie`).
Moreover, the default `User-Agent` sent by Mercury has been changed to
an iPhone to increase the success rate of the scraping process.
The whole `_update_devices` for loop should be covered by a try-except
block. That's because custom attribute getters may be invoked also after
expanding the results, resulting in unhandled `SmartDeviceException`.
The latest release of SQLAlchemy 2.x has apparently removed the
`autocommit` implicit logic for good.
Mutations should be explicitly wrapped into `with ... begin()` blocks,
or they will be rolled back when the connection is closed.
- Compare the serialized versions of the cache before and after, minus
the saved/loaded timestamps, rather than marking the cache as dirty
after a set.
- `Date` and `DateTime` schema fields should have static default values,
or those values will change on every run.
- Always sort all the sets before serializing them.
The format of the `MpvEvent*` classes, the data passed to the event
callback and the available event type enum fields have all changed
between python-mpv < 1.0.0 and >= 1.0.0.
This change makes things work with all mpv + python-mpv versions.
The VLC event callback handler shouldn't try and access the media and/or
the MRL while processing a `MediaPlayerTitleChanged` event.
It seems that in some particular streaming cases (mostly reproducible
with Jellyfin media URLs) this may result in deadlocks - probably
because the media metadata is being handled within the HTTP request, and
the callback handler runs within the same context.
The player may sometimes stop, or the VLC instance crash, without
sending a stop event.
This may leave the Platypush plugin in a PLAYING state, and the instance
may not be properly cleaned up.
This commit adds a polling logic to the monitor thread to verify every
second if the player is still running, and terminate the instance if
that's not the case.
The stream media cache can easily grow in MB in size. Storing it in
Redis means impacting the performance of the application, as on every
web media streaming event it'll have to fetch and deserialize MBs of
data, and Redis may also flush the .rdb file to disk several times in
the process.
- Reduced size of the Ubuntu image by removing some unneeded packages
(docutils, manpages, babel, fonts, python-pil etc.) that take a lot of
space.
- Better self-documented docker-compose.yml.
- Added reference to the registry.platypush.tech/platypush image in
docker-compose.yml (README reference will follow).
- Fixed grep condition in the Docker prepare script.
- Pass `--no-deps` to `pip install platypush`. The dependencies of the
application, now that `marshmallow_dataclasses` has been removed, are
all available in the package managers of the supported images (with
the exception for croniter in Alpine Linux for now), so they can all
be installed via system package manager rather than pip. This also
prevents Ubuntu builds from breaking because system-installed packages
are being overwritten with pip-installed copies.
If a modal was spawned from within an entity in a group, then the whole
group needs to get its zIndex bumped.
Otherwise, the modal component will be "caged" within the scope of the
parent and other entity groups will be rendered above it.
- Add `set` statement, which can be used to set context variables within
YAML procedures. Example:
```yaml
procedure.test:
- set:
foo: bar
- action: logger.info
args:
msg: ${bar}
```
- More reliable flow control for nested break/continue/return.
- Propagate changes to context variables also to upstream procedures.
Any pending `if`s in the parsing queue of a procedure should also be
cleared if the current statement in the procedure is a
break/continue/return.
In such case we should terminate the current branch, and that involves
ensuring that any `if`s branches that are still being parsed are
inserted before the branch-terminating statement.
`Draggable` components should emit `dragend`, not `drop` events.
`drop` should only be emitted by `Droppable` components, or the receiver
of a component that uses both won't be able to tell if a `drop` event
came from a component being dragged, or from an element where a dragged
element was dropped.
The reason is that an `id` specified on procedure level will be applied
to all the child requests.
This means that the first response from the first completed request will
be sent to Redis and mistakenly interpreted by HTTP listeners as the
return value of the whole procedure.
`Procedure.build` should instead calculate its own ID for the procedure,
and apply different IDs to the child requests.
- Added UI panel.
- Added support for entity types.
- Enhanced ability to edit procedures.
- Added ability to create, rename, edit, duplicate and delete stored
procedures.
- Added support for YAML dumps of non-Python procedures.
- Added support for visualizing Python procedures directly in their
source files.
This is to bridge the gap between pointer-based and touch-based devices
and provide a drag-and-drop implementation that exposes a consistent API
for both the interfaces.
These components work by wrapping an underlying draggable/droppable DOM
element and proxying the event handlers consistently when drag/touch
events are detected.
This allows to listen to high-level drag/drop events even on touch-based
interface based on touch start/move/end events.
Example usage:
```vue
<template>
<div class="draggable" ref="draggable">
I can be dragged.
</div>
<div class="droppable" ref="droppable">
Drop elements here.
</div>
<Draggable :element="$refs.draggable"
@drag="console.log('The element is being dragged')"
@drop="console.log('The element is been dropped')" />
<Droppable :element="$refs.droppable"
@dragenter="console.log('Entering')"
@dragleave="console.log('Leaving')"
@dragover="console.log('Dragging over')"
@drop="console.log('Dropped!')" />
</template>
<style lang="scss" scoped>
.draggable {
&.dragged {
opacity: 0.5;
}
}
.droppable {
&.active {
border: 1px solid green;
}
&.selected {
background: yellow;
}
}
</style>
```
- Adds the ability to select lines from the editor, which in turn will
highlight them.
- Adds the ability to load a file and scroll at a specific line if the
URL has with the `line` argument.
- Adds the ability to maximize the file editor modal.
- Added support for multiple element classes.
- Added `glow` property.
- Added support for absolute initial positioning.
- Added dynamic button size.
- Added FloatingButtons component to support groups of floating buttons.
- Don't propagate `close` events. This prevents underlying modals from
being closed on cascade when the current modal is closed.
- Added logic to filter out <ESC> keystrokes that have already targeted
the outermost open modal, so underlying modals won't be closed.
- Added `:before-close` property. This is a callback that can optionally
be passed to the component and it will run some custom logic before
the modal is closed. If it returns false then the modal will stay
open.
- Fail immediately if no branches are checked out.
- Rebase only if we're pushing on master (don't bother for feature
branches).
- Do a push force to Github.
- Update the cached representation of the procedure asynchronously on
the `publish_entities` callback. This prevents stale records from
being loaded from the db before the entities engine has persisted the
new ones.
- Don't re-publish all entities when calling `procedures.status` at the
end of `procedures.save`. This is both for performance reasons and to
avoid sending to the entities engine stale representation of the data.
- `procedures.status` should always sync with the db to ensure that the
action returns the most up-to-date version of the procedures.
- Store, return and propagate entity procedure metadata.
- `procedures.exec` now supports running procedures "on the fly" given a
definition with a list of actions.
- Fixed procedure renaming/overwrite logic.
- Access to `_all_procedures` should always be guarded by a lock.
Earlier any extra parameters passed to the `db` configuration other than
`engine` where ignored.
This enables engine-level configurations such as:
```yaml
db:
# Display all SQL queries
echo: true
```
Messages can be quite big and verbose, and they can anyway be subscribed
over Websockets.
Full dumps are anyway enabled when Platypush is started in verbose mode.
This commit replaces the dumps on INFO level with a quick summary
containing the message ID, request/event type and response time.
If some procedures are removed either from the configuration or from the
loaded scripts, then their associated entities should also be removed
from the database when the `procedures` plugin is loaded.
If some procedures are removed either from the configuration or from the
loaded scripts, then their associated entities should also be removed
from the database when the `procedures` plugin is loaded.
- Added support for file/directory add/copy/move/rename/remove
operations.
- Added automatic detection of MIME types.
- Added support for file view/download.
- Added file uploader component.
- Added custom sorting and other visualization options.
- Added custom `Home` component to show configurable bookmarks above the
filesystem root level.
- Added file editor with automatic syntax highlight.
- Added `uppercase` property (default: true) for the modal title. This
makes it possible to override the default case of the modal title.
- Added support for custom buttons in the modal titlebar.
This is needed in several places in the code where we need to compare if
two strings differ, but either the strings are too long (e.g. content of
large files) or we don't want to pass the original values (e.g.
credentials, session tokens etc.).
- Added `style` property to pass static style rules to the dropdown
body.
- Better positioning of the dropdown when the resulting body is too long
and may overflow the top of the screen - in that case, the dropdown
position needs to be maximized at zero.
- The MIME magic functions apparently aren't thread safe, and they may
crash the interpreter if called concurrently. Lock calls to
`file.get_mime_types`.
- Added an LRU cache for the MIME type results.
`_on_stop_event` may be set by the callback, but then cleared again when
`_reset_state` is called.
This can result in the `_on_stop_event.wait` call in `quit` to time out.
Instead, `_on_stop_event` should be cleared only when the player goes
into `playing` or `paused` mode. It's only then that we know for sure
that the state isn't `stopped`, and only in that case it makes sense to
wait for a stop.
- Support for _Play_ / _Play (With Cache)_ options for YouTube videos.
- Added `media.chromecast` and `media.gstreamer` UI panels.
- Removed `media.omxplayer` - the plugin has been removed.
- Enriched and improved the media info component.
- Propagate the media loading state to all children components.
- Persist query/search state on the URL.
Closes: #422
Don't wait for a value update on `device_set` - an event will be
triggered anyway when the value changes.
This should prevent timeouts when setting/toggling values.
Otherwise the installation won't work properly on Debian oldstable (and
probably any Python installation that doesn't fully support
pyproject.toml yet).
Instead of running `python setup.py --version`.
That's because earlier versions of Python that don't fully support
dynamic version specifications through `pyproject.toml` may just return
`0.0.0` here.
These are required for clients that don't have JS enabled and will get a
404 otherwise.
The routes simply render `index.html`, which will be empty if JS is
disabled and will redirect to the appropriate login/registration Vue
route if the user isn't logged in.
Moved to optional dependencies for `system` plugin.
It requires gcc, linux-headers and python-dev to be installed on the
system.
The `python-psutil` system package however will still be installed when
Platypush is installed through a package manager.
Importing `platypush.__version__` also imports the `platypush` base
package, which in turns depends on at least `pyyaml` being already
installed on the system.
If the connection to Redis goes down, it shouldn't take down the main
thread.
Instead, catch `RedisConnectionError`, and execute `poll` in a loop
until the connection is restored.
- Separated the user model/db classes from the `UserManager`.
- More consistent naming for the flag on the `authenticate_*` functions
that enables returning a tuple with the authentication status - all
those flags are now named `with_status`.
Instead of having a single Flask-provided endpoint, the UI should
initialize its own Vue component and manage the authentication
asynchronously over API.
This is especially a requirement for the implementation of 2FA.
The following routes have also been merged/refactored:
- `POST /register` -> `POST /auth?type=register`
- `POST /login` -> `POST /auth?type=login`
- `POST /auth` -> `POST /auth?type=jwt`
The propagation of the `click` event shouldn't be stopped, as it is
required for the upstream Dropdown event to understand if it needs to
close.
Components should instead listen to `@input` events, so disabled items
will not be triggered.
It seems that `setup.py` started complaining about the installation of
non-regular files.
They seem to be created later anyway (as directories rather than
symlinks), so no further action should be required (hopefully).
Separating the generation of the Arch git package (on each commit to
master) from the generation of the Arch stable package (only on a new
tag) ensures that:
1. The checksum of the package isn't calculated on an older version of
the archive.
2. The stable version of the package is always exactly aligned with the
commit associated to the tag.
The Redis bus now uses a pub/sub architecture rather than a simple
queue.
Earlier on, the application could post an event to the queue and then
pick it up when it started listening.
When doing a publish on a pub/sub channel, however, any messages
sent before the client started listening will be lost.
These events should be available for all YouTube videos, regardless of
where they are rendered:
- `add-to-playlist`
- `remove-from-playlist`
- `download`
- Use pubsub pattern rather than `rpush`/`blpop` - it saves memory, it's
faster, and it decreases the risk of deadlocks.
- Use a connection pool.
- Propagate `PLATYPUSH_REDIS_QUEUE` environment variable so any
subprocesses can access it.
- Use pubsub pattern rather than `rpush`/`blpop` - it saves memory, it's
faster, and it decreases the risk of deadlocks.
- Use a connection pool.
- Propagate `PLATYPUSH_REDIS_QUEUE` environment variable so any
subprocesses can access it.
Streaming content from a Flask route wrapped into a Tornado route is a
buffering nightmare.
`/file` has now been migrated to a pure Tornado asynchronous route
instead.
- `stop_conversation_on_speech_match` should default to True.
- `render_response` should also handle conversation follow-ups, set the
follow-up to True if the response ends with a question mark and the
value of `with_follow_on_turn` is not set,
- Don't render responses if a `tts_plugin` is not set.
The previous commit prompted a new error:
```
2024-06-01 10:54:08,310|ERROR|platypush:plugin:bluetooth|module 'platypush.entities.time' has no attribute 'time'
Traceback (most recent call last):
File "/usr/lib/python3.9/dist-packages/platypush/plugins/__init__.py", line 247, in _runner
self.main()
File "/usr/lib/python3.9/dist-packages/platypush/plugins/bluetooth/__init__.py", line 590, in main
self._refresh_cache()
File "/usr/lib/python3.9/dist-packages/platypush/plugins/bluetooth/__init__.py", line 146, in _refresh_cache
get_entities_engine().wait_start()
File "/usr/lib/python3.9/dist-packages/platypush/entities/__init__.py", line 48, in get_entities_engine
time_start = time.time()
AttributeError: module 'platypush.entities.time' has no attribute 'time'
```
Which explains even the previous error: `import time` in that module
won't use the `time` module from the Python library, but the `.time`
module within the same directory.
This error only happens when the current directory is part of PYTHONPATH
(and usually it shouldn't), but for sake of keeping things safe I've
replaced `time()` with `utcnow().timestamp()`, with `utcnow` imported
from `platypush.utils`.
```
Traceback (most recent call last):
File "/usr/lib/python3.9/dist-packages/platypush/plugins/__init__.py", line 247, in _runner
self.main()
File "/usr/lib/python3.9/dist-packages/platypush/plugins/bluetooth/__init__.py", line 590, in main
self._refresh_cache()
File "/usr/lib/python3.9/dist-packages/platypush/plugins/bluetooth/__init__.py", line 146, in _refresh_cache
get_entities_engine().wait_start()
File "/usr/lib/python3.9/dist-packages/platypush/entities/__init__.py", line 48, in get_entities_engine
time_start = time()
TypeError: 'module' object is not callable
```
There isn't a single reason in this world for this error to happen.
If I do `from time import time`, then `t = time()` is 100% valid Python.
I have no clue of what may be causing it, but I hope that this will fix
it.
No need to maintain two different pieces of logic - a `utcnow()` for
Python < 3.11 and `now(datetime.UTC)` for Python >= 3.11.
`datetime.timezone.utc` existed long before datetime.UTC and that's what
the `utcnow` facade should use.
This means that all the `utcnow()` will always have `tzinfo=UTC`
regardless of the Python version.
There's still a problem with the `utcnow()`-generated timestamps that
have been generated by previous versions of Python and stored on the db.
Therefore, when the code performs comparisons with timestamps fetched
from the db, it should always explicitly do a `.replace(tzinfo=utc)` to
ensure that we always compare offset-aware datetime representations.
See blog post for technical details:
https://manganiello.blog/wheres-my-time-again
The new API no longer returns a list of numeric values alone. Instead,
it returns a tuple where the first element is the raw audio, and the
second element contains extra info on the rendered phonemes.
`datetime.utcnow` may be deprecated on Python >= 3.12, but
`datetime.UTC` isn't present on older Python versions.
Added a `platypush.utils.utcnow()` method as a workaround compatible
with both.
`assistant` contains the assistant plugin object that triggered the
event, but you can't create event hook conditions on attributes that are
plugins.
The event should also store a `plugin` attribute which contains the
unique plugin name, so hooks like these can be built:
```
from platypush import hook
from platypush.events.assistant import ConversationStartEvent
@when(ConversationStartEvent, plugin="assistant.google")
def on_google_conversation_start():
...
```
It wouldn't be possible to construct a hook condition like the one above
on the plugin object reported on the `assistant` attribute.
`datetime.utcnow` may be deprecated on Python >= 3.12, but
`datetime.UTC` isn't present on older Python versions.
Added a `platypush.utils.utcnow()` method as a workaround compatible
with both.
`assistant` contains the assistant plugin object that triggered the
event, but you can't create event hook conditions on attributes that are
plugins.
The event should also store a `plugin` attribute which contains the
unique plugin name, so hooks like these can be built:
```
from platypush import hook
from platypush.events.assistant import ConversationStartEvent
@when(ConversationStartEvent, plugin="assistant.google")
def on_google_conversation_start():
...
```
It wouldn't be possible to construct a hook condition like the one above
on the plugin object reported on the `assistant` attribute.
Even though `platypush.events` is just a symlink to
`platypush.message.event`, imports from those two modules will be
treated as different imports, thus hook conditions build on
`platypush.events` imports will never match.
Even though `platypush.events` is just a symlink to
`platypush.message.event`, imports from those two modules will be
treated as different imports, thus hook conditions build on
`platypush.events` imports will never match.
Weird errors seem to happen on Twine on that image:
```
Traceback (most recent call last):
File "/usr/bin/twine", line 5, in <module>
from twine.__main__ import main
File "/usr/lib/python3.11/site-packages/twine/__init__.py", line 32, in <module>
import importlib.metadata
File "/usr/lib/python3.11/importlib/metadata/__init__.py", line 17, in <module>
from . import _adapters, _meta
File "/usr/lib/python3.11/importlib/metadata/_adapters.py", line 3, in <module>
import email.message
File "/usr/lib/python3.11/email/message.py", line 15, in <module>
from email import utils
File "/usr/lib/python3.11/email/utils.py", line 28, in <module>
import random
File "/usr/lib/python3.11/random.py", line 49, in <module>
from math import log as _log, exp as _exp, pi as _pi, e as _e, ceil as _ceil
ImportError: Error relocating /usr/lib/python3.11/lib-dynload/math.cpython-311-x86_64-linux-musl.so: _PyModule_Add: symbol not found
```
- Renamed _Post-installation_ README section as _Configuration_.
- Docs style tweaks for the latest version of the Sphinx theme.
- Adapted the docs index to the new structure of the wiki.
This fixes the case where Platydock is called within the context of a
virtual environment, but it needs to generate a Docker image - and
therefore, unless the host virtual environment, it needs
--break-system-packages to write to /usr.
If the Platypush setup.py is found in the current directory, then use
that directory as the base for the new image.
Otherwise, clone the repo on the fly and build the image from there.
All the latest versions of Alpine, Debian, Ubuntu and Fedora now require
`--break-system-packages` when installing packages via `pip` outside of
a virtual environment, even if it's within a container.
A fully self-contained 1.5k LoC Drone file isn't very maintainable, and
it makes it hard to reuse parts that are shared across multiple steps
(like SSH and git configuration).
`Requires=redis.service` should be commented unless the service is
started as a privileged user.
Also added some comments on how the systemd service usually works.
This allows procedures and event hooks to have more flexible signatures.
Along the lines of:
```python
@when(SomeEvent)
def hook(event):
...
@when(SomeOtherEvent)
def hook2():
...
```
Instead of supporting only the full context spec:
```python
@when(SomeEvent)
def hook(event, **ctx):
...
```
Closes: #400
YAML isn't part of the Python standard library, while JSON is.
If we want `setup.py` to dynamically parse the available integration
manifest files in order to populate the extra dependencies, then it's
better to rely on a JSON format for manifest files - the parser is part
of the standard library and it doesn't require the user to install
`pyyaml` before `platypush`.
The Fit API has (unfortunately) been deprecated by Google with no
alternatives - the new Health Connect API is only available on Android
devices.
Other Google APIs don't seem to be affected by the refresh token issue
either, so this should hopefully close that issue too.
Closes: #372
- Converted `Response` objects into `Schema`s.
- Removed the last references to the deprecated `Mapping` object.
- Fixed all errors and warnings in the plugin.
If there's a good use-case for overriding `Event._matches_condition`
with a logic that also parses the event arguments, then those arguments
should be accessed directly from the event object, not from the match
result.
Initializing `EventMatchResult` with the arguments from the event means
that, if `EventMatchResult.parsed_args` are populated with custom
extracted arguments, then the upstream event arguments will also be
modified.
If the event is matched against multiple conditions, this will result in
the extracted tokens getting modified by each `matches_condition`
iteration.
Instead of being a list, the hooks in the hook processor should be
backed by by-name and by-value maps.
Don't insert a hook if its exact backing method has already been
inserted. This is actually very common when hooks are defined as Python
snippets imported in other scripts too.
The assistant object now runs in its own thread and leverages an
external `SpeechProcessor` that uses two threads to scan for both
intents and speech in parallel on audio frames.
We shouldn't overwrite `event._set` and `event._clear` if those values
have already been set.
Those attributes hold the original references to `Event.set` and
`Event.clear` respectively, and the `OrEvent` logic overwrites them with
a callback-based logic.
This shouldn't happen if those attributes are already present.
- Added `intent_model_path` parameter.
- Always apply `expanduser` to configuration paths.
- Better logic to infer the fallback model path.
- The Picovoice Leonardo object should always be removed after
`assistant.picovoice.transcribe` is called.
The plugin should leverage `AssistantPlugin._on_mute_changed` to handle
the boilerplate state managent on mute/unmute actions instead of
re-implementing the same logic.
- The `Responding` state should be modelled as an extra event/binary
flag, not as an assistant state. The assistant may be listening for
hotwords even while the `tts` plugin is responding, and we don't want
the two states to interfere with each either - neither to build a more
complex state machine that also needs to take concurrent states into
account.
- Stop any responses being rendered upon the `tts` plugin when a new
hotword audio is detected. If e.g. I say "Ok Google", I should always
be able to trigger the assistant and stop any concurrent audio
process.
- `SpeechRecognizedEvent` should be emitted even if `cheetah`'s latest
audio frame results weren't marked as final, and the speech detection
window timed out. Cheetah's `is_final` detection seems to be quite
buggy sometimes, and it may not properly detect the end of utterances,
especially with non-native accents. The workaround is to flush out
whatever text is available (if at least some speech was detected) into
a `SpeechRecognizedEvent` upon timeout.
- Added wiring between `assistant.picovoice` and `tts.picovoice`.
- Added `RESPONDING` status to the assistant.
- Added ability to override the default speech model upon
`start_conversation`.
- Better handling of conversation timeouts.
- Cache Cheetah objects in a `model -> object` map - at least the
default model should be pre-loaded, since model loading at runtime
seems to take a while, and that could impact the ability to detect the
speech in the first seconds after a hotword is detected.
`AssistantEvent.assistant` is now modelled as an opaque object that
behaves the following way:
- The underlying plugin name is saved under `event.args['_assistant']`.
- `event.assistant` is a property that returns the assistant instance
via `get_plugin`.
- `event.assistant` is reported as a string (plugin qualified name) upon
event dump.
This allows event hooks to easily use `event.assistant` to interact with
the underlying assistant and easily modify the conversation flow, while
event hook conditions can still be easily modelled as equality
operations between strings.
Explicitly use a `CastBrowser` object initialized at plugin boot instead
of relying on blocking calls to `pychromecast.get_chromecasts`.
1. It enables better event handling via callbacks instead of
synchronously waiting for scan batches.
2. It optimizes resources - only one Zeroconf and one CastBrowser object
will be created in the plugin, and destroyed upon stop.
3. No need for separate `get_chromecast`/`_refresh_chromecasts` methods:
all the scanning is run continuously, so we can just return the
results from the maps.
- `pychromecast.get_chromecasts` returns both a list of devices and a
browser object. Since the Chromecast plugin is the most likely culprit
of the excessive number of open MDNS sockets, it seems that we may
need to explicitly stop discovery on the browser and close the
ZeroConf object after the discovery is done.
- I was still using an ancient version of pychromecast on my RPi4, and I
didn't notice that more recent versions implemented several breaking
changes. Adapted the code to cope with those changes.
It seems that the process keeps a lot of open connections to Chromecast
devices during playback.
The most likely culprit is the `_refresh_chromecasts` logic.
We should start a `cast` object and register a status listener only if a
Chromecast with the same identifier isn't already registered in the
plugin.
<!doctype html><htmllang="en"><head><metacharset="utf-8"><metahttp-equiv="X-UA-Compatible"content="IE=edge"><metaname="viewport"content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="/favicon.ico"><![endif]--><linkrel="stylesheet"href="/fonts/poppins.css"><title>platypush</title><scriptdefer="defer"src="/static/js/chunk-vendors.aeea9c55.js"></script><scriptdefer="defer"src="/static/js/app.e71ae2ab.js"></script><linkhref="/static/css/chunk-vendors.a2412607.css" rel="stylesheet"><linkhref="/static/css/app.5b1362a4.css" rel="stylesheet"><linkrel="icon"type="image/svg+xml"href="/img/icons/favicon.svg"><linkrel="icon"type="image/png"sizes="32x32"href="/img/icons/favicon-32x32.png"><linkrel="icon"type="image/png"sizes="16x16"href="/img/icons/favicon-16x16.png"><linkrel="manifest"href="/manifest.json"><metaname="theme-color"content="#ffffff"><metaname="apple-mobile-web-app-capable"content="no"><metaname="apple-mobile-web-app-status-bar-style"content="default"><metaname="apple-mobile-web-app-title"content="Platypush"><linkrel="apple-touch-icon"href="/img/icons/apple-touch-icon-152x152.png"><linkrel="mask-icon"href="/img/icons/safari-pinned-tab.svg"color="#ffffff"><metaname="msapplication-TileImage"content="/img/icons/msapplication-icon-144x144.png"><metaname="msapplication-TileColor"content="#000000"></head><body><noscript><strong>We're sorry but platypush doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><divid="app"></div></body></html>
<!doctype html><htmllang="en"><head><metacharset="utf-8"><metahttp-equiv="X-UA-Compatible"content="IE=edge"><metaname="viewport"content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="/favicon.ico"><![endif]--><linkrel="stylesheet"href="/fonts/poppins.css"><title>platypush</title><scriptdefer="defer"src="/static/js/chunk-vendors.83e191d2.js"></script><scriptdefer="defer"src="/static/js/app.53c00686.js"></script><linkhref="/static/css/chunk-vendors.d510eff2.css" rel="stylesheet"><linkhref="/static/css/app.f97a4bca.css" rel="stylesheet"><linkrel="icon"type="image/svg+xml"href="/img/icons/favicon.svg"><linkrel="icon"type="image/png"sizes="32x32"href="/img/icons/favicon-32x32.png"><linkrel="icon"type="image/png"sizes="16x16"href="/img/icons/favicon-16x16.png"><linkrel="manifest"href="/manifest.json"><metaname="theme-color"content="#ffffff"><metaname="apple-mobile-web-app-capable"content="no"><metaname="apple-mobile-web-app-status-bar-style"content="default"><metaname="apple-mobile-web-app-title"content="Platypush"><linkrel="apple-touch-icon"href="/img/icons/apple-touch-icon-152x152.png"><linkrel="mask-icon"href="/img/icons/safari-pinned-tab.svg"color="#ffffff"><metaname="msapplication-TileImage"content="/img/icons/msapplication-icon-144x144.png"><metaname="msapplication-TileColor"content="#000000"></head><body><noscript><strong>We're sorry but platypush doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><divid="app"></div></body></html>