Compare commits
47 commits
af21ff13ff
...
06781cd72c
Author | SHA1 | Date | |
---|---|---|---|
06781cd72c | |||
24f7d4a789 | |||
c788f2d858 | |||
f6b1f92a88 | |||
9f8fe60cdf | |||
83d21d3f04 | |||
54a6b34a64 | |||
377b2c2425 | |||
a152b0d734 | |||
496dfdb50b | |||
9493445af6 | |||
0657c80a5c | |||
e672a7fb5c | |||
e8acf8615f | |||
336cb18cb3 | |||
342df0eeec | |||
e6a358fe27 | |||
818f60a468 | |||
db34a607e4 | |||
8f2e68f0db | |||
8b3c2a8ee1 | |||
92bff4decb | |||
0bb264792e | |||
a5426ede58 | |||
2c481c54af | |||
0010342fb7 | |||
1e9418b072 | |||
213498318f | |||
077e12e9a8 | |||
897e8a9ff7 | |||
b439b8b0f4 | |||
f1c640fabb | |||
666bbe5372 | |||
9dfb22c23a | |||
2b48edfabc | |||
6e27c9b8e4 | |||
74a2958ff4 | |||
8333cc09ee | |||
01571e2e65 | |||
a21aaee888 | |||
5080caa38e | |||
ca5853cbab | |||
1f120b167b | |||
|
09412acba7 | ||
e0ff180fb0 | |||
1189e71539 | |||
50beb1460b |
462 changed files with 6454 additions and 2239 deletions
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -1,5 +1,17 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [1.2.3]
|
||||||
|
|
||||||
|
- [[#422](https://git.platypush.tech/platypush/platypush/issues/422)]: adapted
|
||||||
|
media plugins to support streaming from the yt-dlp process. This allows
|
||||||
|
videos to have merged audio+video even if they had separate tracks upstream.
|
||||||
|
|
||||||
|
- [`media.*`] Many improvements on the media UI.
|
||||||
|
|
||||||
|
- [`zigbee.mqtt`] Removed synchronous logic from `zigbee.mqtt.device_set`. It
|
||||||
|
was prone to timeouts as well as pointless - the updated device state will
|
||||||
|
anyway be received as an event.
|
||||||
|
|
||||||
## [1.2.2]
|
## [1.2.2]
|
||||||
|
|
||||||
- Fixed regression on older version of Python that don't fully support
|
- Fixed regression on older version of Python that don't fully support
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
``media.omxplayer``
|
|
||||||
=====================================
|
|
||||||
|
|
||||||
.. automodule:: platypush.plugins.media.omxplayer
|
|
||||||
:members:
|
|
||||||
|
|
|
@ -77,7 +77,6 @@ Plugins
|
||||||
platypush/plugins/media.kodi.rst
|
platypush/plugins/media.kodi.rst
|
||||||
platypush/plugins/media.mplayer.rst
|
platypush/plugins/media.mplayer.rst
|
||||||
platypush/plugins/media.mpv.rst
|
platypush/plugins/media.mpv.rst
|
||||||
platypush/plugins/media.omxplayer.rst
|
|
||||||
platypush/plugins/media.plex.rst
|
platypush/plugins/media.plex.rst
|
||||||
platypush/plugins/media.subtitles.rst
|
platypush/plugins/media.subtitles.rst
|
||||||
platypush/plugins/media.vlc.rst
|
platypush/plugins/media.vlc.rst
|
||||||
|
|
|
@ -21,7 +21,7 @@ from .utils import run
|
||||||
# see https://git.platypush.tech/platypush/platypush/issues/399
|
# see https://git.platypush.tech/platypush/platypush/issues/399
|
||||||
when = hook
|
when = hook
|
||||||
|
|
||||||
__version__ = '1.2.2'
|
__version__ = '1.2.3'
|
||||||
__author__ = 'Fabio Manganiello <fabio@manganiello.tech>'
|
__author__ = 'Fabio Manganiello <fabio@manganiello.tech>'
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'Application',
|
'Application',
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import os
|
import os
|
||||||
|
import pathlib
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from datetime import datetime as dt
|
from datetime import datetime as dt
|
||||||
from typing import Optional, Tuple
|
from typing import IO, Optional, Tuple
|
||||||
|
|
||||||
from tornado.web import stream_request_body
|
from tornado.web import stream_request_body
|
||||||
|
|
||||||
|
@ -17,6 +18,8 @@ class FileRoute(StreamingRoute):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
BUFSIZE = 1024
|
BUFSIZE = 1024
|
||||||
|
_bytes_written = 0
|
||||||
|
_out_f: Optional[IO[bytes]] = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def path(cls) -> str:
|
def path(cls) -> str:
|
||||||
|
@ -39,6 +42,10 @@ class FileRoute(StreamingRoute):
|
||||||
def file_size(self) -> int:
|
def file_size(self) -> int:
|
||||||
return os.path.getsize(self.file_path)
|
return os.path.getsize(self.file_path)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _content_length(self) -> int:
|
||||||
|
return int(self.request.headers.get('Content-Length', 0))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def range(self) -> Tuple[Optional[int], Optional[int]]:
|
def range(self) -> Tuple[Optional[int], Optional[int]]:
|
||||||
range_hdr = self.request.headers.get('Range')
|
range_hdr = self.request.headers.get('Range')
|
||||||
|
@ -105,6 +112,77 @@ class FileRoute(StreamingRoute):
|
||||||
|
|
||||||
self.finish()
|
self.finish()
|
||||||
|
|
||||||
|
def on_finish(self) -> None:
|
||||||
|
if self._out_f:
|
||||||
|
try:
|
||||||
|
if not (self._out_f and self._out_f.closed):
|
||||||
|
self._out_f.close()
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.warning('Error while closing the output file: %s', e)
|
||||||
|
|
||||||
|
self._out_f = None
|
||||||
|
|
||||||
|
return super().on_finish()
|
||||||
|
|
||||||
|
def _validate_upload(self, force: bool = False) -> bool:
|
||||||
|
if not self.file_path:
|
||||||
|
self.write_error(400, 'Missing path argument')
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not self._out_f:
|
||||||
|
if not force and os.path.exists(self.file_path):
|
||||||
|
self.write_error(409, f'{self.file_path} already exists')
|
||||||
|
return False
|
||||||
|
|
||||||
|
self._bytes_written = 0
|
||||||
|
dir_path = os.path.dirname(self.file_path)
|
||||||
|
|
||||||
|
try:
|
||||||
|
pathlib.Path(dir_path).mkdir(parents=True, exist_ok=True)
|
||||||
|
self._out_f = open( # pylint: disable=consider-using-with
|
||||||
|
self.file_path, 'wb'
|
||||||
|
)
|
||||||
|
except PermissionError:
|
||||||
|
self.write_error(403, 'Permission denied')
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def finish(self, *args, **kwargs): # type: ignore
|
||||||
|
try:
|
||||||
|
return super().finish(*args, **kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.warning('Error while finishing the request: %s', e)
|
||||||
|
|
||||||
|
def data_received(self, chunk: bytes):
|
||||||
|
# Ignore unless we're in POST/PUT mode
|
||||||
|
if self.request.method not in ('POST', 'PUT'):
|
||||||
|
return
|
||||||
|
|
||||||
|
force = self.request.method == 'PUT'
|
||||||
|
if not self._validate_upload(force=force):
|
||||||
|
self.finish()
|
||||||
|
return
|
||||||
|
|
||||||
|
if not chunk:
|
||||||
|
self.logger.debug('Received EOF from client')
|
||||||
|
self.finish()
|
||||||
|
return
|
||||||
|
|
||||||
|
assert self._out_f
|
||||||
|
self._out_f.write(chunk)
|
||||||
|
self._out_f.flush()
|
||||||
|
self._bytes_written += len(chunk)
|
||||||
|
self.logger.debug(
|
||||||
|
'Written chunk of size %d to %s, progress: %d/%d',
|
||||||
|
len(chunk),
|
||||||
|
self.file_path,
|
||||||
|
self._bytes_written,
|
||||||
|
self._content_length,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.flush()
|
||||||
|
|
||||||
def get(self) -> None:
|
def get(self) -> None:
|
||||||
with self._serve() as f:
|
with self._serve() as f:
|
||||||
if f:
|
if f:
|
||||||
|
@ -119,3 +197,9 @@ class FileRoute(StreamingRoute):
|
||||||
def head(self) -> None:
|
def head(self) -> None:
|
||||||
with self._serve():
|
with self._serve():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def post(self) -> None:
|
||||||
|
self.logger.info('Receiving file POST upload request for %r', self.file_path)
|
||||||
|
|
||||||
|
def put(self) -> None:
|
||||||
|
self.logger.info('Receiving file PUT upload request for %r', self.file_path)
|
||||||
|
|
|
@ -25,10 +25,15 @@ def load_media_map() -> MediaMap:
|
||||||
logger().warning('Could not load media map: %s', e)
|
logger().warning('Could not load media map: %s', e)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
return {
|
parsed_map = {}
|
||||||
media_id: MediaHandler.build(**media_info)
|
for media_id, media_info in media_map.items():
|
||||||
for media_id, media_info in media_map.items()
|
try:
|
||||||
}
|
parsed_map[media_id] = MediaHandler.build(**media_info)
|
||||||
|
except Exception as e:
|
||||||
|
logger().debug('Could not load media %s: %s', media_id, e)
|
||||||
|
continue
|
||||||
|
|
||||||
|
return parsed_map
|
||||||
|
|
||||||
|
|
||||||
def save_media_map(new_map: MediaMap):
|
def save_media_map(new_map: MediaMap):
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
import hashlib
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
from typing import Generator, Optional
|
from typing import Generator, Optional
|
||||||
|
|
||||||
from platypush.message import JSONAble
|
from platypush.message import JSONAble
|
||||||
|
@ -57,9 +56,6 @@ class MediaHandler(JSONAble, ABC):
|
||||||
logging.exception(e)
|
logging.exception(e)
|
||||||
errors[hndl_class.__name__] = str(e)
|
errors[hndl_class.__name__] = str(e)
|
||||||
|
|
||||||
if os.path.exists(source):
|
|
||||||
source = f'file://{source}'
|
|
||||||
|
|
||||||
raise AttributeError(
|
raise AttributeError(
|
||||||
f'The source {source} has no handlers associated. Errors: {errors}'
|
f'The source {source} has no handlers associated. Errors: {errors}'
|
||||||
)
|
)
|
||||||
|
|
|
@ -15,6 +15,9 @@ class FileHandler(MediaHandler):
|
||||||
prefix_handlers = ['file://']
|
prefix_handlers = ['file://']
|
||||||
|
|
||||||
def __init__(self, source, *args, **kwargs):
|
def __init__(self, source, *args, **kwargs):
|
||||||
|
if isinstance(source, str) and os.path.exists(source):
|
||||||
|
source = f'file://{source}'
|
||||||
|
|
||||||
super().__init__(source, *args, **kwargs)
|
super().__init__(source, *args, **kwargs)
|
||||||
|
|
||||||
self.path = os.path.abspath(
|
self.path = os.path.abspath(
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="/favicon.ico"><![endif]--><link rel="stylesheet" href="/fonts/poppins.css"><title>platypush</title><script defer="defer" src="/static/js/chunk-vendors.05911ac4.js"></script><script defer="defer" src="/static/js/app.1f786d8c.js"></script><link href="/static/css/chunk-vendors.d510eff2.css" rel="stylesheet"><link href="/static/css/app.d1412c5b.css" rel="stylesheet"><link rel="icon" type="image/svg+xml" href="/img/icons/favicon.svg"><link rel="icon" type="image/png" sizes="32x32" href="/img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/img/icons/favicon-16x16.png"><link rel="manifest" href="/manifest.json"><meta name="theme-color" content="#ffffff"><meta name="apple-mobile-web-app-capable" content="no"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="Platypush"><link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color="#ffffff"><meta name="msapplication-TileImage" content="/img/icons/msapplication-icon-144x144.png"><meta name="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><div id="app"></div></body></html>
|
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="/favicon.ico"><![endif]--><link rel="stylesheet" href="/fonts/poppins.css"><title>platypush</title><script defer="defer" src="/static/js/chunk-vendors.d9b38fb8.js"></script><script defer="defer" src="/static/js/app.81716459.js"></script><link href="/static/css/chunk-vendors.d510eff2.css" rel="stylesheet"><link href="/static/css/app.92a2d867.css" rel="stylesheet"><link rel="icon" type="image/svg+xml" href="/img/icons/favicon.svg"><link rel="icon" type="image/png" sizes="32x32" href="/img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/img/icons/favicon-16x16.png"><link rel="manifest" href="/manifest.json"><meta name="theme-color" content="#ffffff"><meta name="apple-mobile-web-app-capable" content="no"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="Platypush"><link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color="#ffffff"><meta name="msapplication-TileImage" content="/img/icons/msapplication-icon-144x144.png"><meta name="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><div id="app"></div></body></html>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
platypush/backend/http/webapp/dist/static/css/1449.ce516455.css
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/css/1449.ce516455.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
platypush/backend/http/webapp/dist/static/css/1807.c70c7c56.css
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/css/1807.c70c7c56.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
platypush/backend/http/webapp/dist/static/css/1845.44803384.css
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/css/1845.44803384.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
platypush/backend/http/webapp/dist/static/css/2015.72c85f17.css
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/css/2015.72c85f17.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
platypush/backend/http/webapp/dist/static/css/2018.328996a3.css
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/css/2018.328996a3.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
platypush/backend/http/webapp/dist/static/css/2140.b4ae93f4.css
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/css/2140.b4ae93f4.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
platypush/backend/http/webapp/dist/static/css/215.8a6bf650.css
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/css/215.8a6bf650.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
platypush/backend/http/webapp/dist/static/css/2496.b95258a7.css
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/css/2496.b95258a7.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
platypush/backend/http/webapp/dist/static/css/2694.4d460f53.css
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/css/2694.4d460f53.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
platypush/backend/http/webapp/dist/static/css/2718.5e6c64be.css
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/css/2718.5e6c64be.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
platypush/backend/http/webapp/dist/static/css/293.e32ceb33.css
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/css/293.e32ceb33.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
platypush/backend/http/webapp/dist/static/css/3248.6a769f04.css
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/css/3248.6a769f04.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
platypush/backend/http/webapp/dist/static/css/3748.e5dd2d4d.css
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/css/3748.e5dd2d4d.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
platypush/backend/http/webapp/dist/static/css/4015.32be3494.css
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/css/4015.32be3494.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
platypush/backend/http/webapp/dist/static/css/4364.9c5fd763.css
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/css/4364.9c5fd763.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
platypush/backend/http/webapp/dist/static/css/4964.246eef62.css
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/css/4964.246eef62.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
platypush/backend/http/webapp/dist/static/css/5232.6c48a5f5.css
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/css/5232.6c48a5f5.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
platypush/backend/http/webapp/dist/static/css/5285.3beb55f0.css
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/css/5285.3beb55f0.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
platypush/backend/http/webapp/dist/static/css/6008.efad8522.css
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/css/6008.efad8522.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
platypush/backend/http/webapp/dist/static/css/6429.07c224e1.css
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/css/6429.07c224e1.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
platypush/backend/http/webapp/dist/static/css/6561.12fc66be.css
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/css/6561.12fc66be.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
11
platypush/backend/http/webapp/dist/static/css/6746.be9b6e29.css
vendored
Normal file
11
platypush/backend/http/webapp/dist/static/css/6746.be9b6e29.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
platypush/backend/http/webapp/dist/static/css/6882.6c201612.css
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/css/6882.6c201612.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
platypush/backend/http/webapp/dist/static/css/7435.11ac4415.css
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/css/7435.11ac4415.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
platypush/backend/http/webapp/dist/static/css/7503.a542704b.css
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/css/7503.a542704b.css
vendored
Normal file
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue