<commit_before>import os
import math
import json
import struct
import socket
from typing import Optional, Dict
from PyQt5 import QtWidgets
from panel.widgets.widget import Widget


def _format_time(seconds):
    seconds = math.floor(float(seconds))
    return '%02d:%02d' % (seconds // 60, seconds % 60)


class MpvmdWidget(Widget):
    delay = 0.5

    def __init__(self, app, main_window):
        super().__init__(app, main_window)
        self._socket = None
        self._info = None

        self._status_icon_label = QtWidgets.QLabel()
        self._song_label = QtWidgets.QLabel()
        self._shuffle_icon_label = QtWidgets.QLabel()

        self._status_icon_label.mouseReleaseEvent = self.play_pause_clicked
        self._song_label.mouseReleaseEvent = self.play_pause_clicked
        self._shuffle_icon_label.mouseReleaseEvent = self.shuffle_clicked
        self._status_icon_label.wheelEvent = self.prev_or_next_track
        self._song_label.wheelEvent = self.prev_or_next_track

        container = QtWidgets.QWidget()
        container.setLayout(QtWidgets.QHBoxLayout(margin=0, spacing=6))
        container.layout().addWidget(self._status_icon_label)
        container.layout().addWidget(self._song_label)
        container.layout().addWidget(self._shuffle_icon_label)
        main_window[0].layout().addWidget(container)

    def play_pause_clicked(self, _event):
        with self.exception_guard():
            if self._info['paused']:
                self._send({'msg': 'play'})
            else:
                self._send({'msg': 'pause'})
            self._recv()
            self.refresh()
            self.render()

    def prev_or_next_track(self, event):
        with self.exception_guard():
            if event.angleDelta().y() > 0:
                self._send({'msg': 'playlist-next'})
            else:
                self._send({'msg': 'playlist-prev'})
            self._recv()
            self.refresh()
            self.render()

    def shuffle_clicked(self, _event):
        with self.exception_guard():
            self._send({'msg': 'random', 'random': not self._info['random']})
            self._recv()
            self.refresh()
            self.render()

    def refresh_impl(self):
        if not self._socket:
            self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            try:
                self._socket.connect(('localhost', 36934))
            except ConnectionRefusedError:
                self.delay = min(60, self.delay + 1)
                raise

        try:
            self._send({'msg': 'info'})
            self._info = self._recv()
            self.delay = 0.5
        except (BrokenPipeError, ValueError):
            self._socket = None
            self.delay = min(60, self.delay + 1)
            raise

    def render_impl(self):
        if not self._info:
            return

        if self._info['paused']:
            self.set_icon(self._status_icon_label, 'pause')
        else:
            self.set_icon(self._status_icon_label, 'play')

        metadata = {k.lower(): v for k, v in self._info['metadata'].items()}

        title = metadata.get('title')
        artist = metadata.get('artist')
        file = os.path.basename(self._info['path'] or '')
        elapsed = self._info['time-pos']
        duration = self._info['duration']

        text = ''
        if title:
            if artist:
                text = artist + ' - ' + title
            else:
                text = title
        else:
            text = file

        if elapsed and duration:
            text += ' %s / %s' % (
                _format_time(elapsed), _format_time(duration))

        self._song_label.setText(text)

        shuffle = self._info['random']
        if self._shuffle_icon_label.property('active') != shuffle:
            self._shuffle_icon_label.setProperty('active', shuffle)
            if shuffle:
                self.set_icon(self._shuffle_icon_label, 'shuffle-on')
            else:
                self.set_icon(self._shuffle_icon_label, 'shuffle-off')

    def _recv(self) -> Optional[Dict]:
        data_size_raw = self._socket.recv(4)
        if not data_size_raw:
            return None
        data_size = struct.unpack('<I', data_size_raw)[0]
        data = self._socket.recv(data_size)
        return json.loads(data.decode('utf-8'))

    def _send(self, message: Dict):
        data = json.dumps(message).encode('utf-8')
        self._socket.send(struct.pack('<I', len(data)))
        self._socket.send(data)
<commit_msg>mod/panel: add timeout to socket operations<commit_after>import os
import math
import json
import struct
import socket
from typing import Optional, Dict
from PyQt5 import QtWidgets
from panel.widgets.widget import Widget


def _format_time(seconds):
    seconds = math.floor(float(seconds))
    return '%02d:%02d' % (seconds // 60, seconds % 60)


class MpvmdWidget(Widget):
    delay = 0.5

    def __init__(self, app, main_window):
        super().__init__(app, main_window)
        self._socket = None
        self._info = None

        self._status_icon_label = QtWidgets.QLabel()
        self._song_label = QtWidgets.QLabel()
        self._shuffle_icon_label = QtWidgets.QLabel()

        self._status_icon_label.mouseReleaseEvent = self.play_pause_clicked
        self._song_label.mouseReleaseEvent = self.play_pause_clicked
        self._shuffle_icon_label.mouseReleaseEvent = self.shuffle_clicked
        self._status_icon_label.wheelEvent = self.prev_or_next_track
        self._song_label.wheelEvent = self.prev_or_next_track

        container = QtWidgets.QWidget()
        container.setLayout(QtWidgets.QHBoxLayout(margin=0, spacing=6))
        container.layout().addWidget(self._status_icon_label)
        container.layout().addWidget(self._song_label)
        container.layout().addWidget(self._shuffle_icon_label)
        main_window[0].layout().addWidget(container)

    def play_pause_clicked(self, _event):
        with self.exception_guard():
            if self._info['paused']:
                self._send({'msg': 'play'})
            else:
                self._send({'msg': 'pause'})
            self._recv()
            self.refresh()
            self.render()

    def prev_or_next_track(self, event):
        with self.exception_guard():
            if event.angleDelta().y() > 0:
                self._send({'msg': 'playlist-next'})
            else:
                self._send({'msg': 'playlist-prev'})
            self._recv()
            self.refresh()
            self.render()

    def shuffle_clicked(self, _event):
        with self.exception_guard():
            self._send({'msg': 'random', 'random': not self._info['random']})
            self._recv()
            self.refresh()
            self.render()

    def refresh_impl(self):
        if not self._socket:
            self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            try:
                self._socket.connect(('localhost', 36934))
                self._socket.settimeout(3)
            except ConnectionRefusedError:
                self.delay = min(60, self.delay + 1)
                raise

        try:
            self._send({'msg': 'info'})
            self._info = self._recv()
            self.delay = 0.5
        except (BrokenPipeError, ValueError):
            self._socket = None
            self.delay = min(60, self.delay + 1)
            raise

    def render_impl(self):
        if not self._info:
            return

        if self._info['paused']:
            self.set_icon(self._status_icon_label, 'pause')
        else:
            self.set_icon(self._status_icon_label, 'play')

        metadata = {k.lower(): v for k, v in self._info['metadata'].items()}

        title = metadata.get('title')
        artist = metadata.get('artist')
        file = os.path.basename(self._info['path'] or '')
        elapsed = self._info['time-pos']
        duration = self._info['duration']

        text = ''
        if title:
            if artist:
                text = artist + ' - ' + title
            else:
                text = title
        else:
            text = file

        if elapsed and duration:
            text += ' %s / %s' % (
                _format_time(elapsed), _format_time(duration))

        self._song_label.setText(text)

        shuffle = self._info['random']
        if self._shuffle_icon_label.property('active') != shuffle:
            self._shuffle_icon_label.setProperty('active', shuffle)
            if shuffle:
                self.set_icon(self._shuffle_icon_label, 'shuffle-on')
            else:
                self.set_icon(self._shuffle_icon_label, 'shuffle-off')

    def _recv(self) -> Optional[Dict]:
        data_size_raw = self._socket.recv(4)
        if not data_size_raw:
            return None
        data_size = struct.unpack('<I', data_size_raw)[0]
        data = self._socket.recv(data_size)
        return json.loads(data.decode('utf-8'))

    def _send(self, message: Dict):
        data = json.dumps(message).encode('utf-8')
        self._socket.send(struct.pack('<I', len(data)))
        self._socket.send(data)

{'fraction_non_alphanumeric': 0.1184455391351943, 'fraction_numerical': 0.0071154898741105635, 'mean_word_length': 2.0678307588985896, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7226911', 'n_tokens_mistral': 2765, 'n_tokens_neox': 2643, 'n_words': 498}