Restyled by black

This commit is contained in:
Restyled.io
2021-05-22 01:26:33 +00:00
parent 58e8ba2347
commit 544c57ff1f
52 changed files with 3303 additions and 2729 deletions

View File

@@ -22,9 +22,7 @@ class AbsChunkedInputStream(InputStream, HaltListener):
_decoded_length: int = 0
def __init__(self, retry_on_chunk_error: bool):
self.retries: typing.Final[typing.List[int]] = [
0 for _ in range(self.chunks())
]
self.retries: typing.Final[typing.List[int]] = [0 for _ in range(self.chunks())]
self.retry_on_chunk_error = retry_on_chunk_error
def is_closed(self) -> bool:
@@ -109,10 +107,13 @@ class AbsChunkedInputStream(InputStream, HaltListener):
self.request_chunk_from_stream(chunk)
self.requested_chunks()[chunk] = True
for i in range(chunk + 1,
min(self.chunks() - 1, chunk + self.preload_ahead) + 1):
if (self.requested_chunks()[i]
and self.retries[i] < self.preload_chunk_retries):
for i in range(
chunk + 1, min(self.chunks() - 1, chunk + self.preload_ahead) + 1
):
if (
self.requested_chunks()[i]
and self.retries[i] < self.preload_chunk_retries
):
self.request_chunk_from_stream(i)
self.requested_chunks()[chunk] = True
@@ -146,10 +147,7 @@ class AbsChunkedInputStream(InputStream, HaltListener):
self.check_availability(chunk, True, True)
def read(self,
b: bytearray = None,
offset: int = None,
length: int = None) -> int:
def read(self, b: bytearray = None, offset: int = None, length: int = None) -> int:
if b is None and offset is None and length is None:
return self.internal_read()
if not (b is not None and offset is not None and length is not None):
@@ -159,8 +157,9 @@ class AbsChunkedInputStream(InputStream, HaltListener):
raise IOError("Stream is closed!")
if offset < 0 or length < 0 or length > len(b) - offset:
raise IndexError("offset: {}, length: {}, buffer: {}".format(
offset, length, len(b)))
raise IndexError(
"offset: {}, length: {}, buffer: {}".format(offset, length, len(b))
)
elif length == 0:
return 0
@@ -175,8 +174,7 @@ class AbsChunkedInputStream(InputStream, HaltListener):
self.check_availability(chunk, True, False)
copy = min(len(self.buffer()[chunk]) - chunk_off, length - i)
b[offset + 0:copy] = self.buffer()[chunk][chunk_off:chunk_off +
copy]
b[offset + 0 : copy] = self.buffer()[chunk][chunk_off : chunk_off + copy]
i += copy
self._pos += copy
@@ -224,4 +222,5 @@ class AbsChunkedInputStream(InputStream, HaltListener):
@staticmethod
def from_stream_error(stream_error: int):
return AbsChunkedInputStream.ChunkException(
"Failed due to stream error, code: {}".format(stream_error))
"Failed due to stream error, code: {}".format(stream_error)
)

View File

