def cue_backward(self, **kwargs): cc = self.controller.cued_item if not cc: return NebulaResponse(204) db = DB() nc = get_next_item(cc.id, db=db, force="prev") return self.cue(item=nc, db=db, level=5)
def channel_recover(self): logging.warning("Performing recovery") db = DB() db.query( """ SELECT id_item, start FROM asrun WHERE id_channel = %s ORDER BY id DESC LIMIT 1 """, [self.id_channel], ) try: last_id_item, last_start = db.fetchall()[0] except IndexError: logging.error("Unable to perform recovery.") last_item = Item(last_id_item, db=db) last_item.asset self.controller.current_item = last_item self.controller.cued_item = False self.controller.cued_fname = False if last_start + last_item.duration <= time.time(): logging.info(f"Last {last_item} has been broadcasted.") new_item = self.cue_next(item=last_item, db=db, play=True) else: logging.info(f"Last {last_item} has not been fully broadcasted.") new_item = self.cue_next(item=last_item, db=db) if not new_item: logging.error("Recovery failed. Unable to cue") return self.on_change()
def api_solve(**kwargs): id_item = kwargs.get("id_item", False) solver_name = kwargs.get("solver", False) items = kwargs.get("items", []) user = kwargs.get("user", anonymous) if not user.has_right("rundown_edit", anyval=True): return NebulaResponse(403) if id_item: items.append(id_item) if not (items and solver_name): return NebulaResponse( 400, "You must specify placeholder item ID and a solver name" ) Solver = get_solver(solver_name) if Solver is None: return NebulaResponse(500, "Unable to load the solver. Check logs for details") db = DB() for id_item in items: solver = Solver(Item(id_item, db=db), db=db) response = solver.main() if response.is_error: return response return response
def db(self): if not self._db: if "db" in self.kwargs: self._db = self.kwargs["db"] else: self._db = DB() return self._db
def on_init(self): self.actions = [] db = DB() db.query("SELECT id, title, settings FROM actions") for id, title, settings in db.fetchall(): settings = xml(settings) self.actions.append(Action(id, title, settings))
def on_main(self): db = DB() db.query("SELECT id, meta FROM assets WHERE status=%s", [ObjectStatus.ONLINE]) for _, meta in db.fetchall(): asset = Asset(meta=meta, db=db) self.proc(asset)
def build(self, *args, **kwargs): services = [] db = DB() db.query(""" SELECT id, service_type, host, title, autostart, state, last_seen FROM services ORDER BY id """) for id, service_type, host, title, autostart, state, last_seen in db.fetchall( ): service = { "id": id, "service_type": service_type, "host": host, "title": title, "autostart": autostart, "state": state, "last_seen": last_seen, "message": "", } if time.time() - last_seen > 120: nrtime = s2words(time.time() - last_seen) service["message"] = f"Not responding for {nrtime}" services.append(service) self["name"] = "services" self["title"] = "Services" self["js"] = ["/static/js/services.js"] self["data"] = services
def passwd(*args): print() try: login = input("Login: "******"Password (will be echoed): ").strip() is_admin = input("Admin (yes/no): ").strip() except KeyboardInterrupt: print() logging.warning("Interrupted by user") sys.exit(0) db = DB() db.query("SELECT id FROM users WHERE login=%s", [login]) res = db.fetchall() if not res: critical_error("Unable to set password: no such user") u = User(res[0][0], db=db) if login: u["login"] = u["full_name"] = login u["is_admin"] = 1 if is_admin == "yes" else 0 u.set_password(password) u.save() print() logging.goodnews("Password changed")
def __init__(self, id_channel, **kwargs): self.db = kwargs.get("db", DB()) self.id_channel = id_channel self.playout_config = config["playout_channels"][id_channel] self.status_key = "playout_status/{}".format(self.id_channel) self.send_action = self.playout_config.get("send_action", False) self.scheduled_ids = []
def get_scheduled_assets(id_channel, **kwargs): db = kwargs.get("db", DB()) db.query( """ SELECT a.meta, dist FROM ( SELECT i.id_asset, MIN(ABS(e.start - extract(epoch from now()))) AS dist FROM events as e, items as i WHERE e.start > extract(epoch from now()) - 86400*7 AND e.id_channel = %s AND i.id_bin = e.id_magic AND i.id_asset > 0 GROUP BY i.id_asset) i LEFT JOIN assets a ON a.id = i.id_asset ORDER BY dist ASC """, [id_channel], ) for meta, dist in db.fetchall(): yield Asset(meta=meta, db=db), dist < 86400
def api_actions(**kwargs): objects = kwargs.get("objects") or kwargs.get("ids", []) db = kwargs.get("db", DB()) user = kwargs.get("user", anonymous) if not user: return NebulaResponse(401, "You are not allowed to execute any actions") if not objects: return NebulaResponse(400, "No asset selected") result = [] db.query("SELECT id, title, settings FROM actions ORDER BY id ASC") for id, title, settings in db.fetchall(): allow = False # noqa try: cond = xml(settings).find("allow_if").text except Exception: log_traceback() continue for id_asset in objects: asset = Asset(id_asset, db=db) # noqa if not eval(cond): break else: if user.has_right("job_control", id): result.append((id, title)) return NebulaResponse(200, data=result)
def meta_exists(key, value, db=False): if not db: db = DB() db.query("SELECT id, meta FROM assets WHERE meta->>%s = %s", [str(key), str(value)]) for _, meta in db.fetchall(): return Asset(meta=meta, db=db) return False
def on_main(self): if not self.import_dir: return if not os.path.isdir(self.import_dir): logging.error("Import directory does not exist. Shutting down.") self.import_path = False self.shutdown(no_restart=True) return db = DB() for import_file in get_files(self.import_dir, exts=self.exts): idec = import_file.base_name try: with import_file.open("rb") as f: f.seek(0, 2) fsize = f.tell() except IOError: logging.debug(f"Import file {import_file.base_name} is busy.") continue if not (import_file.path in self.filesizes and self.filesizes[import_file.path] == fsize): self.filesizes[import_file.path] = fsize logging.debug(f"New file '{import_file.base_name}' detected") continue db.query( """ SELECT meta FROM assets WHERE meta->>%s = %s """, [self.identifier, idec], ) for (meta, ) in db.fetchall(): asset = Asset(meta=meta, db=db) if not (asset["id_storage"] and asset["path"]): mk_error(import_file, "This file has no target path.") continue if self.versioning and os.path.exists(asset.file_path): version_backup(asset) do_import(self, import_file, asset) break else: mk_error(import_file, "This file is not expected.") for fname in os.listdir(self.import_dir): if not fname.endswith(".error.txt"): continue idec = fname.replace(".error.txt", "") if idec not in [ os.path.splitext(f)[0] for f in os.listdir(self.import_dir) ]: os.remove(os.path.join(self.import_dir, fname))
def shutdown(self, no_restart=False): logging.info("Shutting down") if no_restart: db = DB() db.query("UPDATE services SET autostart=FALSE WHERE id=%s", [self.id_service]) db.commit() self.on_shutdown() sys.exit(0)
def on_init(self): self.services = {} db = DB() db.query("SELECT id, pid FROM services WHERE host=%s", [config["host"]]) for _, pid in db.fetchall(): if pid: self.kill_service(pid) db.query("UPDATE services SET state = 0 WHERE host=%s", [config["host"]]) db.commit()
def asset_by_full_path(path, db=False): if not db: db = DB() for id_storage in storages: if path.startswith(storages[id_storage].local_path): return asset_by_path(id_storage, path.lstrip(storages[id_storage]["path"]), db=db) return False
def build(self, *args, **kwargs): db = DB() id_user = int(kwargs.get("id_user", 0)) if id_user and self["user"]["is_admin"]: user = User(id_user, db=db) else: user = self["user"] self["user"] = user password = kwargs.get("password", False) full_name = kwargs.get("full_name", False) dashboard = kwargs.get("dashboard", "") user_changed = False if cherrypy.request.method == "POST": if full_name: user["full_name"] = kwargs["full_name"] user_changed = True if password: if len(password) < 8: self.context.message(WEAK_PASS_MSG, "error") return user.set_password(kwargs["password"]) user_changed = True if dashboard != user["dashboard"]: user["dashboard"] = dashboard user_changed = True if user_changed: user.save() if self["user"].id == user.id: self.context["user"] = user.meta self.context.message("User profile saved") db.query("SELECT meta FROM users WHERE meta->>'is_admin' = 'true'") self["admins"] = [User(meta=meta) for meta, in db.fetchall()] self["name"] = "profile" self["title"] = "User profile" self["rights"] = [ ["asset_edit", "Edit assets", "folders"], ["asset_create", "Create assets", "folders"], ["rundown_view", "View rundown", "playout_channels"], ["rundown_edit", "Edit rundown", "playout_channels"], ["scheduler_view", "View scheduler", "playout_channels"], ["scheduler_edit", "Modify scheduler", "playout_channels"], ["job_control", "Control jobs", "actions"], ["service_control", "Control services", "services"], ["mcr", "Control playout", "playout_channels"], ]
def j(*args): print db = DB() db.query(""" SELECT j.id, j.id_action, j.settings, j.priority, j.retries, j.status, j.progress, j.message, j.creation_time, j.start_time, j.end_time, a.meta FROM jobs AS j, assets AS a WHERE a.id = j.id_asset AND j.status in (0,1,5) ORDER BY id DESC LIMIT 50 """) for ( id, id_action, settings, priority, retries, status, progress, message, creation_time, start_time, end_time, meta, ) in db.fetchall(): asset = Asset(meta=meta) line = "{:<30}".format(asset) line += "{} {:.02f}%\n".format(status, progress) try: sys.stdout.write(line) sys.stdout.flush() except IOError: pass
def on_init(self): self.service_type = "conv" self.actions = [] db = DB() db.query(""" SELECT id, title, service_type, settings FROM actions ORDER BY id """) for id_action, title, service_type, settings in db.fetchall(): if service_type == self.service_type: logging.debug(f"Registering action {title}") self.actions.append(Action(id_action, title, xml(settings))) self.reset_jobs()
def build(self, *args, **kwargs): mode = "active" if len(args) > 1: if args[1] in ["finished", "failed"]: mode = args[1] id_asset = kwargs.get("id_asset", 0) id_action = kwargs.get("id_action", 0) try: id_asset = int(id_asset) except ValueError: id_asset = 0 try: id_action = int(id_action) except ValueError: id_action = 0 query = kwargs.get("q", "") if cherrypy.request.method == "POST": if id_asset and id_action: # TODO: how to select restert_existing/running? response = api_send(ids=[id_asset], id_action=id_action, user=self["user"]) if response.is_error: self.context.message(response.message, level="error") else: self.context.message(response.message) # do not use filter: show all active jobs to see queue id_asset = id_action = 0 if id_asset: db = DB() asset = Asset(id_asset, db=db) actions = api_actions(user=self["user"], db=db, ids=[id_asset]) else: actions = NebulaResponse(404) asset = False self["name"] = "jobs" self["js"] = ["/static/js/jobs.js"] self["title"] = mode.capitalize() + " jobs" self["mode"] = mode self["id_asset"] = id_asset self["asset"] = asset self["actions"] = actions.data if actions.is_success else [] self["id_action"] = id_asset self["query"] = query
def get_item_event(id_item, **kwargs): db = kwargs.get("db", DB()) db.query(""" SELECT e.id, e.meta FROM items AS i, events AS e WHERE e.id_magic = i.id_bin AND i.id = {} AND e.id_channel in ({}) """.format( id_item, ", ".join([str(f) for f in config["playout_channels"].keys()]))) for _, meta in db.fetchall(): return Event(meta=meta, db=db) return False
def get_bin_first_item(id_bin, db=False): if not db: db = DB() db.query( """ SELECT id, meta FROM items WHERE id_bin=%s ORDER BY position LIMIT 1 """, [id_bin], ) for _, meta in db.fetchall(): return Item(meta=meta, db=db) return False
def on_init(self): self.last_update = 0 db = DB() try: db.query( """ INSERT INTO hosts(hostname, last_seen) VALUES (%s, %s) ON CONFLICT DO NOTHING """, [config["host"], time.time()], ) except psycopg2.IntegrityError: pass else: db.commit()
def api_delete(**kwargs): object_type = kwargs.get("object_type", "asset") # TODO: ids is deprecated. use objects instead objects = kwargs.get("objects") or kwargs.get("ids", []) db = kwargs.get("db", DB()) user = kwargs.get("user", anonymous) initiator = kwargs.get("initiator", None) if not user: return NebulaResponse(401) if not (objects): return NebulaResponse(200, "No object deleted") object_type_class = { "asset": Asset, "item": Item, "bin": Bin, "event": Event, }[object_type] num = 0 affected_bins = [] for id_object in objects: obj = object_type_class(id_object, db=db) if object_type == "item": if not user.has_right("rundown_edit", anyval=True): return NebulaResponse(403) try: obj.delete() except psycopg2.IntegrityError: return NebulaResponse( 423, f"Unable to delete {obj}. Already aired.") if obj["id_bin"] not in affected_bins: affected_bins.append(obj["id_bin"]) else: return NebulaResponse( 501, f"{object_type} deletion is not implemented") num += 1 if affected_bins: bin_refresh(affected_bins, db=db, initiator=initiator) return NebulaResponse(200, f"{num} objects deleted")
def get_day_events(id_channel, date, num_days=1): chconfig = config["playout_channels"][id_channel] start_time = datestr2ts(date, *chconfig.get("day_start", [6, 0])) end_time = start_time + (3600 * 24 * num_days) db = DB() db.query( """ SELECT id, meta FROM events WHERE id_channel=%s AND start > %s AND start < %s """, (id_channel, start_time, end_time), ) for _, meta in db.fetchall(): yield Event(meta=meta)
def asset_by_path(id_storage, path, db=False): id_storage = str(id_storage) path = path.replace("\\", "/") if not db: db = DB() db.query( """ SELECT id, meta FROM assets WHERE media_type = %s AND meta->>'id_storage' = %s AND meta->>'path' = %s """, [MediaType.FILE, id_storage, path], ) for id, meta in db.fetchall(): return Asset(meta=meta, db=db) return False
def get_user(login, password, db=False): if not db: db = DB() try: db.query( """ SELECT meta FROM users WHERE login=%s AND password=%s """, [login, get_hash(password)], ) except ValueError: return False res = db.fetchall() if not res: return False return User(meta=res[0][0])
def get_item_runs(id_channel, from_ts, to_ts, db=False): db = db or DB() db.query( """ SELECT id_item, start, stop FROM asrun WHERE start >= %s AND start < %s ORDER BY start DESC """, [int(from_ts), int(to_ts)], ) result = {} for id_item, start, stop in db.fetchall(): if id_item not in result: result[id_item] = (start, stop) return result
def build(self, *args, **kwargs): if args[-1] == "reload_settings": load_settings() webtools.load() raise cherrypy.HTTPRedirect("/settings") module = "settings" if len(args) > 1: if args[1] in modules: module = args[1] db = DB() modules[module]["context"](self, db, **kwargs) self["name"] = "settings" self["title"] = "Settings" self["modules"] = modules self["module"] = module
def api_send(**kwargs): objects = kwargs.get("objects") or kwargs.get( "ids", []) # TODO: ids is deprecated. use objects instead id_action = kwargs.get("id_action", False) settings = kwargs.get("settings", {}) db = kwargs.get("db", DB()) user = kwargs.get("user", anonymous) restart_existing = kwargs.get("restart_existing", True) restart_running = kwargs.get("restart_running", False) if not user.has_right("job_control", anyval=True): return NebulaResponse(403, "You are not allowed to execute this action") # TODO: Better ACL if not id_action: return NebulaResponse(400, "No valid action selected") if not objects: return NebulaResponse(400, "No asset selected") if not user.has_right("job_control", id_action): return NebulaResponse(400, "You are not allowed to start this action") logging.info( "{} is starting action {} for the following assets: {}".format( user, id_action, ", ".join([str(i) for i in objects]))) for id_object in objects: send_to( id_object, id_action, settings=settings, id_user=user.id, restart_existing=restart_existing, restart_running=restart_running, db=db, ) return NebulaResponse( 200, "Starting {} job{}".format(len(objects), "s" if len(objects) > 1 else ""))