# -*- coding: utf-8 -*- # # The MIT License (MIT) # # Copyright (c) 2018-2021 Dmitriy Yefremov # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # Author: Dmitriy Yefremov # """ Receiver control module via HTTP API. """ import os from datetime import datetime from enum import Enum from urllib.parse import quote from gi.repository import GLib from .dialogs import show_dialog, DialogType, get_message, get_builder from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, Column from ..commons import run_task, run_with_delay, log, run_idle from ..connections import HttpAPI, UtfFTP from ..eparser.ecommons import BqServiceType class ControlBox(Gtk.HBox): _TIME_STR = "%Y-%m-%d %H:%M" class Tool(Enum): """ The currently displayed tool. """ REMOTE = "control" EPG = "epg" TIMERS = "timers" TIMER = "timer" RECORDINGS = "recordings" class EpgRow(Gtk.ListBoxRow): def __init__(self, event: dict, **properties): super().__init__(**properties) self._event_data = event h_box = Gtk.HBox() h_box.set_orientation(Gtk.Orientation.VERTICAL) self._title = event.get("e2eventtitle", "") title_label = Gtk.Label(self._title) self._desc = event.get("e2eventdescription", "") description = Gtk.Label() description.set_markup("{}".format(self._desc)) description.set_line_wrap(True) description.set_max_width_chars(25) start = int(event.get("e2eventstart", "0")) start_time = datetime.fromtimestamp(start) end_time = datetime.fromtimestamp(start + int(event.get("e2eventduration", "0"))) time_label = Gtk.Label() time_label.set_margin_top(5) self._time_header = "{} - {}".format(start_time.strftime("%A, %H:%M"), end_time.strftime("%H:%M")) time_label.set_markup("{}".format(self._time_header)) h_box.add(time_label) h_box.add(title_label) h_box.add(description) sep = Gtk.Separator() sep.set_margin_top(5) h_box.add(sep) h_box.set_spacing(5) self.add(h_box) self.show_all() @property def event_data(self): return self._event_data or {} @property def title(self): return self._title or "" @property def desc(self): return self._desc or "" @property def time_header(self): return self._time_header or "" class TimerRow(Gtk.ListBoxRow): _UI_PATH = UI_RESOURCES_PATH + "timer_row.glade" def __init__(self, timer, **properties): super().__init__(**properties) self._timer = timer builder = get_builder(self._UI_PATH, None, use_str=True) row_box = builder.get_object("timer_row_box") name_label = builder.get_object("timer_name_label") description_label = builder.get_object("timer_description_label") service_name_label = builder.get_object("timer_service_name_label") time_label = builder.get_object("timer_time_label") name_label.set_text(timer.get("e2name", "") or "") description_label.set_text(timer.get("e2description", "") or "") service_name_label.set_text(timer.get("e2servicename", "") or "") # Time start_time = datetime.fromtimestamp(int(timer.get("e2timebegin", "0"))) end_time = datetime.fromtimestamp(int(timer.get("e2timeend", "0"))) time_label.set_text("{} - {}".format(start_time.strftime("%A, %H:%M"), end_time.strftime("%H:%M"))) self.add(row_box) self.show() @property def timer(self): return self._timer class TimerAction(Enum): ADD = 0 EVENT = 1 CHANGE = 2 class RecordingsRow(Gtk.ListBoxRow): def __init__(self, movie: dict, **properties): super().__init__(**properties) self._movie = movie h_box = Gtk.HBox() h_box.set_orientation(Gtk.Orientation.VERTICAL) self._service = movie.get("e2servicename") service_label = Gtk.Label() service_label.set_markup("{}".format(self._service)) self._title = movie.get("e2title", "") title_label = Gtk.Label(self._title) self._desc = movie.get("e2description", "") description = Gtk.Label() description.set_markup("{}".format(self._desc)) description.set_line_wrap(True) description.set_max_width_chars(25) start_time = datetime.fromtimestamp(int(movie.get("e2time", "0"))) start_time_label = Gtk.Label() start_time_label.set_margin_top(5) start_time_label.set_markup("{}".format(start_time.strftime("%A, %H:%M"))) time_label = Gtk.Label() time_label.set_margin_top(5) time_label.set_markup("{}".format(movie.get("e2length", "0"))) info_box = Gtk.HBox() info_box.set_orientation(Gtk.Orientation.HORIZONTAL) info_box.set_spacing(10) info_box.pack_start(start_time_label, False, True, 5) info_box.pack_end(time_label, False, True, 5) h_box.add(service_label) h_box.add(title_label) h_box.add(description) h_box.add(info_box) sep = Gtk.Separator() sep.set_margin_top(5) h_box.add(sep) h_box.set_spacing(5) self.set_tooltip_text(movie.get("e2filename", "")) self.add(h_box) self.show_all() @property def movie(self): return self._movie @property def service(self): return self._service or "" @property def title(self): return self._title or "" @property def desc(self): return self._desc or "" @property def file(self): return self._movie.get("e2filename", "") def __init__(self, app, http_api, settings, *args, **kwargs): super().__init__(*args, **kwargs) self._http_api = http_api self._settings = settings self._update_epg = False self._app = app self._last_tool = self.Tool.REMOTE self._timer_action = self.TimerAction.ADD self._current_timer = {} handlers = {"on_visible_tool": self.on_visible_tool, "on_volume_changed": self.on_volume_changed, "on_epg_press": self.on_epg_press, "on_epg_filter_changed": self.on_epg_filter_changed, "on_timers_press": self.on_timers_press, "on_timers_drag_data_received": self.on_timers_drag_data_received, "on_recordings_press": self.on_recordings_press, "on_recording_filter_changed": self.on_recording_filter_changed, "on_recordings_dir_changed": self.on_recordings_dir_changed} builder = get_builder(UI_RESOURCES_PATH + "control.glade", handlers) self.add(builder.get_object("control_box")) self._stack = builder.get_object("stack") self._screenshot_image = builder.get_object("screenshot_image") self._screenshot_button_box = builder.get_object("screenshot_button_box") self._screenshot_check_button = builder.get_object("screenshot_check_button") self._screenshot_check_button.bind_property("active", self._screenshot_image, "visible") self._snr_value_label = builder.get_object("snr_value_label") self._ber_value_label = builder.get_object("ber_value_label") self._agc_value_label = builder.get_object("agc_value_label") self._volume_button = builder.get_object("volume_button") self._epg_list_box = builder.get_object("epg_list_box") self._epg_list_box.set_filter_func(self.epg_filter_function) self._epg_filter_entry = builder.get_object("epg_filter_entry") self._timers_list_box = builder.get_object("timers_list_box") # Timers self._timer_remove_button = builder.get_object("timer_remove_button") self._timer_remove_button.bind_property("visible", builder.get_object("timer_edit_button"), "visible") # Timer self._timer_name_entry = builder.get_object("timer_name_entry") self._timer_desc_entry = builder.get_object("timer_desc_entry") self._timer_service_entry = builder.get_object("timer_service_entry") self._timer_service_ref_entry = builder.get_object("timer_service_ref_entry") self._timer_event_id_entry = builder.get_object("timer_event_id_entry") self._timer_begins_entry = builder.get_object("timer_begins_entry") self._timer_ends_entry = builder.get_object("timer_ends_entry") self._timer_begins_calendar = builder.get_object("timer_begins_calendar") self._timer_begins_hr_button = builder.get_object("timer_begins_hr_button") self._timer_begins_min_button = builder.get_object("timer_begins_min_button") self._timer_ends_calendar = builder.get_object("timer_ends_calendar") self._timer_ends_hr_button = builder.get_object("timer_ends_hr_button") self._timer_ends_min_button = builder.get_object("timer_ends_min_button") self._timer_enabled_switch = builder.get_object("timer_enabled_switch") self._timer_action_combo_box = builder.get_object("timer_action_combo_box") self._timer_after_combo_box = builder.get_object("timer_after_combo_box") self._timer_mo_check_button = builder.get_object("timer_mo_check_button") self._timer_tu_check_button = builder.get_object("timer_tu_check_button") self._timer_we_check_button = builder.get_object("timer_we_check_button") self._timer_th_check_button = builder.get_object("timer_th_check_button") self._timer_fr_check_button = builder.get_object("timer_fr_check_button") self._timer_sa_check_button = builder.get_object("timer_sa_check_button") self._timer_su_check_button = builder.get_object("timer_su_check_button") self._timer_location_switch = builder.get_object("timer_location_switch") self._timer_location_entry = builder.get_object("timer_location_entry") self._timer_location_switch.bind_property("active", self._timer_location_entry, "sensitive") # Disable DnD for timer entries. self._timer_name_entry.drag_dest_unset() self._timer_desc_entry.drag_dest_unset() self._timer_service_entry.drag_dest_unset() # DnD initialization for the timer list. self._timers_list_box.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.DEFAULT | Gdk.DragAction.COPY) self._timers_list_box.drag_dest_add_text_targets() # Recordings. self._recordings_list_box = builder.get_object("recordings_list_box") self._recordings_list_box.set_filter_func(self.recording_filter_function) self._recordings_filter_entry = builder.get_object("recordings_filter_entry") self._recordings_dir_box = builder.get_object("recordings_dir_box") self.init_actions(app) self.connect("hide", self.on_hide) self.show() def init_actions(self, app): # Remote controller actions app.set_action("on_up", lambda a, v: self.on_remote_action(HttpAPI.Remote.UP)) app.set_action("on_down", lambda a, v: self.on_remote_action(HttpAPI.Remote.DOWN)) app.set_action("on_left", lambda a, v: self.on_remote_action(HttpAPI.Remote.LEFT)) app.set_action("on_right", lambda a, v: self.on_remote_action(HttpAPI.Remote.RIGHT)) app.set_action("on_ok", lambda a, v: self.on_remote_action(HttpAPI.Remote.OK)) app.set_action("on_menu", lambda a, v: self.on_remote_action(HttpAPI.Remote.MENU)) app.set_action("on_exit", lambda a, v: self.on_remote_action(HttpAPI.Remote.EXIT)) app.set_action("on_red", lambda a, v: self.on_remote_action(HttpAPI.Remote.RED)) app.set_action("on_green", lambda a, v: self.on_remote_action(HttpAPI.Remote.GREEN)) app.set_action("on_yellow", lambda a, v: self.on_remote_action(HttpAPI.Remote.YELLOW)) app.set_action("on_blue", lambda a, v: self.on_remote_action(HttpAPI.Remote.BLUE)) # Power app.set_action("on_standby", lambda a, v: self.on_power_action(HttpAPI.Power.STANDBY)) app.set_action("on_wake_up", lambda a, v: self.on_power_action(HttpAPI.Power.WAKEUP)) app.set_action("on_reboot", lambda a, v: self.on_power_action(HttpAPI.Power.REBOOT)) app.set_action("on_restart_gui", lambda a, v: self.on_power_action(HttpAPI.Power.RESTART_GUI)) app.set_action("on_shutdown", lambda a, v: self.on_power_action(HttpAPI.Power.DEEP_STANDBY)) # Screenshots app.set_action("on_screenshot_all", self.on_screenshot_all) app.set_action("on_screenshot_video", self.on_screenshot_video) app.set_action("on_screenshot_osd", self.on_screenshot_osd) # Timers app.set_action("on_timer_add", self.on_timer_add) app.set_action("on_timer_add_from_event", self.on_timer_add_from_event) app.set_action("on_timer_remove", self.on_timer_remove) app.set_action("on_timer_edit", self.on_timer_edit) app.set_action("on_timer_save", self.on_timer_save) app.set_action("on_timer_cancel", self.on_timer_cancel) app.set_action("on_timer_begins_set", self.on_timer_begins_set) app.set_action("on_timer_ends_set", self.on_timer_ends_set) # Recordings app.set_action("on_recording_remove", self.on_recording_remove) @property def update_epg(self): return self._update_epg def on_visible_tool(self, stack, param): tool = self.Tool(stack.get_visible_child_name()) self._update_epg = tool is self.Tool.EPG if tool is self.Tool.TIMERS: self.update_timer_list() if tool is self.Tool.RECORDINGS: self.update_recordings_list() if tool is not self.Tool.TIMER: self._last_tool = tool def on_hide(self, item): self._update_epg = False # ***************** Remote controller ********************* # def on_remote(self, action, state=False): """ Shows/Hides [R key] remote controller. """ action.set_state(state) self._remote_revealer.set_visible(state) self._remote_revealer.set_reveal_child(state) if state: self._http_api.send(HttpAPI.Request.VOL, "state", self.update_volume) def on_remote_action(self, action): self._http_api.send(HttpAPI.Request.REMOTE, action, self.on_response) @run_with_delay(0.5) def on_volume_changed(self, button, value): self._http_api.send(HttpAPI.Request.VOL, "{:.0f}".format(value), self.on_response) def update_volume(self, vol): if "error_code" in vol: return GLib.idle_add(self._volume_button.set_value, int(vol.get("e2current", "0"))) def on_response(self, resp): if "error_code" in resp: return if self._screenshot_check_button.get_active(): ref = "mode=all" if self._http_api.is_owif else "d=" self._http_api.send(HttpAPI.Request.GRUB, ref, self.update_screenshot) @run_task def update_screenshot(self, data): if "error_code" in data: return data = data.get("img_data", None) if data: from gi.repository import GdkPixbuf allocation = self._screenshot_image.get_parent().get_allocation() loader = GdkPixbuf.PixbufLoader.new_with_type("jpeg") loader.set_size(allocation.width, allocation.height) try: loader.write(data) pix = loader.get_pixbuf() except GLib.Error: pass # NOP else: GLib.idle_add(self._screenshot_image.set_from_pixbuf, pix) finally: loader.close() def on_screenshot_all(self, action, value=None): self._http_api.send(HttpAPI.Request.GRUB, "mode=all" if self._http_api.is_owif else "d=", self.on_screenshot) def on_screenshot_video(self, action, value=None): self._http_api.send(HttpAPI.Request.GRUB, "mode=video" if self._http_api.is_owif else "v=", self.on_screenshot) def on_screenshot_osd(self, action, value=None): self._http_api.send(HttpAPI.Request.GRUB, "mode=osd" if self._http_api.is_owif else "o=", self.on_screenshot) @run_task def on_screenshot(self, data): if "error_code" in data: return img = data.get("img_data", None) if img: is_darwin = self._settings.is_darwin GLib.idle_add(self._screenshot_button_box.set_sensitive, is_darwin) path = os.path.expanduser("~/Desktop") if is_darwin else None try: import tempfile import subprocess with tempfile.NamedTemporaryFile(mode="wb", suffix=".jpg", dir=path, delete=not is_darwin) as tf: tf.write(img) cmd = ["open" if is_darwin else "xdg-open", tf.name] subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() finally: GLib.idle_add(self._screenshot_button_box.set_sensitive, True) def on_power_action(self, action): self._http_api.send(HttpAPI.Request.POWER, action, lambda resp: log("Power status changed...")) def update_signal(self, sig): self._snr_value_label.set_text(sig.get("e2snrdb", "0 dB").strip()) self._ber_value_label.set_text(str(sig.get("e2ber", None) or "0").strip()) self._agc_value_label.set_text(sig.get("e2acg", "0 %").strip()) # ************************ EPG **************************** # def on_service_changed(self, ref): self._app._wait_dialog.show() self._http_api.send(HttpAPI.Request.EPG, quote(ref), self.update_epg_data) @run_idle def update_epg_data(self, epg): list(map(self._epg_list_box.remove, (r for r in self._epg_list_box))) list(map(lambda e: self._epg_list_box.add(self.EpgRow(e)), epg.get("event_list", []))) self._app._wait_dialog.hide() def on_epg_press(self, list_box, event): if event.get_event_type() == Gdk.EventType.DOUBLE_BUTTON_PRESS and len(list_box) > 0: row = list_box.get_selected_row() if row: self.set_timer_from_event_data(row.event_data) def on_epg_filter_changed(self, entry): self._epg_list_box.invalidate_filter() def epg_filter_function(self, row): txt = self._epg_filter_entry.get_text().upper() return any((not txt, txt in row.time_header.upper(), txt in row.title.upper(), txt in row.desc.upper())) def on_timer_add_from_event(self, action, value=None): rows = self._epg_list_box.get_selected_rows() if not rows: self._app.show_error_message("No selected item!") return refs = [] for row in rows: event = row.event_data ref = "timeraddbyeventid?sRef={}&eventid={}&justplay=0".format(event.get("e2eventservicereference", ""), event.get("e2eventid", "")) refs.append(ref) gen = self.write_timers_list(refs) GLib.idle_add(lambda: next(gen, False)) def write_timers_list(self, refs): self._app._wait_dialog.show() tasks = list(refs) for ref in refs: self._http_api.send(HttpAPI.Request.TIMER, ref, lambda x: tasks.pop()) yield True while tasks: yield True self._stack.set_visible_child_name(self.Tool.TIMERS.value) # *********************** Timers *************************** # def on_timers_press(self, list_box, event): if event.get_event_type() == Gdk.EventType.DOUBLE_BUTTON_PRESS and len(list_box) > 0: self.on_timer_edit() def update_timer_list(self): self._app._wait_dialog.show() self._http_api.send(HttpAPI.Request.TIMER_LIST, "", self.update_timers_data) @run_idle def update_timers_data(self, timers): list(map(self._timers_list_box.remove, (r for r in self._timers_list_box))) list(map(lambda t: self._timers_list_box.add(self.TimerRow(t)), timers.get("timer_list", []))) self._timer_remove_button.set_visible(len(self._timers_list_box)) self._app._wait_dialog.hide() def on_timer_add(self, action=None, value=None): self._timer_action = self.TimerAction.ADD date = datetime.now() self.set_begins_date(date) self.set_ends_date(date) self._timer_event_id_entry.set_text("") self._timer_location_switch.set_active(False) self.set_repetition_flags(0) self._stack.set_visible_child_name(self.Tool.TIMER.value) def on_timer_remove(self, action, value=None): rows = self._timers_list_box.get_selected_rows() if not rows or show_dialog(DialogType.QUESTION, self._app._main_window) != Gtk.ResponseType.OK: return refs = {} for row in rows: timer = row.timer ref = "timerdelete?sRef={}&begin={}&end={}".format(quote(timer.get("e2servicereference", "")), timer.get("e2timebegin", ""), timer.get("e2timeend", "")) refs[ref] = row self._app._wait_dialog.show("Deleting data...") gen = self.remove_timers(refs) GLib.idle_add(lambda: next(gen, False)) def remove_timers(self, refs): tasks = list(refs) removed = set() for ref in refs: yield from self.remove_timer(ref, removed, tasks) while tasks: yield True list(map(self._timers_list_box.remove, (refs[ref] for ref in refs if ref in removed))) self._app._wait_dialog.hide() self._timer_remove_button.set_visible(len(self._timers_list_box)) yield True def remove_timer(self, ref, removed, tasks=None): def callback(resp): if resp.get("e2state", "") == "True": log(resp.get("e2statetext", "")) removed.add(ref) else: log(resp.get("e2statetext", None) or "Timer deletion error.") if tasks: tasks.pop() self._http_api.send(HttpAPI.Request.TIMER, ref, callback) yield True def on_timer_edit(self, action=None, value=None): row = self._timers_list_box.get_selected_row() if row: self._timer_action = self.TimerAction.CHANGE timer = row.timer self._current_timer = timer self._timer_name_entry.set_text(timer.get("e2name", "")) self._timer_desc_entry.set_text(timer.get("e2description", "") or "") self._timer_service_entry.set_text(timer.get("e2servicename", "") or "") self._timer_service_ref_entry.set_text(timer.get("e2servicereference", "")) self._timer_event_id_entry.set_text(timer.get("e2eit", "")) self._timer_enabled_switch.set_active((timer.get("e2disabled", "0") == "0")) self._timer_action_combo_box.set_active_id(timer.get("e2justplay", "0")) self._timer_after_combo_box.set_active_id(timer.get("e2afterevent", "0")) self.set_time_data(int(timer.get("e2timebegin", "0")), int(timer.get("e2timeend", "0"))) location = timer.get("e2location", "") self._timer_location_entry.set_text("" if location == "None" else location) # Days self.set_repetition_flags(int(timer.get("e2repeated", "0"))) self._stack.set_visible_child_name(self.Tool.TIMER.value) def on_timer_save(self, action, value=None): args = [] t_data = self.get_timer_data() s_ref = quote(t_data.get("sRef", "")) if self._timer_action is self.TimerAction.EVENT: args.append("timeraddbyeventid?sRef={}".format(s_ref)) args.append("eventid={}".format(t_data.get("eit", "0"))) args.append("justplay={}".format(t_data.get("justplay", ""))) args.append("tags={}".format("")) else: if self._timer_action is self.TimerAction.ADD: args.append("timeradd?sRef={}".format(s_ref)) args.append("deleteOldOnSave={}".format(0)) elif self._timer_action is self.TimerAction.CHANGE: args.append("timerchange?sRef={}".format(s_ref)) args.append("channelOld={}".format(s_ref)) args.append("beginOld={}".format(self._current_timer.get("e2timebegin", "0"))) args.append("endOld={}".format(self._current_timer.get("e2timeend", "0"))) args.append("deleteOldOnSave={}".format(1)) args.append("begin={}".format(t_data.get("begin", ""))) args.append("end={}".format(t_data.get("end", ""))) args.append("name={}".format(quote(t_data.get("name", "")))) args.append("description={}".format(quote(t_data.get("description", "")))) args.append("tags={}".format("")) args.append("eit={}".format("0")) args.append("disabled={}".format(t_data.get("disabled", "1"))) args.append("justplay={}".format(t_data.get("justplay", "1"))) args.append("afterevent={}".format(t_data.get("afterevent", "0"))) args.append("repeated={}".format(self.get_repetition_flags())) if self._timer_location_switch.get_active(): args.append("dirname={}".format(self._timer_location_entry.get_text())) self._http_api.send(HttpAPI.Request.TIMER, "&".join(args), self.timer_add_edit_callback) @run_idle def timer_add_edit_callback(self, resp): if "error_code" in resp: msg = "Error getting timer status.\n{}".format(resp.get("error_code")) self._app.show_error_message(msg) log(msg) return state = resp.get("e2state", None) if state == "False": msg = resp.get("e2statetext", "") self._app.show_error_message(msg) log(msg) if state == "True": log(resp.get("e2statetext", "")) self._stack.set_visible_child_name(self._last_tool.value) else: log("Error getting timer status. No response!") def on_timer_cancel(self, action, value=None): self._stack.set_visible_child_name(self._last_tool.value) def on_timer_begins_set(self, action, value=None): self.set_begins_date(self.get_begins_date()) def on_timer_ends_set(self, action, value=None): self.set_ends_date(self.get_ends_date()) def get_begins_date(self): date = self._timer_begins_calendar.get_date() return datetime(year=date.year, month=date.month + 1, day=date.day, hour=int(self._timer_begins_hr_button.get_value()), minute=int(self._timer_begins_min_button.get_value())) def set_begins_date(self, date): hour = date.hour minute = date.minute self._timer_begins_hr_button.set_value(hour) self._timer_begins_min_button.set_value(minute) self._timer_begins_calendar.select_day(date.day) self._timer_begins_calendar.select_month(date.month - 1, date.year) self._timer_begins_entry.set_text("{}-{}-{} {}:{:02d}".format(date.year, date.month, date.day, hour, minute)) def get_ends_date(self): date = self._timer_ends_calendar.get_date() return datetime(year=date.year, month=date.month + 1, day=date.day, hour=int(self._timer_ends_hr_button.get_value()), minute=int(self._timer_ends_min_button.get_value())) def set_ends_date(self, date): hour = date.hour minute = date.minute self._timer_ends_hr_button.set_value(hour) self._timer_ends_min_button.set_value(minute) self._timer_ends_calendar.select_day(date.day) self._timer_ends_calendar.select_month(date.month - 1, date.year) self._timer_ends_entry.set_text("{}-{}-{} {}:{:02d}".format(date.year, date.month, date.day, hour, minute)) def set_timer_from_event_data(self, timer): self._stack.set_visible_child_name(self.Tool.TIMER.value) self._timer_action = self.TimerAction.EVENT self._timer_name_entry.set_text(timer.get("e2eventtitle", "")) self._timer_desc_entry.set_text(timer.get("e2eventdescription", "")) self._timer_service_entry.set_text(timer.get("e2eventservicename", "")) self._timer_service_ref_entry.set_text(timer.get("e2eventservicereference", "")) self._timer_event_id_entry.set_text(timer.get("e2eventid", "")) self._timer_action_combo_box.set_active_id("1") self._timer_after_combo_box.set_active_id("3") start_time = int(timer.get("e2eventstart", "0")) self.set_time_data(start_time, start_time + int(timer.get("e2eventduration", "0"))) def set_time_data(self, start_time, end_time): """ Sets values for time widgets. """ ev_time_start = datetime.fromtimestamp(start_time) or datetime.now() ev_time_end = datetime.fromtimestamp(end_time) or datetime.now() self._timer_begins_entry.set_text(ev_time_start.strftime(self._TIME_STR)) self._timer_ends_entry.set_text(ev_time_end.strftime(self._TIME_STR)) self._timer_begins_calendar.select_day(ev_time_start.day) self._timer_begins_calendar.select_month(ev_time_start.month - 1, ev_time_start.year) self._timer_ends_calendar.select_day(ev_time_end.day) self._timer_ends_calendar.select_month(ev_time_end.month - 1, ev_time_end.year) self._timer_begins_hr_button.set_value(ev_time_start.hour) self._timer_begins_min_button.set_value(ev_time_start.minute) self._timer_ends_hr_button.set_value(ev_time_end.hour) self._timer_ends_min_button.set_value(ev_time_end.minute) def get_timer_data(self): """ Returns timer data as a dict. """ return {"sRef": self._timer_service_ref_entry.get_text(), "begin": int(datetime.strptime(self._timer_begins_entry.get_text(), self._TIME_STR).timestamp()), "end": int(datetime.strptime(self._timer_ends_entry.get_text(), self._TIME_STR).timestamp()), "name": self._timer_name_entry.get_text(), "description": self._timer_desc_entry.get_text(), "dirname": "", "eit": self._timer_event_id_entry.get_text(), "disabled": int(not self._timer_enabled_switch.get_active()), "justplay": self._timer_action_combo_box.get_active_id(), "afterevent": self._timer_after_combo_box.get_active_id(), "repeated": self.get_repetition_flags()} def get_repetition_flags(self): """ Returns flags for repetition. """ day_flags = 0 for i, box in enumerate((self._timer_mo_check_button, self._timer_tu_check_button, self._timer_we_check_button, self._timer_th_check_button, self._timer_fr_check_button, self._timer_sa_check_button, self._timer_su_check_button)): if box.get_active(): day_flags = day_flags | (1 << i) return day_flags def set_repetition_flags(self, flags): for i, box in enumerate((self._timer_mo_check_button, self._timer_tu_check_button, self._timer_we_check_button, self._timer_th_check_button, self._timer_fr_check_button, self._timer_sa_check_button, self._timer_su_check_button)): box.set_active(flags & 1 == 1) flags = flags >> 1 # ***************** Drag-and-drop ********************* # def on_timers_drag_data_received(self, box, context, x, y, data, info, time): txt = data.get_text() if txt: itr_str, sep, source = txt.partition(self._app.DRAG_SEP) if not source: return itrs = itr_str.split(",") if len(itrs) > 1: self._app.show_error_message("Please, select only one item!") return fav_id = None if source == self._app.FAV_MODEL_NAME: model = self._app.fav_view.get_model() fav_id = model.get_value(model.get_iter_from_string(itrs[0]), Column.FAV_ID) elif source == self._app.SERVICE_MODEL_NAME: model = self._app.services_view.get_model() fav_id = model.get_value(model.get_iter_from_string(itrs[0]), Column.SRV_FAV_ID) service = self._app.current_services.get(fav_id, None) if service: if service.service_type == BqServiceType.ALT.name: msg = "Alternative service.\n\n {}".format(get_message("Not implemented yet!")) show_dialog(DialogType.ERROR, transient=self._app._main_window, text=msg) context.finish(False, False, time) return self._timer_name_entry.set_text(service.service) self._timer_service_entry.set_text(service.service) self._timer_service_ref_entry.set_text(service.picon_id.rstrip(".png").replace("_", ":")) self.on_timer_add() context.finish(True, False, time) # *********************** Recordings *************************** # def on_recordings_press(self, list_box, event): if event.get_event_type() == Gdk.EventType.DOUBLE_BUTTON_PRESS and len(list_box) > 0: row = list_box.get_selected_row() if row: self._http_api.send(HttpAPI.Request.STREAM_TS, row.movie.get("e2filename", ""), self.on_play_recording) def on_recording_filter_changed(self, entry): self._recordings_list_box.invalidate_filter() def recording_filter_function(self, row): txt = self._recordings_filter_entry.get_text().upper() return any((not txt, txt in row.service.upper(), txt in row.title.upper(), txt in row.desc.upper())) def on_recording_remove(self, action, value=None): """ Removes recordings via FTP. """ if show_dialog(DialogType.QUESTION, self._app._main_window) != Gtk.ResponseType.OK: return rows = self._recordings_list_box.get_selected_rows() if rows: settings = self._app._settings with UtfFTP(host=settings.host, user=settings.user, passwd=settings.password) as ftp: ftp.encoding = "utf-8" for r in rows: resp = ftp.delete_file(r.file) if resp.startswith("2"): GLib.idle_add(self._recordings_list_box.remove, r) else: show_dialog(DialogType.ERROR, transient=self._app._main_window, text=resp) break def on_recordings_dir_changed(self, box: Gtk.ComboBoxText): self._http_api.send(HttpAPI.Request.RECORDINGS, quote(box.get_active_id()), self.update_recordings_data) def update_recordings_list(self): if not len(self._recordings_dir_box.get_model()): self._http_api.send(HttpAPI.Request.REC_CURRENT, "", self.update_current_rec_dir) def update_current_rec_dir(self, current): cur = current.get("e2location", None) if cur: self._recordings_dir_box.append(cur, cur) self._http_api.send(HttpAPI.Request.REC_DIRS, "", self.update_rec_dirs) def update_rec_dirs(self, dirs): for d in dirs.get("rec_dirs", []): self._recordings_dir_box.append(d, d) @run_idle def update_recordings_data(self, recordings): list(map(self._recordings_list_box.remove, (r for r in self._recordings_list_box))) list(map(lambda r: self._recordings_list_box.add(self.RecordingsRow(r)), recordings.get("recordings", []))) def on_play_recording(self, m3u): url = self._app.get_url_from_m3u(m3u) if url: self._app.play(url)