@@ -25,10 +25,7 @@ class AudioKeyManager(PacketsReceiver):
def __init__(self, session: Session):
self._session = session
def get_audio_key(self,
gid: bytes,
file_id: bytes,
retry: bool = True) -> bytes:
def get_audio_key(self, gid: bytes, file_id: bytes, retry: bool = True) -> bytes:
seq: int
with self._seqHolderLock:
seq = self._seqHolder
@@ -51,8 +48,9 @@ class AudioKeyManager(PacketsReceiver):
return self.get_audio_key(gid, file_id, False)
raise RuntimeError(
"Failed fetching audio key! gid: {}, fileId: {}".format(
Utils.Utils.bytes_to_hex(gid),
Utils.Utils.bytes_to_hex(file_id)))
Utils.Utils.bytes_to_hex(gid), Utils.Utils.bytes_to_hex(file_id)
)
)
return key
@@ -62,8 +60,7 @@ class AudioKeyManager(PacketsReceiver):
callback = self._callbacks.get(seq)
if callback is None:
self._LOGGER.warning(
"Couldn't find callback for seq: {}".format(seq))
self._LOGGER.warning("Couldn't find callback for seq: {}".format(seq))
return
if packet.is_cmd(Packet.Type.aes_key):
@@ -75,7 +72,9 @@ class AudioKeyManager(PacketsReceiver):
else:
self._LOGGER.warning(
"Couldn't handle packet, cmd: {}, length: {}".format(
packet.cmd, len(packet.payload)))
packet.cmd, len(packet.payload)
)
)
class Callback:
def key(self, key: bytes) -> None:
@@ -99,15 +98,15 @@ class AudioKeyManager(PacketsReceiver):
def error(self, code: int) -> None:
self._audioKeyManager._LOGGER.fatal(
"Audio key error, code: {}".format(code))
"Audio key error, code: {}".format(code)
)
with self.reference_lock:
self.reference.put(None)
self.reference_lock.notify_all()
def wait_response(self) -> bytes:
with self.reference_lock:
self.reference_lock.wait(
AudioKeyManager._AUDIO_KEY_REQUEST_TIMEOUT)
self.reference_lock.wait(AudioKeyManager._AUDIO_KEY_REQUEST_TIMEOUT)
return self.reference.get(block=False)
class AesKeyException(IOError):

View File

@@ -14,7 +14,9 @@ import typing
class PlayableContentFeeder:
_LOGGER: logging = logging.getLogger(__name__)
STORAGE_RESOLVE_INTERACTIVE: str = "/storage-resolve/files/audio/interactive/{}"
STORAGE_RESOLVE_INTERACTIVE_PREFETCH: str = "/storage-resolve/files/audio/interactive_prefetch/{}"
STORAGE_RESOLVE_INTERACTIVE_PREFETCH: str = (
"/storage-resolve/files/audio/interactive_prefetch/{}"
)
session: Session
def __init__(self, session: Session):
@@ -30,20 +32,31 @@ class PlayableContentFeeder:
return None
def load(self, playable_id: PlayableId,
audio_quality_picker: AudioQualityPicker, preload: bool,
halt_listener: HaltListener):
def load(
self,
playable_id: PlayableId,
audio_quality_picker: AudioQualityPicker,
preload: bool,
halt_listener: HaltListener,
):
if type(playable_id) is TrackId:
return self.load_track(playable_id, audio_quality_picker, preload,
halt_listener)
return self.load_track(
playable_id, audio_quality_picker, preload, halt_listener
)
def resolve_storage_interactive(
self, file_id: bytes,
preload: bool) -> StorageResolve.StorageResolveResponse:
self, file_id: bytes, preload: bool
) -> StorageResolve.StorageResolveResponse:
resp = self.session.api().send(
"GET", (self.STORAGE_RESOLVE_INTERACTIVE_PREFETCH
if preload else self.STORAGE_RESOLVE_INTERACTIVE).format(
Utils.bytes_to_hex(file_id)), None, None)
"GET",
(
self.STORAGE_RESOLVE_INTERACTIVE_PREFETCH
if preload
else self.STORAGE_RESOLVE_INTERACTIVE
).format(Utils.bytes_to_hex(file_id)),
None,
None,
)
if resp.status_code != 200:
raise RuntimeError(resp.status_code)
@@ -55,13 +68,15 @@ class PlayableContentFeeder:
storage_resolve_response.ParseFromString(body)
return storage_resolve_response
def load_track(self, track_id_or_track: typing.Union[TrackId,
Metadata.Track],
audio_quality_picker: AudioQualityPicker, preload: bool,
halt_listener: HaltListener):
def load_track(
self,
track_id_or_track: typing.Union[TrackId, Metadata.Track],
audio_quality_picker: AudioQualityPicker,
preload: bool,
halt_listener: HaltListener,
):
if type(track_id_or_track) is TrackId:
original = self.session.api().get_metadata_4_track(
track_id_or_track)
original = self.session.api().get_metadata_4_track(track_id_or_track)
track = self.pick_alternative_if_necessary(original)
if track is None:
raise
@@ -69,25 +84,31 @@ class PlayableContentFeeder:
track = track_id_or_track
file = audio_quality_picker.get_file(track.file)
if file is None:
self._LOGGER.fatal(
"Couldn't find any suitable audio file, available")
self._LOGGER.fatal("Couldn't find any suitable audio file, available")
raise
return self.load_stream(file, track, None, preload, halt_listener)
def load_stream(self, file: Metadata.AudioFile, track: Metadata.Track,
episode: Metadata.Episode, preload: bool,
halt_lister: HaltListener):
def load_stream(
self,
file: Metadata.AudioFile,
track: Metadata.Track,
episode: Metadata.Episode,
preload: bool,
halt_lister: HaltListener,
):
if track is None and episode is None:
raise RuntimeError()
resp = self.resolve_storage_interactive(file.file_id, preload)
if resp.result == StorageResolve.StorageResolveResponse.Result.CDN:
if track is not None:
return CdnFeedHelper.load_track(self.session, track, file,
resp, preload, halt_lister)
return CdnFeedHelper.load_episode(self.session, episode, file,
resp, preload, halt_lister)
return CdnFeedHelper.load_track(
self.session, track, file, resp, preload, halt_lister
)
return CdnFeedHelper.load_episode(
self.session, episode, file, resp, preload, halt_lister
)
elif resp.result == StorageResolve.StorageResolveResponse.Result.STORAGE:
if track is None:
# return StorageFeedHelper
@@ -106,11 +127,13 @@ class PlayableContentFeeder:
normalization_data: NormalizationData
metrics: PlayableContentFeeder.Metrics
def __init__(self, track_or_episode: typing.Union[Metadata.Track,
Metadata.Episode],
input_stream: GeneralAudioStream,
normalization_data: NormalizationData,
metrics: PlayableContentFeeder.Metrics):
def __init__(
self,
track_or_episode: typing.Union[Metadata.Track, Metadata.Episode],
input_stream: GeneralAudioStream,
normalization_data: NormalizationData,
metrics: PlayableContentFeeder.Metrics,
):
if type(track_or_episode) is Metadata.Track:
self.track = track_or_episode
self.episode = None
@@ -128,10 +151,10 @@ class PlayableContentFeeder:
preloaded_audio_key: bool
audio_key_time: int
def __init__(self, file_id: bytes, preloaded_audio_key: bool,
audio_key_time: int):
self.file_id = None if file_id is None else Utils.bytes_to_hex(
file_id)
def __init__(
self, file_id: bytes, preloaded_audio_key: bool, audio_key_time: int
):
self.file_id = None if file_id is None else Utils.bytes_to_hex(file_id)
self.preloaded_audio_key = preloaded_audio_key
self.audio_key_time = audio_key_time

