From 5af1e8f3e14c8067a7c0fadee6e3d40bcfb0552e Mon Sep 17 00:00:00 2001 From: Axel Belinfante Date: Sat, 6 Oct 2012 23:32:52 +0300 Subject: [PATCH 1/6] silence view_web remote url availability check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit avoid that the remote url availability check in view_web causes spurious log messages. without this, we get messages in the /tmp/screenly_viewer.log:  ...  Starting new HTTP connection (1): ... --- viewer.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/viewer.py b/viewer.py index a91e1dcc..69bfaa11 100755 --- a/viewer.py +++ b/viewer.py @@ -27,6 +27,11 @@ logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s', datefmt='%a, %d %b %Y %H:%M:%S') +# Silence urllib info messages ('Starting new HTTP connection') +# that are triggered by the remote url availability check in view_web +requests_log = logging.getLogger("requests") +requests_log.setLevel(logging.WARNING) + # Get config file config = ConfigParser.ConfigParser() conf_file = path.join(getenv('HOME'), '.screenly', 'screenly.conf') From 2475d8444adc27a4a80f548b7b03133535421b14 Mon Sep 17 00:00:00 2001 From: Axel Belinfante Date: Mon, 8 Oct 2012 10:48:03 +0300 Subject: [PATCH 2/6] only wait 60 seconds if we show the splash page --- viewer.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/viewer.py b/viewer.py index 69bfaa11..9cf4b406 100755 --- a/viewer.py +++ b/viewer.py @@ -96,8 +96,9 @@ def load_browser(): logging.info('Browser loaded. Running as PID %d.' % browser.pid) - # Show splash screen for 60 seconds. - sleep(60) + if show_splash: + # Show splash screen for 60 seconds. + sleep(60) return browser From 2f27f1fbbe879e2f3c0c259838a67fda839060cf Mon Sep 17 00:00:00 2001 From: Axel Belinfante Date: Mon, 8 Oct 2012 12:25:30 +0200 Subject: [PATCH 3/6] also without splash page, allow browser time to start --- viewer.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/viewer.py b/viewer.py index 9cf4b406..c4498691 100755 --- a/viewer.py +++ b/viewer.py @@ -99,6 +99,9 @@ def load_browser(): if show_splash: # Show splash screen for 60 seconds. sleep(60) + else: + # Give browser some time to start (we have seen multiple uzbl running without this) + sleep(10) return browser From b75213e2fb9cec41569428ace8db32ac2bfa4b6b Mon Sep 17 00:00:00 2001 From: Axel Belinfante Date: Sat, 13 Oct 2012 11:36:11 +0200 Subject: [PATCH 4/6] Introduced Scheduler class, to simplify viewing loop --- viewer.py | 90 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 60 insertions(+), 30 deletions(-) diff --git a/viewer.py b/viewer.py index c4498691..5a0a0651 100755 --- a/viewer.py +++ b/viewer.py @@ -32,6 +32,8 @@ logging.basicConfig(level=logging.INFO, requests_log = logging.getLogger("requests") requests_log.setLevel(logging.WARNING) +logging.debug('Starting viewer.py') + # Get config file config = ConfigParser.ConfigParser() conf_file = path.join(getenv('HOME'), '.screenly', 'screenly.conf') @@ -54,6 +56,37 @@ def str_to_bol(string): else: return False +class Scheduler(object): + def __init__(self, *args, **kwargs): + logging.debug('Scheduler init') + self.update_playlist() + + def get_next_asset(self): + logging.debug('get_next_asset') + self.refresh_playlist() + logging.debug('get_next_asset after refresh') + if self.nassets == 0: + return None + idx = self.index + self.index = (self.index + 1) % self.nassets + logging.debug('get_next_asset counter %d returning asset %d of %d' % (self.counter, idx+1, self.nassets)) + if self.index == 0: + self.counter += 1 + return self.assets[idx] + + def refresh_playlist(self): + logging.debug('refresh_playlist') + if self.counter >= 5: + self.update_playlist() + + def update_playlist(self): + logging.debug('update_playlist') + self.assets = generate_asset_list() + self.nassets = len(self.assets) + self.counter = 0 + self.index = 0 + logging.debug('update_playlist done, count %d, counter %d, index %d' % (self.nassets, self.counter, self.index)) + def generate_asset_list(): logging.info('Generating asset-list...') conn = sqlite3.connect(database, detect_types=sqlite3.PARSE_DECLTYPES) @@ -62,6 +95,7 @@ def generate_asset_list(): query = c.fetchall() playlist = [] + time_cur = time_lookup() for asset in query: asset_id = asset[0] name = asset[1].encode('ascii', 'ignore') @@ -72,9 +106,9 @@ def generate_asset_list(): duration = asset[6] mimetype = asset[7] - if (start_date and end_date) and (start_date < time_lookup() and end_date > time_lookup()): + if (start_date and end_date) and (start_date < time_cur and end_date > time_cur): playlist.append({"name" : name, "uri" : uri, "duration" : duration, "mimetype" : mimetype}) - + if shuffle_playlist: from random import shuffle shuffle(playlist) @@ -198,8 +232,6 @@ try: except: resolution = '1920x1080' -logging.debug('Starting viewer.py') - # Create folder to hold HTML-pages html_folder = '/tmp/screenly_html/' if not path.isdir(html_folder): @@ -217,36 +249,34 @@ browser_pid = run_browser.pid logging.debug('Getting FIFO.') fifo = get_fifo() +# Bring up the blank page (in case there are only videos). +logging.debug('Loading blank page.') +view_web(black_page, 1) + +logging.debug('Disable the browser status bar') +disable_browser_status() + +scheduler = Scheduler() + # Infinit loop. -# Break every 5th run to refresh database - +logging.debug('Entering infinite loop.') while True: - logging.debug('Entering infinite loop.') - # Bring up the blank page (in case there are only videos). - logging.debug('Loading blank page.') - view_web(black_page, 1) + asset = scheduler.get_next_asset() + logging.debug('got asset'+str(asset)) - assets = generate_asset_list() - - logging.debug('Disable the browser status bar') - disable_browser_status() - - # If the playlist is empty, go to sleep. - if len(assets) == 0: + if asset == None: + # The playlist is empty, go to sleep. logging.info('Playlist is empty. Going to sleep.') sleep(5) else: - counter = 1 - while counter <= 5: - logging.debug('Run counter: %d' % counter) - for asset in assets: - if "image" in asset["mimetype"]: - view_image(asset["uri"], asset["name"], asset["duration"]) - elif "video" in asset["mimetype"]: - view_video(asset["uri"]) - elif "web" in asset["mimetype"]: - view_web(asset["uri"], asset["duration"]) - else: - print "Unknown MimeType, or MimeType missing" - counter += 1 + logging.info('show asset %s' % asset["name"]) + + if "image" in asset["mimetype"]: + view_image(asset["uri"], asset["name"], asset["duration"]) + elif "video" in asset["mimetype"]: + view_video(asset["uri"]) + elif "web" in asset["mimetype"]: + view_web(asset["uri"], asset["duration"]) + else: + print "Unknown MimeType, or MimeType missing" From 12c6328d572f757a18b5bb29665a442969eb1af9 Mon Sep 17 00:00:00 2001 From: Axel Belinfante Date: Sat, 13 Oct 2012 12:39:03 +0200 Subject: [PATCH 5/6] let viewer poll server for playlist updates so they have more immediate effect TODO - compare new playlist with old one, to make smooth transition from old playlist to new one - factor out web address into variable --- server.py | 33 ++++++++++++++++++++++++++++++++- viewer.py | 10 ++++++++-- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/server.py b/server.py index 115c79b8..107b1a54 100644 --- a/server.py +++ b/server.py @@ -9,12 +9,13 @@ __email__ = "vpetersson@wireload.net" import sqlite3, ConfigParser from netifaces import ifaddresses -from sys import exit, platform +from sys import exit, platform, stdout from requests import get as req_get from os import path, getenv, makedirs, getloadavg, statvfs from hashlib import md5 from json import dumps, loads from datetime import datetime, timedelta +from time import time from bottle import route, run, debug, template, request, validate, error, static_file, get from dateutils import datestring from StringIO import StringIO @@ -36,6 +37,12 @@ configdir = path.join(getenv('HOME'), config.get('main', 'configdir')) database = path.join(getenv('HOME'), config.get('main', 'database')) nodetype = config.get('main', 'nodetype') +# get database last modification time +try: + db_mtime = path.getmtime(database) +except: + db_mtime = 0 + def time_lookup(): if nodetype == "standalone": return datetime.now() @@ -126,6 +133,7 @@ def get_assets(): return dumps(playlist) def initiate_db(): + global db_mtime # Create config dir if it doesn't exist if not path.isdir(configdir): @@ -140,10 +148,26 @@ def initiate_db(): if not asset_table: c.execute("CREATE TABLE assets (asset_id TEXT, name TEXT, uri TEXT, md5 TEXT, start_date TIMESTAMP, end_date TIMESTAMP, duration TEXT, mimetype TEXT)") + db_mtime = time() return "Initiated database." +@route('/dbisnewer/:t#[0-9]+(\.[0-9]+)?#') +def dbisnewer(t): + try: + if float(db_mtime) >= float(t): + res = 'yes' + else: + res = 'no' + except: + res = 'error' + + print 'dbisnewer t='+str(t)+' db_mtime='+str(db_mtime)+' : '+res + stdout.flush() + return res + @route('/process_asset', method='POST') def process_asset(): + global db_mtime conn = sqlite3.connect(database, detect_types=sqlite3.PARSE_DECLTYPES) c = conn.cursor() @@ -186,6 +210,7 @@ def process_asset(): c.execute("INSERT INTO assets (asset_id, name, uri, start_date, end_date, duration, mimetype) VALUES (?,?,?,?,?,?,?)", (asset_id, name, uri, start_date, end_date, duration, mimetype)) conn.commit() + db_mtime = time() header = "Yay!" message = "Added asset (" + asset_id + ") to the database." @@ -202,6 +227,7 @@ def process_asset(): @route('/process_schedule', method='POST') def process_schedule(): + global db_mtime conn = sqlite3.connect(database, detect_types=sqlite3.PARSE_DECLTYPES) c = conn.cursor() @@ -232,6 +258,7 @@ def process_schedule(): c.execute("UPDATE assets SET start_date=?, end_date=?, duration=? WHERE asset_id=?", (start_date, end_date, duration, asset_id)) conn.commit() + db_mtime = time() header = "Yes!" message = "Successfully scheduled asset." @@ -244,6 +271,7 @@ def process_schedule(): @route('/update_asset', method='POST') def update_asset(): + global db_mtime conn = sqlite3.connect(database, detect_types=sqlite3.PARSE_DECLTYPES) c = conn.cursor() @@ -277,6 +305,7 @@ def update_asset(): c.execute("UPDATE assets SET start_date=?, end_date=?, duration=?, name=?, uri=?, duration=?, mimetype=? WHERE asset_id=?", (start_date, end_date, duration, name, uri, duration, mimetype, asset_id)) conn.commit() + db_mtime = time() header = "Yes!" message = "Successfully updated asset." @@ -290,12 +319,14 @@ def update_asset(): @route('/delete_asset/:asset_id') def delete_asset(asset_id): + global db_mtime conn = sqlite3.connect(database, detect_types=sqlite3.PARSE_DECLTYPES) c = conn.cursor() c.execute("DELETE FROM assets WHERE asset_id=?", (asset_id,)) try: conn.commit() + db_mtime = time() header = "Success!" message = "Deleted asset." diff --git a/viewer.py b/viewer.py index 5a0a0651..cfc875a4 100755 --- a/viewer.py +++ b/viewer.py @@ -16,7 +16,7 @@ from os import stat as os_stat from subprocess import Popen, call import html_templates from datetime import datetime -from time import sleep +from time import sleep, time import logging from glob import glob from stat import S_ISFIFO @@ -76,13 +76,18 @@ class Scheduler(object): def refresh_playlist(self): logging.debug('refresh_playlist') - if self.counter >= 5: + dbisnewer = get("http://127.0.0.1:8080/dbisnewer/"+str(self.gentime)) + logging.info('dbisnewer: code (%d), text: (%s)' % (dbisnewer.status_code, dbisnewer.text)) + if dbisnewer.status_code == 200 and dbisnewer.text == "yes": + self.update_playlist() + elif self.counter >= 5: self.update_playlist() def update_playlist(self): logging.debug('update_playlist') self.assets = generate_asset_list() self.nassets = len(self.assets) + self.gentime = time() self.counter = 0 self.index = 0 logging.debug('update_playlist done, count %d, counter %d, index %d' % (self.nassets, self.counter, self.index)) @@ -106,6 +111,7 @@ def generate_asset_list(): duration = asset[6] mimetype = asset[7] + logging.debug('generate_asset_list: %s: start (%s) end (%s)' % (name, start_date, end_date)) if (start_date and end_date) and (start_date < time_cur and end_date > time_cur): playlist.append({"name" : name, "uri" : uri, "duration" : duration, "mimetype" : mimetype}) From 76b7eb0049fa6cfb484cf9f7f038796b7b718f81 Mon Sep 17 00:00:00 2001 From: Axel Belinfante Date: Sat, 13 Oct 2012 12:45:28 +0200 Subject: [PATCH 6/6] when not shuffling, only refresh playlist when it is changed, or deadline is reached --- viewer.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/viewer.py b/viewer.py index cfc875a4..6f06379b 100755 --- a/viewer.py +++ b/viewer.py @@ -70,7 +70,7 @@ class Scheduler(object): idx = self.index self.index = (self.index + 1) % self.nassets logging.debug('get_next_asset counter %d returning asset %d of %d' % (self.counter, idx+1, self.nassets)) - if self.index == 0: + if shuffle_playlist and self.index == 0: self.counter += 1 return self.assets[idx] @@ -78,19 +78,23 @@ class Scheduler(object): logging.debug('refresh_playlist') dbisnewer = get("http://127.0.0.1:8080/dbisnewer/"+str(self.gentime)) logging.info('dbisnewer: code (%d), text: (%s)' % (dbisnewer.status_code, dbisnewer.text)) + time_cur = time_lookup() + logging.debug('refresh: counter: (%d) deadline (%s) timecur (%s)' % (self.counter, self.deadline, time_cur)) if dbisnewer.status_code == 200 and dbisnewer.text == "yes": self.update_playlist() - elif self.counter >= 5: + elif shuffle_playlist and self.counter >= 5: + self.update_playlist() + elif self.deadline <= time_cur: self.update_playlist() def update_playlist(self): logging.debug('update_playlist') - self.assets = generate_asset_list() + (self.assets, self.deadline) = generate_asset_list() self.nassets = len(self.assets) self.gentime = time() self.counter = 0 self.index = 0 - logging.debug('update_playlist done, count %d, counter %d, index %d' % (self.nassets, self.counter, self.index)) + logging.debug('update_playlist done, count %d, counter %d, index %d, deadline %s' % (self.nassets, self.counter, self.index, self.deadline)) def generate_asset_list(): logging.info('Generating asset-list...') @@ -101,6 +105,7 @@ def generate_asset_list(): playlist = [] time_cur = time_lookup() + deadline = None for asset in query: asset_id = asset[0] name = asset[1].encode('ascii', 'ignore') @@ -114,12 +119,20 @@ def generate_asset_list(): logging.debug('generate_asset_list: %s: start (%s) end (%s)' % (name, start_date, end_date)) if (start_date and end_date) and (start_date < time_cur and end_date > time_cur): playlist.append({"name" : name, "uri" : uri, "duration" : duration, "mimetype" : mimetype}) + if (start_date and end_date) and (start_date < time_cur and end_date > time_cur): + if deadline == None or end_date < deadline: + deadline = end_date + if (start_date and end_date) and (start_date > time_cur and end_date > start_date): + if deadline == None or start_date < deadline: + deadline = start_date + + logging.debug('generate_asset_list deadline: %s' % deadline) if shuffle_playlist: from random import shuffle shuffle(playlist) - return playlist + return (playlist, deadline) def load_browser(): logging.info('Loading browser...')