Skip to content

Commit

Permalink
v1.4.12.42
Browse files Browse the repository at this point in the history
  • Loading branch information
sebdelsol committed Mar 22, 2024
1 parent 1f03a3c commit c4fe1a1
Show file tree
Hide file tree
Showing 27 changed files with 367 additions and 296 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ You'll find them in the app folder:

# Build
[![version](https://custom-icon-badges.demolab.com/badge/Build%201.4.12.42-informational?logo=github)](/build_config.py#L27)
[![Sloc](https://custom-icon-badges.demolab.com/badge/Sloc%208.4k-informational?logo=file-code)](https://api.codetabs.com/v1/loc/?github=sebdelsol/sfvip-all)
[![Sloc](https://custom-icon-badges.demolab.com/badge/Sloc%208.5k-informational?logo=file-code)](https://api.codetabs.com/v1/loc/?github=sebdelsol/sfvip-all)
[![Ruff](https://custom-icon-badges.demolab.com/badge/Ruff-informational?logo=ruff-color)](https://docs.astral.sh/ruff/)
[![Python](https://custom-icon-badges.demolab.com/badge/Python%203.11.8-linen?logo=python-color)](https://www.python.org/downloads/release/python-3118/)
[![mitmproxy](https://custom-icon-badges.demolab.com/badge/Mitmproxy%2010.2.4-linen?logo=mitmproxy-black)](https://mitmproxy.org/)
[![Nsis](https://custom-icon-badges.demolab.com/badge/Nsis%203.09-linen?logo=nsis-color)](https://nsis.sourceforge.io/Download)
[![Nuitka](https://custom-icon-badges.demolab.com/badge/Nuitka%202.1.2-linen?logo=nuitka)](https://nuitka.net/)
[![Nuitka](https://custom-icon-badges.demolab.com/badge/Nuitka%202.1.3-linen?logo=nuitka)](https://nuitka.net/)
[![PyInstaller](https://custom-icon-badges.demolab.com/badge/PyInstaller%206.5.0-linen?logo=pyinstaller-windowed)](https://pyinstaller.org/en/stable/)

* [***NSIS***](https://nsis.sourceforge.io/Download) will be automatically installed if missing.
Expand Down
6 changes: 6 additions & 0 deletions build/changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 1.4.12.42
* Fix Minor bugs.
* Better translations.
* Better MAC accounts all categories progress.
* Fix suggestion to restart when adding or modifying a User.

## 1.4.12.41
* Faster MAC accounts cache for all categories.
* MAC accounts cache handles partial update.
Expand Down
2 changes: 1 addition & 1 deletion dev/tools/nsis/installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def get_all_languages(loc: CfgLOC, app_name: str) -> Iterator[dict[str, str]]:
retry=loc.Retry,
upper=lang.upper(),
name=lang.capitalize(),
already_running=loc.AlreadyRunning % app_name,
already_running=loc.AlreadyRunning.format(name=app_name),
)


Expand Down
63 changes: 48 additions & 15 deletions dev/tools/translator.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import json
import re
import sys
from pathlib import Path
from typing import Optional, Sequence
from typing import NamedTuple, Optional, Self, Sequence

from deep_translator import DeeplTranslator, GoogleTranslator
from deep_translator.exceptions import ServerException
Expand All @@ -19,12 +20,43 @@ class Args(Tap):
language: str = "" # language to update, all by default


class Marker(NamedTuple):
original: str
replacement: str
count: int


class MarkedText:
def __init__(self, text: str) -> None:
self.text = text
self.markers: list[Marker] = []

def prepare(self) -> Self:
all_markers: list[str] = re.findall(r"[{\[].*?[}\]]", self.text)
for i, marker in enumerate(all_markers):
replacement = f"{i:03}"
original = self.clean(marker) if marker.startswith("[") else marker
self.text = self.text.replace(marker, replacement)
self.markers.append(Marker(original, replacement, self.text.count(replacement)))
return self

def finalize(self, translation: str) -> Optional[str]:
for marker in self.markers:
if marker.count != translation.count(marker.replacement):
return None
translation = translation.replace(marker.replacement, marker.original).strip()
return translation

@staticmethod
def clean(text: str) -> str:
return text.replace("[", "").replace("]", "")


deepl_kwargs = dict(api_key=DEEPL_KEY, use_free_api=True)
deepl_supported_langs = DeeplTranslator(**deepl_kwargs).get_supported_languages() # type: ignore


class Translator:
marker_replace = "%s", "000"
separator = "\n" # DO NOT use it in the texts (best separator found)

def __init__(self, source: str, target: str) -> None:
Expand All @@ -41,24 +73,24 @@ def name(self) -> str:
def translate(self, *texts: str) -> Optional[list[str]]:
"""
translate all texts as a bundle to keep its context
replace %s with our own non translated marker
keep {} and []
"""
marker, marker_replacement = Translator.marker_replace
# save where markers are
is_markers = [marker in text for text in texts]
bundle = Translator.separator.join(texts).replace(marker, marker_replacement)
marked_texts = [MarkedText(text).prepare() for text in texts]
bundle = Translator.separator.join(marked.text for marked in marked_texts)
try:
translation: str = self.translator.translate(bundle)
translated_bundle: str = self.translator.translate(bundle)
except ServerException:
return None
if translation:
translation = translation.replace(marker_replacement, marker)
translated_texts = (text.strip() for text in translation.split(Translator.separator))
translated_texts = list(filter(None, translated_texts))
if len(translated_texts) == len(texts):
if translated_bundle:
translations = translated_bundle.split(Translator.separator)
if len(translations) == len(marked_texts):
# check markers are where they should be
if all((marker in text) == is_marker for text, is_marker in zip(translated_texts, is_markers)):
return translated_texts
finalized_translations = []
for translation, marked in zip(translations, marked_texts):
if not (translation := marked.finalize(translation)):
return None
finalized_translations.append(translation)
return finalized_translations
return None


Expand All @@ -81,6 +113,7 @@ def translate(texts: CfgTexts, all_languages: Sequence[str], translation_dir: Cf
if args.force or is_newer_texts.than(json_file):
texts_values = texts.as_dict().values()
if target_language == texts.language:
texts_values = [MarkedText.clean(text) for text in texts_values]
using = "already translated"
else:
translator = Translator(texts.language, target_language)
Expand Down
2 changes: 1 addition & 1 deletion src/mitm/addon/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def __init__(self, accounts_urls: set[str]) -> None:

async def __call__(self, flow: http.HTTPFlow) -> Optional[APItype]:
request = flow.request
if api := ApiRequest._api.get(request.path_components[0]):
if (components := request.path_components) and (api := ApiRequest._api.get(components[0])):
return api
return APItype.M3U if request.url in self.accounts_urls else None

Expand Down
6 changes: 3 additions & 3 deletions src/mitm/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,8 @@ class AllCached(NamedTuple):

def title(self, loaded: MacCacheLoad) -> str:
if missing_percent := loaded.missing_percent:
missing_str = f"{max(1, min(round(missing_percent * 100), 99))}%"
missing_str = f"⚠️ {self.missing % missing_str}"
percent = max(1, min(round(missing_percent * 100), 99))
missing_str = f"⚠️ {self.missing.format(percent=percent)}"
else:
missing_str = f"✔ {self.complete}"
return (
Expand All @@ -237,7 +237,7 @@ def _days_ago(self, path: Path) -> str:
case 1:
return self.one_day
case _:
return self.several_days % days
return self.several_days.format(days=days)


class MACCache(CacheCleaner):
Expand Down
33 changes: 19 additions & 14 deletions src/sfvip/accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,16 @@ def load(self) -> None:
self._accessed_by_me = self._atime

def save(self) -> None:
self._database.open_and_do("w", self.accounts.dump)
self._accessed_by_me = self._atime
self._shared_self_modified.set()
with self.lock:
self._database.open_and_do("w", self.accounts.dump)
self._accessed_by_me = self._atime
self._shared_self_modified.set()

@property
def shared_self_modified_time(self):
# time when any instance have internally modified the database
return self._shared_self_modified.time
with self.lock:
return self._shared_self_modified.time


class AccountsProxies:
Expand Down Expand Up @@ -144,10 +146,14 @@ def _set_proxies(self, proxies: dict[str, str], msg: str) -> None:
logger.info("%s user %s proxy to '%s'", msg.capitalize(), account.Name, account.HttpProxy)
self._database.save()

def _infos(self, proxies: dict[str, str]) -> Sequence[Info]:
def _infos(self, proxies_to_restore: dict[str, str]) -> Sequence[Info]:
self._database.load()
return tuple(
Info(account.Name, proxies.get(account.HttpProxy, ""), account.HttpProxy)
Info(
account.Name,
account.HttpProxy if proxies_to_restore.get(account.HttpProxy) is not None else "",
proxies_to_restore.get(account.HttpProxy, account.HttpProxy),
)
for account in self._accounts_to_set
)

Expand All @@ -156,24 +162,23 @@ def set(self, proxies: dict[str, str]) -> Iterator[Callable[[Callable[[int], Non
"""set proxies, infos and provide a method to restore the proxies"""

def set_infos(player_relaunch: Optional[Callable[[int], None]] = None) -> None:
infos = self._infos(proxies)
infos = self._infos(shared_proxies_to_restore.all)
self._ui.set_infos(infos, player_relaunch)

def set_proxies() -> None:
proxies_to_restore.add({v: k for k, v in proxies.items()})
shared_proxies_to_restore.add({v: k for k, v in proxies.items()})
self._set_proxies(proxies, "set")

def restore_proxies() -> None:
self._set_proxies(proxies_to_restore.all, "restore")
self._set_proxies(shared_proxies_to_restore.all, "restore")

# TODO new account !!!!!!!!!!!!!!
def restore_after_being_read(player_relaunch: Callable[[int], None]) -> None:
def on_modified(last_modified: float) -> None:
# to prevent recursion check it occured after any modification done by any instance
if last_modified > self._database.shared_self_modified_time:
logger.info("Accounts proxies file has been externaly modified")
restore_proxies()
set_infos(player_relaunch)
restore_proxies()

self._database.wait_being_read()
restore_proxies()
Expand All @@ -182,12 +187,12 @@ def on_modified(last_modified: float) -> None:
self._database.watcher.add_callback(on_modified)
self._database.watcher.start()

proxies_to_restore = SharedProxiesToRestore(self._app_roaming)
set_infos()
shared_proxies_to_restore = SharedProxiesToRestore(self._app_roaming)
set_proxies()
set_infos()
try:
yield restore_after_being_read
finally:
self._database.watcher.stop()
restore_proxies()
proxies_to_restore.clean()
shared_proxies_to_restore.clean()
2 changes: 1 addition & 1 deletion src/sfvip/app_updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def ask_and_install() -> bool:

update_exe = self._update_exe(update)
title = f"{LOC.Install} {self._app_info.name}"
ask_win = AskWindow(title, LOC.RestartInstall % f"v{update.version}", LOC.Restart, LOC.Cancel)
ask_win = AskWindow(title, LOC.RestartInstall.format(name=f"v{update.version}"), LOC.Restart, LOC.Cancel)
return bool(ask_win.run_in_thread(ask_and_install, *exceptions))


Expand Down
7 changes: 6 additions & 1 deletion src/sfvip/epg.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ def __init__(self, ui: UI) -> None:

def show_channel(self, channel: ShowChannel) -> None:
if channel.show:
self.ui.hover_message.show(LOC.EPGFoundConfidence % (channel.name or "", f"{channel.confidence}%"))
self.ui.hover_message.show(
LOC.EPGFoundConfidence.format(
channel=channel.name or "",
confidence=f"{channel.confidence}%",
)
)
else:
self.ui.hover_message.hide()

Expand Down
11 changes: 8 additions & 3 deletions src/sfvip/player/find_exe.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,16 @@ def _find(self) -> FoundExe:
if found := self._find_from(self._from_config, self._from_registry):
if isinstance(found, FoundExe):
return found
self._ui.showinfo(LOC.PlayerTooOld % (PlayerExe._name.title(), PlayerExe._min_version))
self._ui.showinfo(
LOC.PlayerTooOld.format(
name=PlayerExe._name.title(),
version=PlayerExe._min_version,
)
)
if found := self._find_from(self._from_file_or_download):
if isinstance(found, FoundExe):
return found
raise PlayerNotFoundError(LOC.NotFound % PlayerExe._name.title())
raise PlayerNotFoundError(LOC.NotFound.format(name=PlayerExe._name.title()))

def _from_config(self) -> Iterator[str]:
if exe := self._app_config.Player.exe:
Expand All @@ -137,7 +142,7 @@ def _from_registry() -> Iterator[str]:
def _from_file_or_download(self) -> Iterator[str]:
while True:
ok = self._ui.ask(
f"{LOC.NotFound % PlayerExe._name.title()}\n{LOC.SearchOrDownload}",
f"{LOC.NotFound.format(name=PlayerExe._name.title())}\n{LOC.SearchOrDownload}",
LOC.Search,
LOC.Download,
)
Expand Down
4 changes: 3 additions & 1 deletion src/sfvip/player/libmpv_dll.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,9 @@ def _ask_restart() -> bool:
ask_win.wait_window()
return bool(ask_win.ok)

ask_win = AskWindow(f"{LOC.Install} Libmpv", LOC.RestartInstall % "Libmpv", LOC.Restart, LOC.Cancel)
ask_win = AskWindow(
f"{LOC.Install} Libmpv", LOC.RestartInstall.format(name="Libmpv"), LOC.Restart, LOC.Cancel
)
return bool(ask_win.run_in_thread(_ask_restart, *exceptions))


Expand Down
4 changes: 2 additions & 2 deletions src/sfvip/player/updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def _sanitize(text: str) -> str:
def __str__(self) -> str:
return "\n\n".join(
(
LOC.ChangeLog % "Sfvip Player",
LOC.ChangeLog.format(name="Sfvip Player"),
*(
f"{PlayerChangelogs._prefix}{version}:\n" f"{PlayerChangelogs._tab}- {self._sanitize(text)}"
for i, (version, text) in enumerate(self._changelogs.items())
Expand Down Expand Up @@ -151,7 +151,7 @@ def _ask() -> bool:
ask_win.wait_window()
return bool(ask_win.ok)

ask_win = AskWindow(f"{LOC.Install} {name}", message % name, ok, LOC.Cancel)
ask_win = AskWindow(f"{LOC.Install} {name}", message.format(name=name), ok, LOC.Cancel)
return bool(ask_win.run_in_thread(_ask))

def ask_install(self, version: Version) -> bool:
Expand Down
10 changes: 5 additions & 5 deletions src/sfvip/ui/infos.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def _get_infos_headers(app_name: str) -> Sequence[Style]:
return (
_InfoStyle.name(LOC.User).bold,
_InfoStyle.blank,
_InfoStyle.proxy(LOC.Proxy % app_name).bigger(2).bold,
_InfoStyle.proxy(LOC.Proxy.format(name=app_name)).bigger(2).bold,
_InfoStyle.blank,
_InfoStyle.upstream(LOC.UserProxy).bigger(2).bold,
)
Expand Down Expand Up @@ -125,7 +125,7 @@ def _get_app_version(app_info: AppInfo) -> Style:

def _app_version_tooltip(app_info: AppInfo) -> Style:
n_logs_showed = app_info.config.App.n_logs_showed
lines = [LOC.ChangeLog % app_info.name]
lines = [LOC.ChangeLog.format(name=app_info.name)]
prefix = "• v"
tab = " " * len(prefix)
n_logs = 0
Expand Down Expand Up @@ -156,7 +156,7 @@ def _app_version_tooltip(app_info: AppInfo) -> Style:

def _get_app_warn(app_info: AppInfo) -> Style:
if app_info.bitness != app_info.os_bitness:
warn = LOC.ShouldUseVersion % app_info.os_bitness
warn = LOC.ShouldUseVersion.format(version=app_info.os_bitness)
return _InfoStyle.app_warn(warn).red
warn = LOC.SearchWholeCatalog
return _InfoStyle.app_warn(warn).lime_green
Expand Down Expand Up @@ -245,7 +245,7 @@ def _get_epg_prefer_label() -> Style:


def _epg_url_tooltip() -> Style:
return _InfoStyle.app(LOC.EPGUrlTip % ("xml", "xml.gz")).grey.no_truncate
return _InfoStyle.app(LOC.EPGUrlTip).grey.no_truncate


def _epg_libmpv_tooltip() -> Style:
Expand All @@ -258,7 +258,7 @@ def _epg_prefer_tooltip() -> Style:


def _proxy_tooltip(app_info: AppInfo) -> Style:
return _InfoStyle.app(LOC.ProxyTip % app_info.name).grey.no_truncate
return _InfoStyle.app(LOC.ProxyTip.format(name=app_info.name)).grey.no_truncate


def _user_proxy_tooltip() -> Style:
Expand Down
Loading

0 comments on commit c4fe1a1

Please sign in to comment.