View File

@@ -40,8 +40,7 @@ class CdnFeedHelper:
streamer = session.cdn().stream_file(file, key, url, halt_listener)
input_stream = streamer.stream()
normalization_data = NormalizationData.NormalizationData.read(
input_stream)
normalization_data = NormalizationData.NormalizationData.read(input_stream)
if input_stream.skip(0xA7) != 0xA7:
raise IOError("Couldn't skip 0xa7 bytes!")
return PlayableContentFeeder.PlayableContentFeeder.LoadedStream(
@@ -49,13 +48,13 @@ class CdnFeedHelper:
streamer,
normalization_data,
PlayableContentFeeder.PlayableContentFeeder.Metrics(
file.file_id, preload, -1 if preload else audio_key_time),
file.file_id, preload, -1 if preload else audio_key_time
),
)
@staticmethod
def load_episode_external(
session: Session, episode: Metadata.Episode,
halt_listener: HaltListener
session: Session, episode: Metadata.Episode, halt_listener: HaltListener
) -> PlayableContentFeeder.PlayableContentFeeder.LoadedStream:
resp = session.client().head(episode.external_url)
@@ -63,17 +62,18 @@ class CdnFeedHelper:
CdnFeedHelper._LOGGER.warning("Couldn't resolve redirect!")
url = resp.url
CdnFeedHelper._LOGGER.debug("Fetched external url for {}: {}".format(
Utils.Utils.bytes_to_hex(episode.gid), url))
CdnFeedHelper._LOGGER.debug(
"Fetched external url for {}: {}".format(
Utils.Utils.bytes_to_hex(episode.gid), url
)
)
streamer = session.cdn().stream_external_episode(
episode, url, halt_listener)
streamer = session.cdn().stream_external_episode(episode, url, halt_listener)
return PlayableContentFeeder.PlayableContentFeeder.LoadedStream(
episode,
streamer,
None,
PlayableContentFeeder.PlayableContentFeeder.Metrics(
None, False, -1),
PlayableContentFeeder.PlayableContentFeeder.Metrics(None, False, -1),
)
@staticmethod
@@ -102,5 +102,6 @@ class CdnFeedHelper:
streamer,
normalization_data,
PlayableContentFeeder.PlayableContentFeeder.Metrics(
file.file_id, False, audio_key_time),
file.file_id, False, audio_key_time
),
)

