def do_edit(options): """Downloadliste anzeigen und editieren""" # Liste lesen rows = options.filmDB.read_downloads() if not rows: Msg.msg("INFO", "Keine vorgemerkten Filme vorhanden") return # Liste aufbereiten select_liste = [] for row in rows: status = row['STATUS'] datum_status = row['DATUMSTATUS'].strftime("%d.%m.%y") sender = row['SENDER'] thema = row['THEMA'] titel = row['TITEL'] dauer = row['DAUER'] datum = row['DATUM'].strftime("%d.%m.%y") select_liste.append( DLL_FORMAT.format(status, datum_status, sender, thema, datum, dauer, titel)) selected = pick(select_liste, DLL_TITEL, multi_select=True) # IDs extrahieren und Daten löschen deletes = [] for sel_text, sel_index in selected: row = rows[sel_index] deletes.append((row['_ID'], )) if len(deletes): changes = options.filmDB.delete_downloads(deletes) else: changes = 0 Msg.msg("INFO", "%d vorgemerkte Filme gelöscht" % changes)
def suche(): # Auslesen Request-Parameter such_args = [] token = bottle.request.forms.get('global') if token: such_args.append(token) for arg in ['sender', 'thema', 'datum', 'titel', 'bechreibung']: token = bottle.request.forms.get(arg) if token: such_args.append(arg + ':' + token) Msg.msg("DEBUG", "Suchbegriffe: " + str(such_args)) # Film-DB abfragen statement = options.filmDB.get_query(such_args) rows = options.filmDB.execute_query(statement) # Ergebnis aufbereiten result = [] for row in rows: item = {} item['DATUM'] = row['DATUM'].strftime("%d.%m.%y") for key in [ 'SENDER', 'THEMA', 'TITEL', 'DAUER', 'BESCHREIBUNG', '_ID' ]: item[key] = row[key] result.append(item) Msg.msg("DEBUG", "Anzahl Treffer: %d" % len(result)) bottle.response.content_type = 'application/json' return json.dumps(result)
def get_datei(): """ Datei herunterladen """ # get name-parameter dateiname = bottle.request.query.getunicode('name') Msg.msg("DEBUG", "Downloadanforderung (Dateiname: %s)" % dateiname) bottle.response.content_type = 'application/json' if dateiname is None: msg = '"kein Dateiname angegeben"' bottle.response.status = 400 # bad request return '{"msg": ' + msg +'}' # Überprüfen, ob Dateiname in Film-DB existiert rows = options.filmDB.read_recs(dateiname) if not rows: Msg.msg("WARN", "Dateiname %s nicht in Film-DB" % dateiname) msg = '"Ungültiger Dateiname"' bottle.response.status = 400 # bad request return '{"msg": ' + msg +'}' if not os.path.exists(dateiname): msg = '"Dateiname existiert nicht"' bottle.response.status = 404 # not found return '{"msg": ' + msg +'}' # Datei roh herunterladen bottle.response.content_type = 'application/mp4' f = '"'+os.path.basename(dateiname)+'"' bottle.response.set_header('Content-Disposition','attachment; filename=%s' % f) return subprocess.check_output(['cat',dateiname])
def download_film(options, film): """Download eines einzelnen Films""" # Infos zusammensuchen _id = film._id size, url = film.get_url(options.config["QUALITAET"]) film.thema = film.thema.replace('/', '_') film.titel = film.titel.replace('/', '_') ziel = options.config["ZIEL_DOWNLOADS"].format(ext=url.split(".")[-1], **film.asDict()) cmd = options.config["CMD_DOWNLOADS"].format(ziel=ziel, url=url) # Zielverzeichnis erstellen ziel_dir = os.path.dirname(ziel) if not os.path.exists(ziel_dir): os.mkdirs(ziel_dir) # Download ausführen options.filmDB.update_downloads(_id, 'A') Msg.msg("INFO", "Start Download (%s) %s" % (size, film.titel[0:50])) p = subprocess.Popen(shlex.split(cmd), stdout=DEVNULL, stderr=STDOUT) p.wait() rc = p.returncode Msg.msg( "INFO", "Ende Download (%s) %s (Return-Code: %d)" % (size, film.titel[0:50], rc)) if rc == 0: options.filmDB.update_downloads(_id, 'K') else: options.filmDB.update_downloads(_id, 'F') return rc
def del_datei(): """ Datei löschen """ # get name-parameter dateiname = bottle.request.forms.getunicode('name') Msg.msg("DEBUG", "Löschanforderung (Dateiname: %s)" % dateiname) bottle.response.content_type = 'application/json' if dateiname is None: msg = '"kein Dateiname angegeben"' bottle.response.status = 400 # bad request return '{"msg": ' + msg +'}' # Datei in der Aufname-DB suchen und dann löschen rows = options.filmDB.read_recs(dateiname) if not rows: Msg.msg("WARN", "Dateiname %s nicht in Film-DB" % dateiname) msg = '"Ungültiger Dateiname"' bottle.response.status = 400 # bad request elif os.path.exists(dateiname): os.unlink(dateiname) options.filmDB.delete_recs([(dateiname,)]) msg = '"Datei erfolgreich gelöscht"' bottle.response.status = 200 # OK Msg.msg("INFO", "Dateiname %s gelöscht" % dateiname) else: msg = '"Datei existiert nicht"' bottle.response.status = 400 # bad request Msg.msg("WARN", "Dateiname %s existiert nicht" % dateiname) return '{"msg": ' + msg +'}'
def dateien(): # Film-DB abfragen rows = options.filmDB.read_recs() if not rows: Msg.msg("DEBUG","Keine Dateien gefunden") return "{[]}" # Liste aufbereiten result = [] deleted = [] for row in rows: dateiname = row['DATEINAME'] if not os.path.exists(dateiname): Msg.msg("WARN","Datei %s existiert nicht" % dateiname) deleted.append((dateiname,)) continue item = {} item['DATEI'] = os.path.basename(dateiname) item['DATUMFILM'] = row['DATUMFILM'].strftime("%d.%m.%y") item['DATUMDATEI'] = row['DATUMDATEI'].strftime("%d.%m.%y") for key in ['SENDER','TITEL','BESCHREIBUNG','DATEINAME']: item[key] = row[key] result.append(item) # Löschen nicht mehr vorhandener Dateien if deleted: Msg.msg("DEBUG","Lösche %d Einträge in Aufnahmelisteliste" % len(deleted)) options.filmDB.delete_recs(deleted) # Ergebnis ausliefern Msg.msg("DEBUG","Anzahl Einträge in Dateilisteliste: %d" % len(result)) bottle.response.content_type = 'application/json' return json.dumps(result)
def do_later(options): """Filmliste anzeigen, Auswahl für späteren Download speichern""" rows = filme_suchen(options) if rows: if options.doBatch: selected = [('dummy', i) for i in range(len(rows))] else: selected = zeige_liste(rows) changes = save_selected(options.filmDB, rows, selected, "V") Msg.msg( "INFO", "%d von %d Filme vorgemerkt für den Download" % (changes, len(selected))) else: Msg.msg("INFO", "Keine Suchtreffer")
def delete_downloads(self,rows): """Downloads löschen""" DEL_STMT = "DELETE FROM downloads where _id=?" Msg.msg("DEBUG","rows: " + str(rows)) # Ein Lock ist hier nicht nötig, da Downloads immer in # einem eigene Aufruf von mtv_cli stattfinden cursor = self.open() cursor.executemany(DEL_STMT,rows) changes = self.db.total_changes self.commit() self.close() return changes
def loeschen(): # Auslesen Request-Parameter ids = bottle.request.forms.get('ids').split(" ") Msg.msg("DEBUG", "IDs: " + str(ids)) if len(ids): # delete_downloads braucht Array von Tuplen changes = options.filmDB.delete_downloads([(id, ) for id in ids]) else: changes = 0 Msg.msg("INFO", "%d vorgemerkte Filme gelöscht" % changes) bottle.response.content_type = 'application/json' msg = '"%d vorgemerkte Filme gelöscht"' % changes return '{"msg": ' + msg + '}'
def delete_recs(self, rows): """ Aufnahme löschen. rows ist Array von Tuplen: [(name,),(name,), ...]""" DEL_STMT = "DELETE FROM recordings where Dateiname=?" Msg.msg("DEBUG", "rows: " + str(rows)) # Ein Lock ist hier nicht nötig, da Downloads immer in # einem eigene Aufruf von mtv_cli stattfinden cursor = self.open() cursor.executemany(DEL_STMT, rows) changes = self.db.total_changes self.commit() self.close() return changes
def status(): result = {"_akt": "00.00.0000", "_anzahl": "0"} try: rows = options.filmDB.read_status(['_akt', '_anzahl']) for row in rows: key = row['key'] if key == "_akt": tstamp = row['Zeit'].strftime("%d.%m.%Y %H:%M:%S") result[key] = tstamp else: text = row['text'] result[key] = text except: pass Msg.msg("DEBUG", "Status: " + str(result)) bottle.response.content_type = 'application/json' return json.dumps(result)
def read_status(self,keys): """Status aus Status-Tabelle auslesen""" SEL_STMT = "SELECT * FROM status WHERE key in %s" % str(tuple(keys)) rows = None try: self.lock.acquire() cursor = self.open() cursor.execute(SEL_STMT) rows = cursor.fetchall() self.close() except sqlite3.OperationalError as e: Msg.msg("DEBUG","SQL-Fehler: %s" % e) rows = None finally: self.lock.release() return rows
def do_now(options): """Filmliste anzeigen, sofortiger Download nach Auswahl""" rows = filme_suchen(options) if rows: if options.doBatch: selected = [('dummy', i) for i in range(len(rows))] else: selected = zeige_liste(rows) changes = save_selected(options.filmDB, rows, selected, "S") Msg.msg( "INFO", "%d von %d Filme vorgemerkt für Sofort-Download" % (changes, len(selected))) # Anstoß Downlaod if changes > 0: do_download(options) else: Msg.msg("INFO", "Keine Suchtreffer")
def do_update(options): """Update der Filmliste""" if options.upd_src == "auto": src = random.choice(URL_FILMLISTE) elif options.upd_src == "json": # existierende Filmliste verwenden src = os.path.join(MTV_CLI_HOME, "filme.json") else: src = options.upd_src Msg.msg("INFO", "Erzeuge %s aus %s" % (options.dbfile, src)) try: if src.startswith("http"): fpin = get_lzma_fp(get_url_fp(src)) else: fpin = open(src, "r", encoding='utf-8') split_content(fpin, options.filmDB) finally: fpin.close()
def downloads(): # Film-DB abfragen rows = options.filmDB.read_downloads() if not rows: Msg.msg("DEBUG", "Keine vorgemerkten Filme vorhanden") return "{}" # Liste aufbereiten result = [] for row in rows: item = {} item['DATUM'] = row['DATUM'].strftime("%d.%m.%y") item['DATUMSTATUS'] = row['DATUMSTATUS'].strftime("%d.%m.%y") for key in ['STATUS', 'SENDER', 'THEMA', 'TITEL', 'DAUER', '_ID']: item[key] = row[key] result.append(item) Msg.msg("DEBUG", "Anzahl Einträge in Downloadliste: %d" % len(result)) bottle.response.content_type = 'application/json' return json.dumps(result)
def read_downloads(self,ui=True,status="'V','S','A','F','K'"): """Downloads auslesen. Falls ui=True, Subset für Anzeige. Bedeutung der Status-Codes: V - Vorgemerkt S - Sofort A - Aktiv F - Fehler K - Komplett """ # SQL-Teile if ui: SEL_STMT = """SELECT d.status as status, d.DatumStatus as DatumStatus, d._id as _id, f.sender as sender, f.thema as thema, f.titel as titel, f.dauer as dauer, f.datum as datum FROM filme as f, downloads as d WHERE f._id = d._id AND d.status in (%s) ORDER BY DatumStatus DESC""" % status else: SEL_STMT = """SELECT f.* FROM filme as f, downloads as d WHERE f._id = d._id AND d.status in (%s)""" % status Msg.msg("DEBUG","SQL-Query: %s" % SEL_STMT) cursor = self.open() try: cursor.execute(SEL_STMT) rows = cursor.fetchall() except sqlite3.OperationalError as e: Msg.msg("DEBUG","SQL-Fehler: %s" % e) rows = None self.close() if ui: return rows else: return [FilmInfo(*row) for row in rows]
def read_recs(self, Dateiname=None): """Aufnahmen auslesen. """ if Dateiname: SEL_STMT = "SELECT * from recordings where Dateiname=?" else: SEL_STMT = "SELECT * from recordings" Msg.msg("DEBUG", "SQL-Query: %s" % SEL_STMT) cursor = self.open() try: if Dateiname: cursor.execute(SEL_STMT, (Dateiname, )) else: cursor.execute(SEL_STMT) rows = cursor.fetchall() except sqlite3.OperationalError as e: Msg.msg("DEBUG", "SQL-Fehler: %s" % e) rows = None self.close() return rows
def download_filme(options,status="'V','F','A'"): # Filme lesen filme = options.filmDB.read_downloads(ui=False,status=status) if not filme: Msg.msg("INFO","Keine vorgemerkten Filme vorhanden") return if options.config["NUM_DOWNLOADS"] == 1: # Spezialbehandlung (erleichtert Debugging) for film in filme: download_film(options,film) else: with ThreadPool(options.config["NUM_DOWNLOADS"]) as pool: results = [] for film in filme: pool.apply_async(download_film,(options,film)) pool.close() pool.join() options.filmDB.save_status('_download')
def download_film(options,film): """Download eines einzelnen Films""" # Infos zusammensuchen _id = film._id size,url = film.get_url(options.config["QUALITAET"]) film.thema = film.thema.replace('/','_') film.titel = film.titel.replace('/','_') ext = url.split(".")[-1].lower() # Kommando bei Playlisten anpassen. Die Extension der gespeicherten Datei # wird auf mp4 geändert if ext.startswith('m3u'): cmd = options.config["CMD_DOWNLOADS_M3U"] ext = 'mp4' isM3U = True else: cmd = options.config["CMD_DOWNLOADS"] isM3U = False ziel = options.config["ZIEL_DOWNLOADS"].format(ext=ext, **film.asDict()) cmd = cmd.format(ziel=ziel,url=url) # Zielverzeichnis erstellen ziel_dir = os.path.dirname(ziel) if not os.path.exists(ziel_dir): os.mkdirs(ziel_dir) # Download ausführen options.filmDB.update_downloads(_id,'A') Msg.msg("INFO","Start Download (%s) %s" % (size,film.titel[0:50])) if isM3U: Msg.msg("DEBUG","Kommando: %s" % cmd) p = subprocess.Popen(cmd,shell=True,stdout=DEVNULL, stderr=STDOUT) else: Msg.msg("DEBUG","Kommando: %r" % shlex.split(cmd)) p = subprocess.Popen(shlex.split(cmd),stdout=DEVNULL, stderr=STDOUT) p.wait() rc = p.returncode Msg.msg("INFO", "Ende Download (%s) %s (Return-Code: %d)" % (size,film.titel[0:50],rc)) if rc==0: options.filmDB.update_downloads(_id,'K') options.filmDB.save_recs(_id,ziel) else: options.filmDB.update_downloads(_id,'F') return rc
def vormerken(): # Auslesen Request-Parameter ids = bottle.request.forms.get('ids').split(" ") dates = bottle.request.forms.get('dates').split(" ") Msg.msg("DEBUG", "IDs: " + str(ids)) Msg.msg("DEBUG", "Datum: " + str(dates)) inserts = [] i = 0 for id in ids: inserts.append((id, dates[i], 'V')) i += 1 Msg.msg("DEBUG", "inserts: " + str(inserts)) changes = options.filmDB.save_downloads(inserts) Msg.msg("DEBUG", "changes: " + str(changes)) bottle.response.content_type = 'application/json' msg = '"%d von %d Filme vorgemerkt für den Download"' % (changes, len(ids)) return '{"msg": ' + msg + '}'
if options.doVersionInfo: print("Version: %s" % VERSION) sys.exit(0) # Message-Klasse konfigurieren if options.level: Msg.level = options.level else: Msg.level = config["MSG_LEVEL"] # Verzeichnis HOME/.mediathek3 anlegen if not os.path.exists(MTV_CLI_HOME): os.mkdir(MTV_CLI_HOME) if not options.upd_src and not os.path.isfile(options.dbfile): Msg.msg("ERROR", "Datenbank %s existiert nicht!" % options.dbfile) sys.exit(3) # Lock anfordern if not get_lock(options.dbfile): Msg.msg("ERROR", "Datenbank %s ist gesperrt" % options.dbfile) sys.exit(3) # Globale Objekte anlegen options.config = config options.filmDB = FilmDB(options) if options.upd_src: do_update(options) elif options.doEdit: do_edit(options)
# Optionen lesen opt_parser = get_parser() options = opt_parser.parse_args(namespace=Options) # Message-Klasse konfigurieren if options.level: Msg.level = options.level else: Msg.level = config["MSG_LEVEL"] # Globale Objekte anlegen options.upd_src = "auto" options.config = config options.filmDB = FilmDB(options) # Server starten WEB_ROOT = get_webroot(__file__) Msg.msg("DEBUG", "Web-Root Verzeichnis: %s" % WEB_ROOT) if Msg.level == "DEBUG": Msg.msg("DEBUG", "Starte den Webserver im Debug-Modus") bottle.run(host='localhost', port=config["PORT"], debug=True, reloader=True) else: bottle.run(host=config["HOST"], port=config["PORT"], debug=False, reloader=False)
def get_query(self,suche): """Aus Suchbegriff eine SQL-Query erzeugen""" #Basisausdruck select_clause = "select * from Filme where " if not len(suche): return select_clause[0:-7] # remove " where " elif suche[0].lower().startswith("select"): # Suchausdruck ist fertige Query return ' '.join(suche) where_clause = "" op = "" for token in suche: if token in ["(","und","oder","and","or",")"]: if op: where_clause = where_clause + op op = " %s " % token continue if ':' in token: # Suche per Schlüsselwort key,value = token.split(":") if where_clause: where_clause = where_clause + (op if op else " and ") if key.upper() == "DATUM": # Sonderbehandlung Datum: if (">" in value) or ("<" in value) or ("=" in value): # datum:=xxx, datum:>xxx, datum:>=xxx usw. if value[1] in ["<",">","="]: date_op = value[0:2] value = value[2:] else: date_op = value[0] value = value[1:] where_clause = where_clause + "(%s %s '%s')" % \ (key,date_op,self.iso_date(value)) elif "-" in value: # datum:start-end limits = value.split("-") where_clause = where_clause + ( "(%s >= '%s' and %s <= '%s')" % (key,self.iso_date(limits[0]) ,key,self.iso_date(limits[1]))) else: # datum:xxx (identisch zu datum:=xxx) where_clause = where_clause + "(%s='%s')" % (key,self.iso_date(value)) else: where_clause = where_clause + "(%s like '%%%s%%')" % (key,value) else: # Volltextsuche if where_clause: where_clause = where_clause + (op if op else " or ") where_clause = where_clause + ( """(Sender like '%%%s%%' or Thema like '%%%s%%' or Titel like '%%%s%%' or Beschreibung like '%%%s%%')""" % (token,token,token,token)) op = "" # falls noch ein Operator übrig ist: if op: where_clause = where_clause + op Msg.msg("DEBUG","SQL-Where: %s" % where_clause) return select_clause + where_clause
def split_content(fpin, filmDB): """Inhalt aufteilen""" have_header = False last_rec = "" filmDB.create_filmtable() filmDB.isolation_level = None filmDB.cursor.execute("BEGIN;") total = 0 buf_count = 0 regex = re.compile(',\n? *"X" ?: ?') while True: # Buffer neu lesen buffer = fpin.read(BUFSIZE) buf_count = buf_count + 1 if not buf_count % 100: Msg.msg("INFO", '.', False) # Verarbeitung Dateiende (verbliebenen Satz schreiben) if len(buffer) == 0: if len(last_rec): total = total + 1 filmDB.insert_film(last_rec[0:-1]) break # Sätze aufspalten records = regex.split(last_rec + str(buffer)) Msg.msg("DEBUG", "Anzahl Sätze: %d" % len(records)) # Sätze ausgeben. Der letzte Satz ist entweder leer, # oder er ist eigentlich ein Satzanfang und wird aufgehoben last_rec = records[-1] for record in records[0:-1]: Msg.msg("TRACE", record) if not have_header: have_header = True continue total = total + 1 filmDB.insert_film(record) # Datensätze speichern und Datenbank schließen filmDB.commit() filmDB.save_filmtable() Msg.msg("INFO", "\n", False) Msg.msg("INFO", "Anzahl Buffer: %d" % buf_count) Msg.msg("INFO", "Anzahl Sätze (gesamt): %d" % total) Msg.msg("INFO", "Anzahl Sätze (gespeichert): %d" % filmDB.get_count())
def save_recs(self, id, Dateiname): """Aufnahme sichern.""" Msg.msg("INFO", "Sichere Aufnahmen: %s,%s" % (id, Dateiname)) CREATE_STMT = """CREATE TABLE IF NOT EXISTS recordings ( Sender text, Titel text, Beschreibung text, DatumFilm date, Dateiname text primary key, DatumDatei date)""" INSERT_STMT = """INSERT OR IGNORE INTO recordings Values (?,?,?,?,?,?)""" SEL_STMT = """SELECT sender, titel, beschreibung, datum FROM filme WHERE _id = ?""" # ausgewählte Felder aus Film-DB lesen cursor = self.open() try: Msg.msg("DEBUG", "SQL-Query: %s" % SEL_STMT) cursor.execute(SEL_STMT, (id, )) row = cursor.fetchone() except sqlite3.OperationalError as e: Msg.msg("DEBUG", "SQL-Fehler: %s" % e) row = None for r in row: Msg.msg("INFO", "row: %r" % r) if not row: self.close() return # Tabelle bei Bedarf erstellen Msg.msg("DEBUG", "SQL-Create: %s" % CREATE_STMT) cursor.execute(CREATE_STMT) self.commit() # ohne Lock, da Insert mit neuem Schlüssel try: self.lock.acquire() Msg.msg("DEBUG", "SQL-Insert: %s" % INSERT_STMT) cursor.execute( INSERT_STMT, tuple(row[i] for i in range(len(row))) + (Dateiname, datetime.date.today())) self.commit() except sqlite3.OperationalError as e: Msg.msg("DEBUG", "SQL-Fehler: %s" % e) finally: self.lock.release() self.close()