View File

@@ -37,9 +37,9 @@ class CdnManager:
def get_head(self, file_id: bytes):
resp = self._session.client().get(
self._session.get_user_attribute(
"head-files-url",
"https://heads-fa.spotify.com/head/{file_id}").replace(
"{file_id}", Utils.bytes_to_hex(file_id)))
"head-files-url", "https://heads-fa.spotify.com/head/{file_id}"
).replace("{file_id}", Utils.bytes_to_hex(file_id))
)
if resp.status_code != 200:
raise IOError("{}".format(resp.status_code))
@@ -50,9 +50,9 @@ class CdnManager:
return body
def stream_external_episode(self, episode: Metadata.Episode,
external_url: str,
halt_listener: HaltListener):
def stream_external_episode(
self, episode: Metadata.Episode, external_url: str, halt_listener: HaltListener
):
return CdnManager.Streamer(
self._session,
StreamId(episode),
@@ -84,7 +84,8 @@ class CdnManager:
resp = self._session.api().send(
"GET",
"/storage-resolve/files/audio/interactive/{}".format(
Utils.bytes_to_hex(file_id)),
Utils.bytes_to_hex(file_id)
),
None,
None,
)
@@ -100,11 +101,13 @@ class CdnManager:
proto.ParseFromString(body)
if proto.result == StorageResolve.StorageResolveResponse.Result.CDN:
url = random.choice(proto.cdnurl)
self._LOGGER.debug("Fetched CDN url for {}: {}".format(
Utils.bytes_to_hex(file_id), url))
self._LOGGER.debug(
"Fetched CDN url for {}: {}".format(Utils.bytes_to_hex(file_id), url)
)
return url
raise CdnManager.CdnException(
"Could not retrieve CDN url! result: {}".format(proto.result))
"Could not retrieve CDN url! result: {}".format(proto.result)
)
class CdnException(Exception):
pass
@@ -158,13 +161,14 @@ class CdnManager:
continue
if s[0][:i] == "exp":
expire_at = int(s[0][i + 1:])
expire_at = int(s[0][i + 1 :])
break
if expire_at is None:
self._expiration = -1
self._cdnManager._LOGGER.warning(
"Invalid __token__ in CDN url: {}".format(url))
"Invalid __token__ in CDN url: {}".format(url)
)
return
self._expiration = expire_at * 1000
@@ -174,8 +178,10 @@ class CdnManager:
except ValueError:
self._expiration = -1
self._cdnManager._LOGGER.warning(
"Couldn't extract expiration, invalid parameter in CDN url: "
.format(url))
"Couldn't extract expiration, invalid parameter in CDN url: ".format(
url
)
)
return
self._expiration = int(token_url.query[:i]) * 1000
@@ -184,8 +190,8 @@ class CdnManager:
self._expiration = -1
class Streamer(
GeneralAudioStream.GeneralAudioStream,
GeneralWritableStream.GeneralWritableStream,
GeneralAudioStream.GeneralAudioStream,
GeneralWritableStream.GeneralWritableStream,
):
_session: Session = None
_streamId: StreamId = None
@@ -218,39 +224,38 @@ class CdnManager:
self._cdnUrl = cdn_url
self._haltListener = halt_listener
resp = self.request(range_start=0,
range_end=ChannelManager.CHUNK_SIZE - 1)
resp = self.request(range_start=0, range_end=ChannelManager.CHUNK_SIZE - 1)
content_range = resp._headers.get("Content-Range")
if content_range is None:
raise IOError("Missing Content-Range header!")
split = Utils.split(content_range, "/")
self._size = int(split[1])
self._chunks = int(
math.ceil(self._size / ChannelManager.CHUNK_SIZE))
self._chunks = int(math.ceil(self._size / ChannelManager.CHUNK_SIZE))
first_chunk = resp._buffer
self._available = [False for _ in range(self._chunks)]
self._requested = [False for _ in range(self._chunks)]
self._buffer = [bytearray() for _ in range(self._chunks)]
self._internalStream = CdnManager.Streamer.InternalStream(
self, False)
self._internalStream = CdnManager.Streamer.InternalStream(self, False)
self._requested[0] = True
self.write_chunk(first_chunk, 0, False)
def write_chunk(self, chunk: bytes, chunk_index: int,
cached: bool) -> None:
def write_chunk(self, chunk: bytes, chunk_index: int, cached: bool) -> None:
if self._internalStream.is_closed():
return
self._session._LOGGER.debug(
"Chunk {}/{} completed, cached: {}, stream: {}".format(
chunk_index + 1, self._chunks, cached, self.describe()))
chunk_index + 1, self._chunks, cached, self.describe()
)
)
self._buffer[chunk_index] = self._audioDecrypt.decrypt_chunk(
chunk_index, chunk)
chunk_index, chunk
)
self._internalStream.notify_chunk_available(chunk_index)
def stream(self) -> AbsChunkedInputStream:
@@ -261,8 +266,7 @@ class CdnManager:
def describe(self) -> str:
if self._streamId.is_episode():
return "episode_gid: {}".format(
self._streamId.get_episode_gid())
return "episode_gid: {}".format(self._streamId.get_episode_gid())
return "file_id: {}".format(self._streamId.get_file_id())
def decrypt_time_ms(self) -> int:
@@ -272,10 +276,9 @@ class CdnManager:
resp = self.request(index)
self.write_chunk(resp._buffer, index, False)
def request(self,
chunk: int = None,
range_start: int = None,
range_end: int = None) -> CdnManager.InternalResponse:
def request(
self, chunk: int = None, range_start: int = None, range_end: int = None
) -> CdnManager.InternalResponse:
if chunk is None and range_start is None and range_end is None:
raise TypeError()
@@ -285,9 +288,7 @@ class CdnManager:
resp = self._session.client().get(
self._cdnUrl._url,
headers={
"Range": "bytes={}-{}".format(range_start, range_end)
},
headers={"Range": "bytes={}-{}".format(range_start, range_end)},
)
if resp.status_code != 206:
@@ -323,16 +324,21 @@ class CdnManager:
def request_chunk_from_stream(self, index: int) -> None:
self.streamer._executorService.submit(
lambda: self.streamer.request_chunk(index))
lambda: self.streamer.request_chunk(index)
)
def stream_read_halted(self, chunk: int, _time: int) -> None:
if self.streamer._haltListener is not None:
self.streamer._executorService.submit(
lambda: self.streamer._haltListener.stream_read_halted(
chunk, _time))
chunk, _time
)
)
def stream_read_resumed(self, chunk: int, _time: int) -> None:
if self.streamer._haltListener is not None:
self.streamer._executorService.submit(
lambda: self.streamer._haltListener.
stream_read_resumed(chunk, _time))
lambda: self.streamer._haltListener.stream_read_resumed(
chunk, _time
)
)

View File

@@ -11,28 +11,35 @@ class AudioQuality(enum.Enum):
@staticmethod
def get_quality(audio_format: AudioFile.Format) -> AudioQuality:
if audio_format == AudioFile.MP3_96 or \
audio_format == AudioFile.OGG_VORBIS_96 or \
audio_format == AudioFile.AAC_24_NORM:
if (
audio_format == AudioFile.MP3_96
or audio_format == AudioFile.OGG_VORBIS_96
or audio_format == AudioFile.AAC_24_NORM
):
return AudioQuality.NORMAL
if audio_format == AudioFile.MP3_160 or \
audio_format == AudioFile.MP3_160_ENC or \
audio_format == AudioFile.OGG_VORBIS_160 or \
audio_format == AudioFile.AAC_24:
if (
audio_format == AudioFile.MP3_160
or audio_format == AudioFile.MP3_160_ENC
or audio_format == AudioFile.OGG_VORBIS_160
or audio_format == AudioFile.AAC_24
):
return AudioQuality.HIGH
if audio_format == AudioFile.MP3_320 or \
audio_format == AudioFile.MP3_256 or \
audio_format == AudioFile.OGG_VORBIS_320 or \
audio_format == AudioFile.AAC_48:
if (
audio_format == AudioFile.MP3_320
or audio_format == AudioFile.MP3_256
or audio_format == AudioFile.OGG_VORBIS_320
or audio_format == AudioFile.AAC_48
):
return AudioQuality.VERY_HIGH
raise RuntimeError("Unknown format: {}".format(format))
def get_matches(self,
files: typing.List[AudioFile]) -> typing.List[AudioFile]:
def get_matches(self, files: typing.List[AudioFile]) -> typing.List[AudioFile]:
file_list = []
for file in files:
if hasattr(file, "format") and AudioQuality.get_quality(
file.format) == self:
if (
hasattr(file, "format")
and AudioQuality.get_quality(file.format) == self
):
file_list.append(file)
return file_list

View File

@@ -6,10 +6,26 @@ import time
class AesAudioDecrypt(AudioDecrypt):
audio_aes_iv = bytes([
0x72, 0xe0, 0x67, 0xfb, 0xdd, 0xcb, 0xcf, 0x77, 0xeb, 0xe8, 0xbc, 0x64,
0x3f, 0x63, 0x0d, 0x93
])
audio_aes_iv = bytes(
[
0x72,
0xE0,
0x67,
0xFB,
0xDD,
0xCB,
0xCF,
0x77,
0xEB,
0xE8,
0xBC,
0x64,
0x3F,
0x63,
0x0D,
0x93,
]
)
iv_int = int.from_bytes(audio_aes_iv, "big")
iv_diff = 0x100
cipher = None
@@ -25,17 +41,21 @@ class AesAudioDecrypt(AudioDecrypt):
iv = self.iv_int + int(ChannelManager.CHUNK_SIZE * chunk_index / 16)
start = time.time_ns()
for i in range(0, len(buffer), 4096):
cipher = AES.new(key=self.key,
mode=AES.MODE_CTR,
counter=Counter.new(128, initial_value=iv))
cipher = AES.new(
key=self.key,
mode=AES.MODE_CTR,
counter=Counter.new(128, initial_value=iv),
)
count = min(4096, len(buffer) - i)
decrypted_buffer = cipher.decrypt(buffer[i:i + count])
decrypted_buffer = cipher.decrypt(buffer[i : i + count])
new_buffer += decrypted_buffer
if count != len(decrypted_buffer):
raise RuntimeError(
"Couldn't process all data, actual: {}, expected: {}".
format(len(decrypted_buffer), count))
"Couldn't process all data, actual: {}, expected: {}".format(
len(decrypted_buffer), count
)
)
iv += self.iv_diff
@@ -45,5 +65,8 @@ class AesAudioDecrypt(AudioDecrypt):
return new_buffer
def decrypt_time_ms(self):
return 0 if self.decrypt_count == 0 else int(
(self.decrypt_total_time / self.decrypt_count) / 1000000)
return (
0
if self.decrypt_count == 0
else int((self.decrypt_total_time / self.decrypt_count) / 1000000)
)

View File

@@ -6,6 +6,5 @@ if typing.TYPE_CHECKING:
class AudioQualityPicker:
def get_file(self,
files: typing.List[Metadata.AudioFile]) -> Metadata.AudioFile:
def get_file(self, files: typing.List[Metadata.AudioFile]) -> Metadata.AudioFile:
pass

View File

@@ -19,7 +19,8 @@ class ChannelManager(Closeable, PacketsReceiver.PacketsReceiver):
_channels: typing.Dict[int, Channel] = {}
_seqHolder: int = 0
_seqHolderLock: threading.Condition = threading.Condition()
_executorService: concurrent.futures.ThreadPoolExecutor = concurrent.futures.ThreadPoolExecutor(
_executorService: concurrent.futures.ThreadPoolExecutor = (
concurrent.futures.ThreadPoolExecutor()
)
_session: Session = None
@@ -37,8 +38,8 @@ class ChannelManager(Closeable, PacketsReceiver.PacketsReceiver):
out.write_short(channel.chunkId)
out.write_int(0x00000000)
out.write_int(0x00000000)
out.write_int(0x00004e20)
out.write_int(0x00030d40)
out.write_int(0x00004E20)
out.write_int(0x00030D40)
out.write(file_id)
out.write_int(start)
out.write_int(end)
@@ -53,7 +54,9 @@ class ChannelManager(Closeable, PacketsReceiver.PacketsReceiver):
if channel is None:
self._LOGGER.warning(
"Couldn't find channel, id: {}, received: {}".format(
chunk_id, len(packet.payload)))
chunk_id, len(packet.payload)
)
)
return
channel._add_to_queue(payload)
@@ -63,14 +66,18 @@ class ChannelManager(Closeable, PacketsReceiver.PacketsReceiver):
if channel is None:
self._LOGGER.warning(
"Dropping channel error, id: {}, code: {}".format(
chunk_id, payload.read_short()))
chunk_id, payload.read_short()
)
)
return
channel.stream_error(payload.read_short())
else:
self._LOGGER.warning(
"Couldn't handle packet, cmd: {}, payload: {}".format(
packet.cmd, Utils.Utils.bytes_to_hex(packet.payload)))
packet.cmd, Utils.Utils.bytes_to_hex(packet.payload)
)
)
def close(self) -> None:
self._executorService.shutdown()
@@ -84,8 +91,9 @@ class ChannelManager(Closeable, PacketsReceiver.PacketsReceiver):
_buffer: BytesOutputStream = BytesOutputStream()
_header: bool = True
def __init__(self, channel_manager: ChannelManager, file: AudioFile,
chunk_index: int):
def __init__(
self, channel_manager: ChannelManager, file: AudioFile, chunk_index: int
):
self._channelManager = channel_manager
self._file = file
self._chunkIndex = chunk_index
@@ -94,17 +102,18 @@ class ChannelManager(Closeable, PacketsReceiver.PacketsReceiver):
self._channelManager._seqHolder += 1
self._channelManager._executorService.submit(
lambda: ChannelManager.Channel.Handler(self))
lambda: ChannelManager.Channel.Handler(self)
)
def _handle(self, payload: BytesInputStream) -> bool:
if len(payload.buffer) == 0:
if not self._header:
self._file.write_chunk(bytearray(payload.buffer),
self._chunkIndex, False)
self._file.write_chunk(
bytearray(payload.buffer), self._chunkIndex, False
)
return True
self._channelManager._LOGGER.debug(
"Received empty chunk, skipping.")
self._channelManager._LOGGER.debug("Received empty chunk, skipping.")
return False
if self._header:
@@ -115,8 +124,9 @@ class ChannelManager(Closeable, PacketsReceiver.PacketsReceiver):
break
header_id = payload.read_byte()
header_data = payload.read(length - 1)
self._file.write_header(int.from_bytes(header_id, "big"),
bytearray(header_data), False)
self._file.write_header(
int.from_bytes(header_id, "big"), bytearray(header_data), False
)
self._header = False
else:
self._buffer.write(payload.read(len(payload.buffer)))
@@ -137,11 +147,12 @@ class ChannelManager(Closeable, PacketsReceiver.PacketsReceiver):
def run(self) -> None:
self._channel._channelManager._LOGGER.debug(
"ChannelManager.Handler is starting")
"ChannelManager.Handler is starting"
)
with self._channel._q.all_tasks_done:
self._channel._channelManager._channels.pop(
self._channel.chunkId)
self._channel._channelManager._channels.pop(self._channel.chunkId)
self._channel._channelManager._LOGGER.debug(
"ChannelManager.Handler is shutting down")
"ChannelManager.Handler is shutting down"
)