From 80bcf21cb200e7b743b5f3d9cbf8d6c6b14e3019 Mon Sep 17 00:00:00 2001 From: amolodykh Date: Fri, 11 May 2018 19:10:28 +0600 Subject: [PATCH 001/224] Updated ansible and replaced webview --- .travis.yml | 4 +- Dockerfile.viewer | 53 +++++++++++ ansible/roles/network/tasks/main.yml | 4 +- ansible/roles/screenly/files/X.service | 11 --- ansible/roles/screenly/files/gtkrc-2.0 | 9 -- ansible/roles/screenly/files/matchbox.service | 12 --- .../screenly/files/screenly-viewer.service | 32 ------- ansible/roles/screenly/files/uzbl-config | 2 - ansible/roles/screenly/tasks/main.yml | 58 +++++++---- ansible/roles/screenly/vars/main.yml | 5 +- ansible/roles/system/files/rc.local | 3 + ansible/roles/system/tasks/main.yml | 41 ++------ bin/install.sh | 13 ++- bin/start_viewer.sh | 33 +++++++ html_templates.py | 23 ----- requirements.txt | 3 +- requirements.viewer.txt | 11 +++ server.py | 3 - viewer.py | 95 ++++++------------- 19 files changed, 193 insertions(+), 222 deletions(-) create mode 100644 Dockerfile.viewer delete mode 100644 ansible/roles/screenly/files/X.service delete mode 100644 ansible/roles/screenly/files/gtkrc-2.0 delete mode 100644 ansible/roles/screenly/files/matchbox.service delete mode 100644 ansible/roles/screenly/files/screenly-viewer.service delete mode 100644 ansible/roles/screenly/files/uzbl-config create mode 100644 bin/start_viewer.sh delete mode 100644 html_templates.py create mode 100644 requirements.viewer.txt diff --git a/.travis.yml b/.travis.yml index 97595b5dc..94a30afd3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,10 +15,10 @@ before_install: install: - pip install -U pip - pip install -r requirements.txt + - pip install -r requirements.viewer.txt - pip install -r requirements.dev.txt before_script: - - mkdir -p ~/.screenly ~/.config/uzbl/ ~/screenly_assets - - cp ansible/roles/screenly/files/uzbl-config ~/.config/uzbl/config-screenly + - mkdir -p ~/.screenly ~/screenly_assets - cp ansible/roles/screenly/files/screenly.conf ~/.screenly/ - cp ansible/roles/screenly/files/screenly.db ~/.screenly/ - cp ansible/roles/screenly/files/screenly_utils.sh /tmp/screenly_utils.sh diff --git a/Dockerfile.viewer b/Dockerfile.viewer new file mode 100644 index 000000000..6dd12b384 --- /dev/null +++ b/Dockerfile.viewer @@ -0,0 +1,53 @@ +FROM raspbian/stretch +MAINTAINER Anton Molodykh + +RUN apt-get update && \ + apt-get -y install \ + build-essential \ + curl \ + git-core \ + gstreamer0.10-plugins-good \ + libdbus-glib-1-dev \ + libffi-dev \ + libqt5webkit5 \ + libssl-dev \ + matchbox \ + net-tools \ + omxplayer \ + psmisc \ + python-dev \ + python-gi \ + python-netifaces \ + sqlite3 \ + x11-utils \ + x11-xserver-utils \ + xserver-xorg && \ + apt-get clean + +# Install Python requirements +ADD requirements.viewer.txt /tmp/requirements.txt +RUN curl -s https://bootstrap.pypa.io/get-pip.py | python && \ + pip install -r /tmp/requirements.txt + +# Create runtime user +RUN useradd pi -d /home/pi \ + && /usr/sbin/usermod -a -G video pi + +RUN mkdir /usr/local/share/ScreenlyWebview + +RUN wget -P /tmp https://github.com/antonmolodykh/Screenly-WebVew/releases/download/0.1/screenly_webview-v0.1.tar.gz +RUN tar -xzf /tmp/screenly_webview-v0.1.tar.gz -C /tmp + +RUN cp -r /tmp/res /usr/local/share/ScreenlyWebview/res +RUN cp /tmp/ScreenlyWebview /usr/local/bin/ScreenlyWebview + +# Fix white flickering in omxplayer +ENV NOREFRESH 1 + +# Copy X11 configs +COPY ansible/roles/system/files/10-serverflags.conf /usr/share/X11/xorg.conf.d/10-serverflags.conf +COPY ansible/roles/system/files/10-evdev.conf /usr/share/X11/xorg.conf.d/10-evdev.conf + +WORKDIR /home/pi/screenly + +CMD ["bash", "bin/start_viewer.sh"] diff --git a/ansible/roles/network/tasks/main.yml b/ansible/roles/network/tasks/main.yml index 9db34ff17..b8350094e 100644 --- a/ansible/roles/network/tasks/main.yml +++ b/ansible/roles/network/tasks/main.yml @@ -103,4 +103,6 @@ dest: "/etc/systemd/system/wifi-connect.service" - name: Enable wifi-connect systemd service - command: systemctl enable wifi-connect.service chdir=/etc/systemd/system + systemd: + name: wifi-connect.service + enabled: yes diff --git a/ansible/roles/screenly/files/X.service b/ansible/roles/screenly/files/X.service deleted file mode 100644 index b59dfee83..000000000 --- a/ansible/roles/screenly/files/X.service +++ /dev/null @@ -1,11 +0,0 @@ -[Unit] -Description=X11 -After=network.target - -[Service] -ExecStart=/usr/bin/X -User=root -Restart=on-failure - -[Install] -WantedBy=multi-user.target \ No newline at end of file diff --git a/ansible/roles/screenly/files/gtkrc-2.0 b/ansible/roles/screenly/files/gtkrc-2.0 deleted file mode 100644 index 8d399f94f..000000000 --- a/ansible/roles/screenly/files/gtkrc-2.0 +++ /dev/null @@ -1,9 +0,0 @@ -style "uzbl" { - GtkScrollbar::slider-width=0 - GtkScrollbar::trough-border=0 - GtkScrollbar::has-backward-stepper=0 - GtkScrollbar::has-forward-stepper=0 - GtkScrollbar::has-secondary-backward-stepper=0 - GtkScrollbar::has-secondary-forward-stepper=0 -} -widget "Uzbl*" style "uzbl" diff --git a/ansible/roles/screenly/files/matchbox.service b/ansible/roles/screenly/files/matchbox.service deleted file mode 100644 index 6bb0b06df..000000000 --- a/ansible/roles/screenly/files/matchbox.service +++ /dev/null @@ -1,12 +0,0 @@ -[Unit] -Description=X11 -After=X.service - -[Service] -Environment=DISPLAY=:0.0 -ExecStart=/usr/bin/matchbox-window-manager -use_titlebar no -use_cursor no -User=pi -Restart=on-failure - -[Install] -WantedBy=multi-user.target diff --git a/ansible/roles/screenly/files/screenly-viewer.service b/ansible/roles/screenly/files/screenly-viewer.service deleted file mode 100644 index e606d80b2..000000000 --- a/ansible/roles/screenly/files/screenly-viewer.service +++ /dev/null @@ -1,32 +0,0 @@ -[Unit] -Description=Screenly Viewer -After=matchbox.service screenly-web.service - -[Service] -WorkingDirectory=/home/pi/screenly -User=pi - -Environment=DISPLAY=:0.0 - -# Fix white flickering in omxplayer -Environment=NOREFRESH=1 - -Environment=PYTHONPATH=/home/pi/screenly - -# Don't activate screensaver -ExecStartPre=/usr/bin/xset s off - -# Disable DPMS (Energy Star) features -ExecStartPre=/usr/bin/xset -dpms - -# Don't blank the video device -ExecStartPre=/usr/bin/xset s noblank - -ExecStart=/usr/bin/python /home/pi/screenly/viewer.py -Restart=on-failure - -ExecStartPost=/bin/rm -f /tmp/uzbl_* -ExecStartPost=/bin/rm -f /tmp/screenly_html/* - -[Install] -WantedBy=multi-user.target diff --git a/ansible/roles/screenly/files/uzbl-config b/ansible/roles/screenly/files/uzbl-config deleted file mode 100644 index 6d6609434..000000000 --- a/ansible/roles/screenly/files/uzbl-config +++ /dev/null @@ -1,2 +0,0 @@ -set show_status = 0 -set ssl_verify = 1 diff --git a/ansible/roles/screenly/tasks/main.yml b/ansible/roles/screenly/tasks/main.yml index ae5c1ad04..9bed3934f 100644 --- a/ansible/roles/screenly/tasks/main.yml +++ b/ansible/roles/screenly/tasks/main.yml @@ -1,13 +1,9 @@ -- name: Ensure folders exist +- name: Ensure folder exist file: - path: "/home/pi/{{ item }}" + path: "/home/pi/.screenly" state: directory owner: pi group: pi - with_items: - - .screenly - - .config - - .config/uzbl - name: Copy Screenly default config copy: @@ -23,20 +19,6 @@ state: absent dest: /home/pi/.screenly/screenly.conf -- name: Copy in GTK config - copy: - owner: pi - group: pi - src: gtkrc-2.0 - dest: /home/pi/.gtkrc-2.0 - -- name: Copy in UZBL config - copy: - owner: pi - group: pi - src: uzbl-config - dest: /home/pi/.config/uzbl/config-screenly - - name: Install pip dependencies pip: requirements=/home/pi/screenly/requirements.txt @@ -85,5 +67,39 @@ with_items: "{{ screenly_systemd_units }}" - name: Enable screenly systemd services - command: systemctl enable {{ item }} chdir=/etc/systemd/system + systemd: + name: "{{ item }}" + enabled: yes with_items: "{{ screenly_systemd_units }}" + +- name: Disable deprecated systemd services + systemd: + name: "{{ item }}" + enabled: no + with_items: + - screenly-viewer.service + - matchbox.service + - X.service + +- name: Remove deprecated systemd units + file: + path: "/etc/systemd/system/{{ item }}" + state: absent + with_items: + - screenly-viewer.service + - matchbox.service + - X.service + +- name: Create screenly-viewer container + docker_container: + name: screenly-viewer + image: amolodykh/screenly-viewer + pull: true + state: present + restart_policy: unless-stopped + network_mode: host + privileged: yes + volumes: + - /home/pi/screenly:/home/pi/screenly + - /home/pi/.screenly:/home/pi/.screenly + - /home/pi/screenly_assets:/home/pi/screenly_assets diff --git a/ansible/roles/screenly/vars/main.yml b/ansible/roles/screenly/vars/main.yml index 833bb6d8b..bf3e5aaf6 100644 --- a/ansible/roles/screenly/vars/main.yml +++ b/ansible/roles/screenly/vars/main.yml @@ -1,6 +1,3 @@ screenly_systemd_units: - - X.service - - matchbox.service - - screenly-viewer.service - screenly-web.service - - screenly-websocket_server_layer.service \ No newline at end of file + - screenly-websocket_server_layer.service diff --git a/ansible/roles/system/files/rc.local b/ansible/roles/system/files/rc.local index 46b4e9848..ea1378fa1 100644 --- a/ansible/roles/system/files/rc.local +++ b/ansible/roles/system/files/rc.local @@ -3,6 +3,9 @@ # Make sure the SSH host keys have been generated ssh-keygen -A +# Solves issue with black square in the upper left-hand corner +/bin/chvt 8 ; sleep 0.1 ; /bin/chvt 7 + # Disable wifi power management iwconfig wlan0 power off diff --git a/ansible/roles/system/tasks/main.yml b/ansible/roles/system/tasks/main.yml index 7a71c7077..f95c58b70 100644 --- a/ansible/roles/system/tasks/main.yml +++ b/ansible/roles/system/tasks/main.yml @@ -150,17 +150,10 @@ - console-data - libffi-dev - libssl-dev - - matchbox - - omxplayer - python-dev - - python-netifaces - - python-simplejson - rpi-update - sqlite3 - systemd - - uzbl - - x11-xserver-utils - - xserver-xorg - name: Install systemd-sysv on Wheezy command: bash -c 'echo "Yes, do as I say!" | apt-get install -y --force-yes systemd-sysv' @@ -171,10 +164,15 @@ name: "{{ item }}" state: absent with_items: - - supervisor + - dphys-swapfile - lightdm - lightdm-gtk-greeter - - dphys-swapfile + - matchbox + - omxplayer + - supervisor + - uzbl + - x11-xserver-utils + - xserver-xorg - name: Perform system upgrade apt: @@ -208,31 +206,6 @@ owner: root group: root -- name: Copy in evdev - copy: - src: 10-evdev.conf - dest: /usr/share/X11/xorg.conf.d/10-evdev.conf - mode: 0644 - owner: root - group: root - -- name: Disable DPMS - copy: - src: 10-serverflags.conf - dest: /usr/share/X11/xorg.conf.d/10-serverflags.conf - mode: 0644 - owner: root - group: root - -- name: Clear out X11 configs (disables touchpad and other unnecessary things) - file: - path: "/usr/share/X11/xorg.conf.d/{{ item }}" - state: absent - with_items: - - 50-synaptics.conf - - 10-quirks.conf - - 50-wacom.conf - - name: Disable swap command: /sbin/swapoff --all removes=/var/swap diff --git a/bin/install.sh b/bin/install.sh index 314f40718..1c31eef91 100755 --- a/bin/install.sh +++ b/bin/install.sh @@ -65,7 +65,18 @@ sudo apt-get update sudo apt-get purge -y python-setuptools python-pip python-pyasn1 sudo apt-get install -y python-dev git-core libffi-dev libssl-dev curl -s https://bootstrap.pypa.io/get-pip.py | sudo python -sudo pip install ansible==2.1.0.0 + +docker -v > /dev/null +if [ "$?" != '0' ]; then + sudo apt-get install -y apt-transport-https ca-certificates curl + curl -fsSL "https://download.docker.com/linux/raspbian/gpg" | apt-key add -qq - >/dev/null + echo "deb [arch=armhf] https://download.docker.com/linux/raspbian stretch edge" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + sudo apt-get update + sudo apt-get install -y -qq --no-install-recommends docker-ce + sudo usermod -aG docker pi +fi + +sudo pip install ansible==2.5.2 ansible localhost -m git -a "repo=${1:-https://github.com/screenly/screenly-ose.git} dest=/home/pi/screenly version=$BRANCH" cd /home/pi/screenly/ansible diff --git a/bin/start_viewer.sh b/bin/start_viewer.sh new file mode 100644 index 000000000..28dd5b83e --- /dev/null +++ b/bin/start_viewer.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# SUGUSR1 from the viewer is also sent to the container +# Prevent it so that the container does not fall +trap '' 16 + +# Start X +rm -f /tmp/.X0-lock +/usr/bin/X & +export DISPLAY=:0.0 + +# Waiting for X11 +while ! xdpyinfo >/dev/null 2>&1; do + sleep 0.5 +done + +su - pi -c " /usr/bin/matchbox-window-manager -use_titlebar no -use_cursor no &" + +su - pi -c "cd /home/pi/screenly && dbus-run-session python viewer.py &" + +# Waiting for the viewer +while true; do + PID=$(pidof python) + if [ "$?" == '0' ]; then + break + fi + sleep 0.5 +done + +# Exit when the viewer falls +while kill -0 "$PID"; do + sleep 1 +done diff --git a/html_templates.py b/html_templates.py deleted file mode 100644 index 29ddbe967..000000000 --- a/html_templates.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf8 -*- - - -def black_page(filepath): - html = """ - - - - -""" - - with open(filepath, 'w') as f: - f.write(html) - return filepath diff --git a/requirements.txt b/requirements.txt index 84cc51351..bd89bcd10 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,9 @@ -Mako==0.7.3 MarkupSafe==1.0 ansible==2.5.3 certifi==2018.4.16 cffi==1.7.0 configparser==3.5.0 +docker-py==1.10.6 Flask-Cors==3.0.4 flask-restful-swagger-2==0.35 flask-swagger-ui==3.0.12 @@ -14,7 +14,6 @@ gevent-websocket==0.10.1 gevent==1.2.2 gunicorn==19.8.1 hurry.filesize==0.9 -mixpanel==4.3.2 netifaces==0.10.4 pwgen==0.8.2.post0 pyasn1==0.1.8 diff --git a/requirements.viewer.txt b/requirements.viewer.txt new file mode 100644 index 000000000..1b065e278 --- /dev/null +++ b/requirements.viewer.txt @@ -0,0 +1,11 @@ +certifi==2018.4.16 +configparser==3.5.0 +flask==0.12.2 +mixpanel==4.3.2 +netifaces==0.10.4 +python-dateutil==2.4.2 +pytz==2012d +pyzmq==16.0.2 +pydbus==0.6.0 +requests[security]==2.18.4 +sh==1.08 \ No newline at end of file diff --git a/server.py b/server.py index 4cde0b82c..8450a3e66 100755 --- a/server.py +++ b/server.py @@ -12,11 +12,8 @@ import json from mimetypes import guess_type from os import getenv, makedirs, mkdir, path, remove, rename, statvfs -from pwgen import pwgen -import sh from sh import git from subprocess import check_output -from time import sleep import traceback import uuid diff --git a/viewer.py b/viewer.py index 8e36f958f..9b0f1e0d5 100755 --- a/viewer.py +++ b/viewer.py @@ -9,17 +9,16 @@ from mixpanel import Mixpanel, MixpanelException from netifaces import gateways -from requests import get as req_get from signal import signal, SIGUSR1 from time import sleep import logging +from pydbus import SessionBus import random import sh import string import zmq from settings import settings, LISTEN, PORT -import html_templates from lib.github import fetch_remote_hash, remote_branch_available from lib.utils import url_fails, touch, is_ci from lib import db @@ -35,16 +34,14 @@ EMPTY_PL_DELAY = 5 # secs INITIALIZED_FILE = '/.screenly/initialized' -BLACK_PAGE = '/tmp/screenly_html/black_page.html' WATCHDOG_PATH = '/tmp/screenly.watchdog' -SCREENLY_HTML = '/tmp/screenly_html/' LOAD_SCREEN = '/screenly/loading.png' # relative to $HOME -UZBLRC = '/.config/uzbl/config-screenly' # relative to $HOME -INTRO = '/screenly/intro-template.html' current_browser_url = None browser = None +browser_bus = None + VIDEO_TIMEOUT = 20 # secs HOME = None @@ -217,66 +214,35 @@ def watchdog(): utime(WATCHDOG_PATH, None) -def load_browser(url=None): - global browser, current_browser_url +def load_browser(): + global browser logging.info('Loading browser...') - if browser: - logging.info('killing previous uzbl %s', browser.pid) - browser.process.kill() - - if url is not None: - current_browser_url = url - - # --config=- read commands (and config) from stdin - # --print-events print events to stdout - browser = sh.Command('uzbl-browser')(print_events=True, config='-', uri=current_browser_url, _bg=True) - logging.info('Browser loading %s. Running as PID %s.', current_browser_url, browser.pid) - - uzbl_rc = 'set ssl_verify = {}\n'.format('1' if settings['verify_ssl'] else '0') - with open(HOME + UZBLRC) as f: # load uzbl.rc - uzbl_rc = f.read() + uzbl_rc - browser_send(uzbl_rc) + browser = sh.Command('ScreenlyWebview')(_bg=True, _err_to_out=True) + while 'Screenly service start' not in browser.process.stdout: + sleep(1) -def browser_send(command, cb=lambda _: True): - if not (browser is None) and browser.process.alive: - while not browser.process._pipe_queue.empty(): # flush stdout - browser.next() +def view_webpage(uri): + global current_browser_url - browser.process.stdin.put(command + '\n') - while True: # loop until cb returns True - if cb(browser.next()): - break - else: - logging.info('browser found dead, restarting') + if browser is None or not browser.process.alive: load_browser() + if current_browser_url is not uri: + browser_bus.loadPage(uri) + current_browser_url = uri + logging.info('current url is {0}'.format(current_browser_url)) -def browser_clear(force=False): - """Load a black page. Default cb waits for the page to load.""" - browser_url('file://' + BLACK_PAGE, force=force, cb=lambda buf: 'LOAD_FINISH' in buf and BLACK_PAGE in buf) - - -def browser_url(url, cb=lambda _: True, force=False): +def view_image(uri): global current_browser_url - if url == current_browser_url and not force: - logging.debug('Already showing %s, reloading it.', current_browser_url) - else: - current_browser_url = url - - """Uzbl handles full URI format incorrect: scheme://uname:passwd@domain:port/path - We need to escape @""" - escaped_url = current_browser_url.replace('@', '\\@') - - browser_send('uri ' + escaped_url, cb=cb) - logging.info('current url is %s', current_browser_url) - - -def view_image(uri): - browser_clear() - browser_send('js window.setimg("{0}")'.format(uri), cb=lambda b: 'COMMAND_EXECUTED' in b and 'setimg' in b) + if browser is None or not browser.process.alive: + load_browser() + if current_browser_url is not uri: + browser_bus.loadImage(uri) + current_browser_url = uri + logging.info('current url is {0}'.format(current_browser_url)) def view_video(uri, duration): @@ -294,7 +260,7 @@ def view_video(uri, duration): run = sh.Command(player_args[0])(*player_args[1:], **player_kwargs) - browser_clear(force=True) + browser_bus.loadImage('null') try: while run.process.alive: watchdog() @@ -393,9 +359,7 @@ def asset_loop(scheduler): if 'image' in mime: view_image(uri) elif 'web' in mime: - # FIXME If we want to force periodic reloads of repeated web assets, force=True could be used here. - # See e38e6fef3a70906e7f8739294ffd523af6ce66be. - browser_url(uri) + view_webpage(uri) elif 'video' or 'streaming' in mime: view_video(uri, asset['duration']) else: @@ -411,7 +375,7 @@ def asset_loop(scheduler): def setup(): - global HOME, arch, db_conn + global HOME, arch, db_conn, browser_bus HOME = getenv('HOME', '/home/pi') arch = machine() @@ -420,8 +384,9 @@ def setup(): load_settings() db_conn = db.conn(settings['database']) - sh.mkdir(SCREENLY_HTML, p=True) - html_templates.black_page(BLACK_PAGE) + load_browser() + bus = SessionBus() + browser_bus = bus.get('screenly.webview', '/Screenly') def main(): @@ -429,13 +394,13 @@ def main(): if not path.isfile(HOME + INITIALIZED_FILE) and not gateways().get('default'): url = 'http://{0}/hotspot'.format(LISTEN) - load_browser(url=url) + view_webpage(url) while not path.isfile(HOME + INITIALIZED_FILE): sleep(1) url = 'http://{0}:{1}/splash_page'.format(LISTEN, PORT) if settings['show_splash'] else 'file://' + BLACK_PAGE - browser_url(url=url) + view_webpage(url) if settings['show_splash']: sleep(SPLASH_DELAY) From 2f8555a5c1b8ac8d38f564642320f2a8eb4dd0af Mon Sep 17 00:00:00 2001 From: amolodykh Date: Tue, 15 May 2018 19:52:32 +0600 Subject: [PATCH 002/224] Move all things to docker --- .travis.yml | 5 ++- Dockerfile => Dockerfile.server | 22 +++------- Dockerfile.template | 8 +++- Dockerfile.viewer | 8 +++- Dockerfile.websocket_server | 23 ++++++++++ ansible/roles/nginx/tasks/main.yml | 7 --- ansible/roles/screenly/tasks/main.yml | 61 ++++++++++++++++++--------- ansible/roles/screenly/vars/main.yml | 5 ++- ansible/roles/ssl/tasks/main.yml | 12 ------ ansible/roles/system/tasks/main.yml | 7 +-- ansible/site.yml | 18 -------- bin/install.sh | 6 +-- requirements.server.txt | 22 ++++++++++ requirements.txt | 25 ----------- requirements.websocket_server.txt | 7 +++ server.py | 28 ++++++------ templates/system_info.html | 4 +- 17 files changed, 137 insertions(+), 131 deletions(-) rename Dockerfile => Dockerfile.server (57%) create mode 100644 Dockerfile.websocket_server create mode 100644 requirements.server.txt create mode 100644 requirements.websocket_server.txt diff --git a/.travis.yml b/.travis.yml index 94a30afd3..6fd44d345 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,11 +11,12 @@ addons: before_install: - sudo add-apt-repository ppa:mc3man/trusty-media -y - sudo apt-get update -q - - sudo apt-get install ffmpeg + - sudo apt-get install ffmpeg python-gi python-dev install: - pip install -U pip - - pip install -r requirements.txt + - pip install ansible==2.5.2 - pip install -r requirements.viewer.txt + - pip install -r requirements.server.txt - pip install -r requirements.dev.txt before_script: - mkdir -p ~/.screenly ~/screenly_assets diff --git a/Dockerfile b/Dockerfile.server similarity index 57% rename from Dockerfile rename to Dockerfile.server index 5edfceac1..85fdd6d04 100644 --- a/Dockerfile +++ b/Dockerfile.server @@ -1,44 +1,32 @@ -FROM debian:stretch -MAINTAINER Viktor Petersson +FROM raspbian/stretch +MAINTAINER Anton Molodykh RUN apt-get update && \ apt-get -y install \ build-essential \ curl \ - ffmpeg \ git-core \ libffi-dev \ libssl-dev \ - mplayer \ net-tools \ - procps \ python-dev \ - python-imaging \ python-netifaces \ - python-simplejson \ - sqlite3 \ - && \ + sqlite3 && \ apt-get clean # Install Python requirements -ADD requirements.txt /tmp/requirements.txt +ADD requirements.server.txt /tmp/requirements.txt RUN curl -s https://bootstrap.pypa.io/get-pip.py | python && \ pip install -r /tmp/requirements.txt # Create runtime user RUN useradd pi -# Install config file and file structure RUN mkdir -p /home/pi/.screenly /home/pi/screenly /home/pi/screenly_assets -COPY ansible/roles/screenly/files/screenly.conf /home/pi/.screenly/screenly.conf - -# Copy in code base -COPY . /home/pi/screenly RUN chown -R pi:pi /home/pi USER pi -WORKDIR /home/pi/screenly -EXPOSE 8080 +WORKDIR /home/pi/screenly CMD python server.py diff --git a/Dockerfile.template b/Dockerfile.template index 6ac2eb068..2b125eed5 100644 --- a/Dockerfile.template +++ b/Dockerfile.template @@ -27,9 +27,13 @@ RUN rm /etc/nginx/sites-enabled/default COPY ansible/roles/ssl/files/nginx_resin.conf /etc/nginx/sites-enabled/screenly.conf # Install Python requirements -ADD requirements.txt /tmp/requirements.txt +ADD requirements.viewer.txt /tmp/requirements.viewer.txt +ADD requirements.server.txt /tmp/requirements.server.txt +ADD requirements.websocket_server.txt /tmp/requirements.websocket_server.txt RUN curl -s https://bootstrap.pypa.io/get-pip.py | python && \ - pip install --upgrade -r /tmp/requirements.txt + pip install --upgrade -r /tmp/requirements.viewer.txt && \ + pip install --upgrade -r /tmp/requirements.server.txt && \ + pip install --upgrade -r /tmp/requirements.websocket_server.txt # Screenly Websocker Server COPY ansible/roles/screenly/files/screenly-websocket_server_layer.service /etc/systemd/system/screenly-websocket_server_layer.service diff --git a/Dockerfile.viewer b/Dockerfile.viewer index 6dd12b384..b4671afbc 100644 --- a/Dockerfile.viewer +++ b/Dockerfile.viewer @@ -10,6 +10,7 @@ RUN apt-get update && \ libdbus-glib-1-dev \ libffi-dev \ libqt5webkit5 \ + libraspberrypi0 \ libssl-dev \ matchbox \ net-tools \ @@ -32,11 +33,14 @@ RUN curl -s https://bootstrap.pypa.io/get-pip.py | python && \ # Create runtime user RUN useradd pi -d /home/pi \ && /usr/sbin/usermod -a -G video pi +RUN mkdir -p /home/pi/.screenly /home/pi/screenly /home/pi/screenly_assets + +RUN chown -R pi:pi /home/pi RUN mkdir /usr/local/share/ScreenlyWebview -RUN wget -P /tmp https://github.com/antonmolodykh/Screenly-WebVew/releases/download/0.1/screenly_webview-v0.1.tar.gz -RUN tar -xzf /tmp/screenly_webview-v0.1.tar.gz -C /tmp +RUN wget -P /tmp https://github.com/antonmolodykh/Screenly-WebVew/releases/download/0.1/screenly-webview-v0.1.tar.gz +RUN tar -xzf /tmp/screenly-webview-v0.1.tar.gz -C /tmp RUN cp -r /tmp/res /usr/local/share/ScreenlyWebview/res RUN cp /tmp/ScreenlyWebview /usr/local/bin/ScreenlyWebview diff --git a/Dockerfile.websocket_server b/Dockerfile.websocket_server new file mode 100644 index 000000000..4eed4c082 --- /dev/null +++ b/Dockerfile.websocket_server @@ -0,0 +1,23 @@ +FROM raspbian/stretch +MAINTAINER Anton Molodykh + +RUN apt-get update && \ + apt-get -y install \ + build-essential \ + curl \ + libffi-dev \ + python-dev && \ + apt-get clean + +# Install Python requirements +ADD requirements.websocket_server.txt /tmp/requirements.txt +RUN curl -s https://bootstrap.pypa.io/get-pip.py | python && \ + pip install -r /tmp/requirements.txt + +# Create runtime user +RUN useradd pi + +USER pi +WORKDIR /home/pi/screenly + +CMD python websocket_server_layer.py diff --git a/ansible/roles/nginx/tasks/main.yml b/ansible/roles/nginx/tasks/main.yml index dc2561c97..18e610a30 100644 --- a/ansible/roles/nginx/tasks/main.yml +++ b/ansible/roles/nginx/tasks/main.yml @@ -24,10 +24,3 @@ mode: 644 owner: root group: root - -- name: Modifies screenly-web service to only listen on localhost - lineinfile: - regexp: '^.*LISTEN.*' - state: absent - dest: /etc/systemd/system/screenly-web.service - when: no_ssl diff --git a/ansible/roles/screenly/tasks/main.yml b/ansible/roles/screenly/tasks/main.yml index 9bed3934f..586dafe21 100644 --- a/ansible/roles/screenly/tasks/main.yml +++ b/ansible/roles/screenly/tasks/main.yml @@ -1,9 +1,12 @@ -- name: Ensure folder exist +- name: Ensure folders exist file: - path: "/home/pi/.screenly" + path: "/home/pi/{{ item }}" state: directory owner: pi group: pi + with_items: + - .screenly + - screenly_assets - name: Copy Screenly default config copy: @@ -60,35 +63,39 @@ owner: root group: root -- name: Copy screenly systemd units - copy: - src: "{{ item }}" - dest: "/etc/systemd/system/{{ item }}" - with_items: "{{ screenly_systemd_units }}" +- name: Check if deprecated systemd services exists + stat: + path: /etc/systemd/system/X.service + register: X_service -- name: Enable screenly systemd services - systemd: - name: "{{ item }}" - enabled: yes - with_items: "{{ screenly_systemd_units }}" +- set_fact: X_service_exist="{{X_service.stat.exists}}" - name: Disable deprecated systemd services systemd: name: "{{ item }}" enabled: no - with_items: - - screenly-viewer.service - - matchbox.service - - X.service + with_items: "{{ screenly_systemd_units }}" + when: X_service_exist - name: Remove deprecated systemd units file: path: "/etc/systemd/system/{{ item }}" state: absent - with_items: - - screenly-viewer.service - - matchbox.service - - X.service + with_items: "{{ screenly_systemd_units }}" + +- name: Create screenly-server container + docker_container: + name: screenly-server + image: amolodykh/screenly-server + pull: true + state: present + recreate: true + restart_policy: unless-stopped + network_mode: host + volumes: + - /home/pi/screenly:/home/pi/screenly + - /home/pi/.screenly:/home/pi/.screenly + - /home/pi/screenly_assets:/home/pi/screenly_assets - name: Create screenly-viewer container docker_container: @@ -96,6 +103,7 @@ image: amolodykh/screenly-viewer pull: true state: present + recreate: true restart_policy: unless-stopped network_mode: host privileged: yes @@ -103,3 +111,16 @@ - /home/pi/screenly:/home/pi/screenly - /home/pi/.screenly:/home/pi/.screenly - /home/pi/screenly_assets:/home/pi/screenly_assets + +- name: Create screenly-websocket-server container + docker_container: + name: screenly-websocket-server + image: amolodykh/screenly-websocket-server + pull: true + state: present + recreate: true + restart_policy: unless-stopped + network_mode: host + volumes: + - /home/pi/screenly:/home/pi/screenly + - /home/pi/.screenly:/home/pi/.screenly diff --git a/ansible/roles/screenly/vars/main.yml b/ansible/roles/screenly/vars/main.yml index bf3e5aaf6..833bb6d8b 100644 --- a/ansible/roles/screenly/vars/main.yml +++ b/ansible/roles/screenly/vars/main.yml @@ -1,3 +1,6 @@ screenly_systemd_units: + - X.service + - matchbox.service + - screenly-viewer.service - screenly-web.service - - screenly-websocket_server_layer.service + - screenly-websocket_server_layer.service \ No newline at end of file diff --git a/ansible/roles/ssl/tasks/main.yml b/ansible/roles/ssl/tasks/main.yml index ace2021f0..48126ce66 100644 --- a/ansible/roles/ssl/tasks/main.yml +++ b/ansible/roles/ssl/tasks/main.yml @@ -68,15 +68,3 @@ tags: - enable-ssl when: not no_use_ssl_parameter - -- name: Modifies screenly-web service to only listen on localhost - lineinfile: - regexp: '^.*LISTEN.*' - state: absent - dest: /etc/systemd/system/screenly-web.service - notify: - - reload systemctl - - restart-screenly-websocket_server_layer - - restart-screenly-server - tags: - - enable-ssl diff --git a/ansible/roles/system/tasks/main.yml b/ansible/roles/system/tasks/main.yml index f95c58b70..77ebf0667 100644 --- a/ansible/roles/system/tasks/main.yml +++ b/ansible/roles/system/tasks/main.yml @@ -151,14 +151,10 @@ - libffi-dev - libssl-dev - python-dev + - python-netifaces - rpi-update - - sqlite3 - systemd -- name: Install systemd-sysv on Wheezy - command: bash -c 'echo "Yes, do as I say!" | apt-get install -y --force-yes systemd-sysv' - when: ansible_distribution_release == "wheezy" - - name: Remove deprecated apt dependencies apt: name: "{{ item }}" @@ -169,6 +165,7 @@ - lightdm-gtk-greeter - matchbox - omxplayer + - sqlite3 - supervisor - uzbl - x11-xserver-utils diff --git a/ansible/site.yml b/ansible/site.yml index bf5b95200..36bdfc16f 100644 --- a/ansible/site.yml +++ b/ansible/site.yml @@ -9,24 +9,6 @@ name: nginx state: restarted - - name: reload systemctl - command: systemctl daemon-reload - - - name: restart-screenly-websocket_server_layer - command: systemctl restart screenly-websocket_server_layer.service - - - name: restart-screenly-server - command: systemctl restart screenly-web.service - - - name: restart-x-server - command: "systemctl restart {{ item }}" - with_items: - - X.service - - matchbox.service - - - name: restart-screenly-viewer - command: systemctl restart screenly-viewer.service - roles: - system - network diff --git a/bin/install.sh b/bin/install.sh index 1c31eef91..60b67cd9b 100755 --- a/bin/install.sh +++ b/bin/install.sh @@ -69,10 +69,10 @@ curl -s https://bootstrap.pypa.io/get-pip.py | sudo python docker -v > /dev/null if [ "$?" != '0' ]; then sudo apt-get install -y apt-transport-https ca-certificates curl - curl -fsSL "https://download.docker.com/linux/raspbian/gpg" | apt-key add -qq - >/dev/null + curl -fsSL "https://download.docker.com/linux/raspbian/gpg" | sudo apt-key add -qq - >/dev/null echo "deb [arch=armhf] https://download.docker.com/linux/raspbian stretch edge" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt-get update - sudo apt-get install -y -qq --no-install-recommends docker-ce + sudo apt-get install -y --no-install-recommends docker-ce sudo usermod -aG docker pi fi @@ -93,8 +93,6 @@ sudo find /usr/share/locale -mindepth 1 -maxdepth 1 ! -name 'en*' ! -name 'de*' cd ~/screenly && git rev-parse HEAD > ~/.screenly/latest_screenly_sha -if test -f ~/.screenly/wifi_set; then rm ~/.screenly/wifi_set; fi - set +x echo "Installation completed." diff --git a/requirements.server.txt b/requirements.server.txt new file mode 100644 index 000000000..343a0743f --- /dev/null +++ b/requirements.server.txt @@ -0,0 +1,22 @@ +certifi==2018.4.16 +configparser==3.5.0 +Flask-Cors==3.0.4 +flask-restful-swagger-2==0.35 +flask-swagger-ui==3.0.12 +flask==0.12.2 +flask_restful==0.3.6 +futures==3.0.5 +gunicorn==19.8.1 +hurry.filesize==0.9 +netifaces==0.10.4 +pyasn1==0.1.8 +python-dateutil==2.4.2 +pytz==2012d +pyzmq==16.0.2 +requests[security]==2.18.4 +sh==1.08 +six==1.11.0 +uptime==2.0.2 +werkzeug==0.10.4 +wsgiref==0.1.2 +youtube_dl>=2017.7.2 diff --git a/requirements.txt b/requirements.txt index bd89bcd10..970d6ad48 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,29 +1,4 @@ -MarkupSafe==1.0 -ansible==2.5.3 -certifi==2018.4.16 -cffi==1.7.0 -configparser==3.5.0 docker-py==1.10.6 -Flask-Cors==3.0.4 -flask-restful-swagger-2==0.35 -flask-swagger-ui==3.0.12 -flask==0.12.2 -flask_restful==0.3.6 -futures==3.0.5 -gevent-websocket==0.10.1 -gevent==1.2.2 -gunicorn==19.8.1 -hurry.filesize==0.9 netifaces==0.10.4 pwgen==0.8.2.post0 -pyasn1==0.1.8 -python-dateutil==2.4.2 -pytz==2012d -pyzmq==16.0.2 -requests[security]==2.18.4 sh==1.08 -six==1.11.0 -uptime==2.0.2 -werkzeug==0.10.4 -wsgiref==0.1.2 -youtube_dl>=2017.7.2 diff --git a/requirements.websocket_server.txt b/requirements.websocket_server.txt new file mode 100644 index 000000000..a5ef325d5 --- /dev/null +++ b/requirements.websocket_server.txt @@ -0,0 +1,7 @@ +configparser==3.5.0 +flask==0.12.2 +gevent-websocket==0.10.1 +gevent==1.2.2 +pyasn1==0.1.8 +pyzmq==16.0.2 +six==1.11.0 diff --git a/server.py b/server.py index 8450a3e66..47365c987 100755 --- a/server.py +++ b/server.py @@ -661,19 +661,19 @@ class Info(Resource): method_decorators = [api_response, auth_basic] def get(self): - viewlog = None - try: - viewlog = [line.decode('utf-8') for line in - check_output(['sudo', 'systemctl', 'status', 'screenly-viewer.service', '-n', '20']).split('\n')] - except: - pass + # viewlog = None + # try: + # viewlog = [line.decode('utf-8') for line in + # check_output(['sudo', 'systemctl', 'status', 'screenly-viewer.service', '-n', '20']).split('\n')] + # except: + # pass # Calculate disk space slash = statvfs("/") free_space = size(slash.f_bavail * slash.f_frsize) return { - 'viewlog': viewlog, + # 'viewlog': viewlog, 'loadavg': diagnostics.get_load_avg()['15 min'], 'free_space': free_space, 'display_info': diagnostics.get_monitor_status(), @@ -804,12 +804,12 @@ def settings_page(): @app.route('/system_info') @auth_basic def system_info(): - viewlog = None - try: - viewlog = [line.decode('utf-8') for line in - check_output(['sudo', 'systemctl', 'status', 'screenly-viewer.service', '-n', '20']).split('\n')] - except: - pass + # viewlog = None + # try: + # viewlog = [line.decode('utf-8') for line in + # check_output(['sudo', 'systemctl', 'status', 'screenly-viewer.service', '-n', '20']).split('\n')] + # except: + # pass loadavg = diagnostics.get_load_avg()['15 min'] @@ -831,7 +831,7 @@ def system_info(): return template( 'system_info.html', player_name=player_name, - viewlog=viewlog, + # viewlog=viewlog, loadavg=loadavg, free_space=free_space, uptime=system_uptime, diff --git a/templates/system_info.html b/templates/system_info.html index fb686ec9a..dbe145e91 100644 --- a/templates/system_info.html +++ b/templates/system_info.html @@ -75,7 +75,7 @@

-
+ From 90bc957f6a78641820b151d1ecd5375162289ab2 Mon Sep 17 00:00:00 2001 From: amolodykh Date: Mon, 21 May 2018 12:38:46 +0600 Subject: [PATCH 003/224] Refactoring previous commits --- .travis.yml | 49 ++++++------------- Dockerfile.server | 18 ++++--- Dockerfile.travis | 48 ++++++++++++++++++ Dockerfile.viewer | 18 +++---- Dockerfile.websocket_server | 12 +++-- .../roles/screenly/files/screenly-web.service | 15 ------ .../screenly-websocket_server_layer.service | 11 ----- ansible/roles/screenly/tasks/main.yml | 9 ++-- ansible/roles/system/tasks/main.yml | 27 ++++++++++ bin/install.sh | 12 +---- .../system/files => configs}/10-evdev.conf | 0 .../files => configs}/10-serverflags.conf | 0 requirements.host.txt | 4 ++ requirements.server.txt | 4 +- requirements.txt | 7 ++- requirements.viewer.txt | 8 +-- requirements.websocket_server.txt | 3 -- tests/updates_test.py | 3 +- tests/viewer_test.py | 47 +----------------- viewer.py | 4 +- 20 files changed, 143 insertions(+), 156 deletions(-) create mode 100644 Dockerfile.travis delete mode 100644 ansible/roles/screenly/files/screenly-web.service delete mode 100644 ansible/roles/screenly/files/screenly-websocket_server_layer.service rename {ansible/roles/system/files => configs}/10-evdev.conf (100%) rename {ansible/roles/system/files => configs}/10-serverflags.conf (100%) create mode 100644 requirements.host.txt diff --git a/.travis.yml b/.travis.yml index 6fd44d345..31f0ebe26 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,43 +1,26 @@ language: python python: - "2.7" -addons: - firefox: "45.0" - apt: - packages: - - net-tools - - mplayer - - python-setuptools -before_install: - - sudo add-apt-repository ppa:mc3man/trusty-media -y - - sudo apt-get update -q - - sudo apt-get install ffmpeg python-gi python-dev + +services: + - docker + install: - - pip install -U pip - - pip install ansible==2.5.2 - - pip install -r requirements.viewer.txt - - pip install -r requirements.server.txt - - pip install -r requirements.dev.txt + - docker build . -f Dockerfile.travis -t screenly/travis-ci + - docker run --name travis-ci -v $TRAVIS_BUILD_DIR:/root/screenly -td screenly/travis-ci /bin/bash + before_script: - - mkdir -p ~/.screenly ~/screenly_assets - - cp ansible/roles/screenly/files/screenly.conf ~/.screenly/ - - cp ansible/roles/screenly/files/screenly.db ~/.screenly/ - - cp ansible/roles/screenly/files/screenly_utils.sh /tmp/screenly_utils.sh - - echo -e "[local]\nlocalhost ansible_connection=local" > ansible/localhost - - curl https://www.screenly.io/upload/ose-logo.png > /tmp/image.png - - curl https://www.screenly.io/upload/ose-logo.png > ~/screenly_assets/image.tmp - - curl https://www.screenly.io/upload/big_buck_bunny_720p_10mb.flv > /tmp/video.flv - - export DISPLAY=:99.0 - - /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16 - - python tests/rtmplite/rtmp.py -r /tmp/ & + - docker exec travis-ci bash -c "echo -e '[local]\nlocalhost ansible_connection=local' > ansible/localhost" + - docker exec travis-ci bash -c "python tests/rtmplite/rtmp.py -r /tmp/" & + - sleep 3 + - docker exec travis-ci bash -c "python server.py" & + - sleep 3 + - docker exec travis-ci bash -c "Xvfb :99 -ac" & - sleep 3 - - python server.py & - - sleep 3 # give xvfb some time to start - script: - - find . ! -path "*/rtmplite/*" -name \*.py -exec pep8 --ignore=E402,E501,E731 {} + - - nosetests -v -a '!fixme' --with-isolation - - ansible-playbook --syntax-check -i ansible/localhost ansible/site.yml + - docker exec -ti travis-ci bash -c "find . ! -path '*/rtmplite/*' -name \*.py -exec pep8 --ignore=E402,E501,E731 {} +" + - docker exec -ti travis-ci bash -c "nosetests -v -a '!fixme' --with-isolation" + - docker exec -ti travis-ci bash -c "ansible-playbook --syntax-check -i ansible/localhost ansible/site.yml" - python -m SimpleHTTPServer 8080 & - sleep 3 - bash static/spec/runner.sh diff --git a/Dockerfile.server b/Dockerfile.server index 85fdd6d04..6260e9d88 100644 --- a/Dockerfile.server +++ b/Dockerfile.server @@ -7,23 +7,27 @@ RUN apt-get update && \ curl \ git-core \ libffi-dev \ + libraspberrypi0 \ libssl-dev \ net-tools \ + omxplayer \ python-dev \ python-netifaces \ + python-pip \ sqlite3 && \ apt-get clean +RUN pip install --upgrade pip + # Install Python requirements -ADD requirements.server.txt /tmp/requirements.txt -RUN curl -s https://bootstrap.pypa.io/get-pip.py | python && \ - pip install -r /tmp/requirements.txt +ADD requirements.txt /tmp/requirements.txt +ADD requirements.server.txt /tmp/requirements.server.txt +RUN pip install -r /tmp/requirements.txt +RUN pip install -r /tmp/requirements.server.txt # Create runtime user -RUN useradd pi - -RUN mkdir -p /home/pi/.screenly /home/pi/screenly /home/pi/screenly_assets -RUN chown -R pi:pi /home/pi +RUN useradd pi -d /home/pi \ + && /usr/sbin/usermod -a -G video pi USER pi diff --git a/Dockerfile.travis b/Dockerfile.travis new file mode 100644 index 000000000..57a290d55 --- /dev/null +++ b/Dockerfile.travis @@ -0,0 +1,48 @@ +FROM ubuntu:16.04 +MAINTAINER Anton Molodykh + +RUN apt-get update && \ + apt-get -y install \ + build-essential \ + curl \ + ffmpeg \ + git-core \ + libdbus-glib-1-dev \ + libgtk2.0-0 \ + mplayer \ + net-tools \ + psmisc \ + python-dev \ + python-pip \ + software-properties-common \ + sqlite3 \ + xvfb && \ + apt-get clean + +RUN curl https://ftp.mozilla.org/pub/firefox/releases/45.0/linux-x86_64/en-US/firefox-45.0.tar.bz2 > /tmp/firefox.tar.bz2 +RUN tar -xjf /tmp/firefox.tar.bz2 -C /tmp +RUN ln -s /tmp/firefox/firefox /usr/bin/firefox + +ENV DISPLAY=:99 + +RUN pip install --upgrade pip +ADD requirements.txt /tmp/requirements.txt +ADD requirements.viewer.txt /tmp/requirements.viewer.txt +ADD requirements.server.txt /tmp/requirements.server.txt +ADD requirements.dev.txt /tmp/requirements.dev.txt + +RUN pip install ansible==2.5.3 \ + -r /tmp/requirements.txt \ + -r /tmp/requirements.viewer.txt \ + -r /tmp/requirements.server.txt \ + -r /tmp/requirements.dev.txt + +RUN mkdir -p ~/.screenly ~/screenly_assets +ADD ansible/roles/screenly/files/screenly.conf /root/.screenly/ +ADD ansible/roles/screenly/files/screenly.db /root/.screenly/ +ADD ansible/roles/screenly/files/screenly_utils.sh /tmp/screenly_utils.sh +RUN curl https://www.screenly.io/upload/ose-logo.png > /tmp/image.png +RUN curl https://www.screenly.io/upload/ose-logo.png > ~/screenly_assets/image.tmp +RUN curl https://www.screenly.io/upload/big_buck_bunny_720p_10mb.flv > /tmp/video.flv + +WORKDIR /root/screenly diff --git a/Dockerfile.viewer b/Dockerfile.viewer index b4671afbc..ebf0d91d9 100644 --- a/Dockerfile.viewer +++ b/Dockerfile.viewer @@ -17,25 +17,25 @@ RUN apt-get update && \ omxplayer \ psmisc \ python-dev \ - python-gi \ python-netifaces \ + python-pip \ sqlite3 \ x11-utils \ x11-xserver-utils \ xserver-xorg && \ apt-get clean +RUN pip install --upgrade pip + # Install Python requirements -ADD requirements.viewer.txt /tmp/requirements.txt -RUN curl -s https://bootstrap.pypa.io/get-pip.py | python && \ - pip install -r /tmp/requirements.txt +ADD requirements.txt /tmp/requirements.txt +ADD requirements.viewer.txt /tmp/requirements.viewer.txt +RUN pip install -r /tmp/requirements.txt +RUN pip install -r /tmp/requirements.viewer.txt # Create runtime user RUN useradd pi -d /home/pi \ && /usr/sbin/usermod -a -G video pi -RUN mkdir -p /home/pi/.screenly /home/pi/screenly /home/pi/screenly_assets - -RUN chown -R pi:pi /home/pi RUN mkdir /usr/local/share/ScreenlyWebview @@ -49,8 +49,8 @@ RUN cp /tmp/ScreenlyWebview /usr/local/bin/ScreenlyWebview ENV NOREFRESH 1 # Copy X11 configs -COPY ansible/roles/system/files/10-serverflags.conf /usr/share/X11/xorg.conf.d/10-serverflags.conf -COPY ansible/roles/system/files/10-evdev.conf /usr/share/X11/xorg.conf.d/10-evdev.conf +COPY configs/10-serverflags.conf /usr/share/X11/xorg.conf.d/10-serverflags.conf +COPY configs/10-evdev.conf /usr/share/X11/xorg.conf.d/10-evdev.conf WORKDIR /home/pi/screenly diff --git a/Dockerfile.websocket_server b/Dockerfile.websocket_server index 4eed4c082..f1b1881b2 100644 --- a/Dockerfile.websocket_server +++ b/Dockerfile.websocket_server @@ -6,13 +6,17 @@ RUN apt-get update && \ build-essential \ curl \ libffi-dev \ - python-dev && \ + python-dev \ + python-pip && \ apt-get clean +RUN pip install --upgrade pip + # Install Python requirements -ADD requirements.websocket_server.txt /tmp/requirements.txt -RUN curl -s https://bootstrap.pypa.io/get-pip.py | python && \ - pip install -r /tmp/requirements.txt +ADD requirements.txt /tmp/requirements.txt +ADD requirements.websocket_server.txt /tmp/requirements.websocket_server.txt +RUN pip install -r /tmp/requirements.txt +RUN pip install -r /tmp/requirements.websocket_server.txt # Create runtime user RUN useradd pi diff --git a/ansible/roles/screenly/files/screenly-web.service b/ansible/roles/screenly/files/screenly-web.service deleted file mode 100644 index 15051195c..000000000 --- a/ansible/roles/screenly/files/screenly-web.service +++ /dev/null @@ -1,15 +0,0 @@ -[Unit] -Description=Screenly Web UI -After=network-online.target - -[Service] -WorkingDirectory=/home/pi/screenly -User=pi -ExecStartPre=/usr/bin/python /home/pi/screenly/bin/wait.py -ExecStart=/usr/bin/python /home/pi/screenly/server.py -Restart=always -RestartSec=5 -Environment=PYTHONPATH=/home/pi/screenly - -[Install] -WantedBy=multi-user.target diff --git a/ansible/roles/screenly/files/screenly-websocket_server_layer.service b/ansible/roles/screenly/files/screenly-websocket_server_layer.service deleted file mode 100644 index e196768c0..000000000 --- a/ansible/roles/screenly/files/screenly-websocket_server_layer.service +++ /dev/null @@ -1,11 +0,0 @@ -[Unit] -Description=Websocket Server layer - -[Service] -WorkingDirectory=/home/pi/screenly -User=pi -ExecStart=/usr/bin/python /home/pi/screenly/websocket_server_layer.py -Restart=always - -[Install] -WantedBy=multi-user.target \ No newline at end of file diff --git a/ansible/roles/screenly/tasks/main.yml b/ansible/roles/screenly/tasks/main.yml index 586dafe21..ac026c47a 100644 --- a/ansible/roles/screenly/tasks/main.yml +++ b/ansible/roles/screenly/tasks/main.yml @@ -23,7 +23,7 @@ dest: /home/pi/.screenly/screenly.conf - name: Install pip dependencies - pip: requirements=/home/pi/screenly/requirements.txt + pip: requirements=/home/pi/screenly/requirements.host.txt - name: Create default assets database if does not exists copy: @@ -66,16 +66,16 @@ - name: Check if deprecated systemd services exists stat: path: /etc/systemd/system/X.service - register: X_service + register: x_service -- set_fact: X_service_exist="{{X_service.stat.exists}}" +- set_fact: x_service_exist="{{x_service.stat.exists}}" - name: Disable deprecated systemd services systemd: name: "{{ item }}" enabled: no with_items: "{{ screenly_systemd_units }}" - when: X_service_exist + when: x_service_exist - name: Remove deprecated systemd units file: @@ -92,6 +92,7 @@ recreate: true restart_policy: unless-stopped network_mode: host + privileged: yes volumes: - /home/pi/screenly:/home/pi/screenly - /home/pi/.screenly:/home/pi/.screenly diff --git a/ansible/roles/system/tasks/main.yml b/ansible/roles/system/tasks/main.yml index 77ebf0667..686ee2862 100644 --- a/ansible/roles/system/tasks/main.yml +++ b/ansible/roles/system/tasks/main.yml @@ -171,6 +171,33 @@ - x11-xserver-utils - xserver-xorg +- name: Add docker apt key + apt_key: + url: https://download.docker.com/linux/raspbian/gpg + state: present + +- name : Get raspbian name + command: lsb_release -cs + register: raspbian_name + +- name: Add Docker repo + lineinfile: + path: /etc/apt/sources.list.d/docker.list + create: yes + line: "deb [arch=armhf] https://download.docker.com/linux/raspbian {{ raspbian_name.stdout }} edge" + state: present + +- name: Install Docker + apt: + name: docker-ce + update_cache: yes + install_recommends: no + +- name: Add pi to docker group + user: + name: pi + group: docker + - name: Perform system upgrade apt: upgrade: dist diff --git a/bin/install.sh b/bin/install.sh index 60b67cd9b..d61ff8464 100755 --- a/bin/install.sh +++ b/bin/install.sh @@ -66,17 +66,7 @@ sudo apt-get purge -y python-setuptools python-pip python-pyasn1 sudo apt-get install -y python-dev git-core libffi-dev libssl-dev curl -s https://bootstrap.pypa.io/get-pip.py | sudo python -docker -v > /dev/null -if [ "$?" != '0' ]; then - sudo apt-get install -y apt-transport-https ca-certificates curl - curl -fsSL "https://download.docker.com/linux/raspbian/gpg" | sudo apt-key add -qq - >/dev/null - echo "deb [arch=armhf] https://download.docker.com/linux/raspbian stretch edge" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null - sudo apt-get update - sudo apt-get install -y --no-install-recommends docker-ce - sudo usermod -aG docker pi -fi - -sudo pip install ansible==2.5.2 +sudo pip install ansible==2.5.3 ansible localhost -m git -a "repo=${1:-https://github.com/screenly/screenly-ose.git} dest=/home/pi/screenly version=$BRANCH" cd /home/pi/screenly/ansible diff --git a/ansible/roles/system/files/10-evdev.conf b/configs/10-evdev.conf similarity index 100% rename from ansible/roles/system/files/10-evdev.conf rename to configs/10-evdev.conf diff --git a/ansible/roles/system/files/10-serverflags.conf b/configs/10-serverflags.conf similarity index 100% rename from ansible/roles/system/files/10-serverflags.conf rename to configs/10-serverflags.conf diff --git a/requirements.host.txt b/requirements.host.txt new file mode 100644 index 000000000..970d6ad48 --- /dev/null +++ b/requirements.host.txt @@ -0,0 +1,4 @@ +docker-py==1.10.6 +netifaces==0.10.4 +pwgen==0.8.2.post0 +sh==1.08 diff --git a/requirements.server.txt b/requirements.server.txt index 343a0743f..262d5257b 100644 --- a/requirements.server.txt +++ b/requirements.server.txt @@ -1,13 +1,13 @@ certifi==2018.4.16 -configparser==3.5.0 +cryptography==2.2.2 Flask-Cors==3.0.4 flask-restful-swagger-2==0.35 flask-swagger-ui==3.0.12 -flask==0.12.2 flask_restful==0.3.6 futures==3.0.5 gunicorn==19.8.1 hurry.filesize==0.9 +idna==2.6 netifaces==0.10.4 pyasn1==0.1.8 python-dateutil==2.4.2 diff --git a/requirements.txt b/requirements.txt index 970d6ad48..707082ef7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ -docker-py==1.10.6 -netifaces==0.10.4 -pwgen==0.8.2.post0 -sh==1.08 +configparser==3.5.0 +flask==0.12.2 +pyzmq==16.0.2 diff --git a/requirements.viewer.txt b/requirements.viewer.txt index 1b065e278..a88c24c82 100644 --- a/requirements.viewer.txt +++ b/requirements.viewer.txt @@ -1,11 +1,11 @@ certifi==2018.4.16 -configparser==3.5.0 -flask==0.12.2 +cryptography==2.2.2 +dbus-python==1.2.8 +idna==2.6 mixpanel==4.3.2 netifaces==0.10.4 python-dateutil==2.4.2 pytz==2012d pyzmq==16.0.2 -pydbus==0.6.0 requests[security]==2.18.4 -sh==1.08 \ No newline at end of file +sh==1.08 diff --git a/requirements.websocket_server.txt b/requirements.websocket_server.txt index a5ef325d5..58472df53 100644 --- a/requirements.websocket_server.txt +++ b/requirements.websocket_server.txt @@ -1,7 +1,4 @@ -configparser==3.5.0 -flask==0.12.2 gevent-websocket==0.10.1 gevent==1.2.2 pyasn1==0.1.8 -pyzmq==16.0.2 six==1.11.0 diff --git a/tests/updates_test.py b/tests/updates_test.py index db91e3bf4..73d87753b 100644 --- a/tests/updates_test.py +++ b/tests/updates_test.py @@ -48,10 +48,9 @@ def test_if_sha_file_is_new__check_update__should_return_false(self): with open(self.sha_file, 'r') as f: self.assertEqual(f.readline(), fancy_sha) - @patch('viewer.req_get', side_effect=mocked_req_get) @patch('viewer.remote_branch_available', side_effect=lambda _: True) @patch('viewer.fetch_remote_hash', side_effect=lambda _: 'master') - def test_if_sha_file_is_empty__check_update__should_return_true(self, req_get, remote_branch_available, fetch_remote_hash): + def test_if_sha_file_is_empty__check_update__should_return_true(self, remote_branch_available, fetch_remote_hash): with open(self.sha_file, 'w+') as f: pass diff --git a/tests/viewer_test.py b/tests/viewer_test.py index 7df3638d1..b11a800e9 100644 --- a/tests/viewer_test.py +++ b/tests/viewer_test.py @@ -43,59 +43,16 @@ def tearDown(self): self.u.SPLASH_DELAY = self.original_splash_delay -@attr('fixme') -class TestEmptyPl(ViewerTestCase): - def test_empty(self): - m_asset_list = mock.Mock() - m_asset_list.return_value = ([], None) - with mock.patch.object(self.u, 'generate_asset_list', m_asset_list): - self.u.main() - - -class TestBrowserSend(ViewerTestCase): - def test_send(self): - self.p_cmd.start() - self.p_send.start() - self.u.setup() - self.u.load_browser() - self.p_send.stop() - self.p_cmd.stop() - - m_put = mock.Mock(name='uzbl_put') - self.m_cmd.return_value.return_value.process.stdin.put = m_put - - self.u.browser_send('test_cmd') - m_put.assert_called_once_with('test_cmd\n') - - def test_dead(self): - self.p_loadb.start() - self.u.browser_send(None) - self.m_loadb.assert_called_once() - self.p_loadb.stop() - - -class TestBrowserClear(ViewerTestCase): - def test_clear(self): - with mock.patch.object(self.u, 'browser_url', mock.Mock()) as m_url: - self.u.setup() - self.u.browser_clear() - m_url.assert_called_once() - - class TestLoadBrowser(ViewerTestCase): - def test_setup(self): - self.u.setup() - ok_(os.path.isdir(self.u.SCREENLY_HTML)) - def load_browser(self): - m_uzbl = mock.Mock(name='uzbl') + m_uzbl = mock.Mock(name='ScreenlyWebview') self.m_cmd.return_value = m_uzbl self.p_cmd.start() self.p_send.start() self.u.load_browser() self.p_send.stop() self.p_cmd.stop() - self.m_cmd.assert_called_once_with('uzbl-browser') + self.m_cmd.assert_called_once_with('ScreenlyWebview') m_uzbl.assert_called_once_with(print_events=True, config='-', uri=None, _bg=True) m_send.assert_called_once() diff --git a/viewer.py b/viewer.py index 9b0f1e0d5..282c884c5 100755 --- a/viewer.py +++ b/viewer.py @@ -7,12 +7,12 @@ from random import shuffle from threading import Thread +from dbus import SessionBus from mixpanel import Mixpanel, MixpanelException from netifaces import gateways from signal import signal, SIGUSR1 from time import sleep import logging -from pydbus import SessionBus import random import sh import string @@ -386,7 +386,7 @@ def setup(): load_browser() bus = SessionBus() - browser_bus = bus.get('screenly.webview', '/Screenly') + browser_bus = bus.get_object('screenly.webview', '/Screenly') def main(): From d7e820217e8af6a7fd20ce9338fb810be7b87f7f Mon Sep 17 00:00:00 2001 From: amolodykh Date: Tue, 29 May 2018 13:24:39 +0600 Subject: [PATCH 004/224] Removes X11 --- Dockerfile.viewer | 37 +++++++++++++++++--------- ansible/roles/system/tasks/main.yml | 24 +++++++++++++++++ bin/start_viewer.sh | 14 +--------- configs/10-evdev.conf | 40 ----------------------------- configs/10-serverflags.conf | 6 ----- viewer.py | 9 +++---- 6 files changed, 54 insertions(+), 76 deletions(-) delete mode 100644 configs/10-evdev.conf delete mode 100644 configs/10-serverflags.conf diff --git a/Dockerfile.viewer b/Dockerfile.viewer index ebf0d91d9..a7ffb238e 100644 --- a/Dockerfile.viewer +++ b/Dockerfile.viewer @@ -12,17 +12,14 @@ RUN apt-get update && \ libqt5webkit5 \ libraspberrypi0 \ libssl-dev \ - matchbox \ + libts-dev \ net-tools \ omxplayer \ psmisc \ python-dev \ python-netifaces \ python-pip \ - sqlite3 \ - x11-utils \ - x11-xserver-utils \ - xserver-xorg && \ + sqlite3 && \ apt-get clean RUN pip install --upgrade pip @@ -33,12 +30,28 @@ ADD requirements.viewer.txt /tmp/requirements.viewer.txt RUN pip install -r /tmp/requirements.txt RUN pip install -r /tmp/requirements.viewer.txt -# Create runtime user -RUN useradd pi -d /home/pi \ - && /usr/sbin/usermod -a -G video pi +# QtBase from packages does not support eglfs +RUN wget -P /tmp https://github.com/antonmolodykh/Screenly-WebVew/releases/download/0.1/qtbase.tar.gz +RUN tar -xzf /tmp/qtbase.tar.gz -C /usr/local +RUN echo "/usr/local/qt5pi/lib" > /etc/ld.so.conf.d/00-qt5pi.conf +RUN ldconfig -RUN mkdir /usr/local/share/ScreenlyWebview +# Fix symlinks +RUN rm /usr/lib/arm-linux-gnueabihf/libEGL.so.1 +RUN rm /usr/lib/arm-linux-gnueabihf/libEGL.so.1.0.0 +RUN ln -s /opt/vc/lib/libbrcmEGL.so /usr/lib/arm-linux-gnueabihf/libEGL.so +RUN ln -s /opt/vc/lib/libbrcmEGL.so /usr/lib/arm-linux-gnueabihf/libEGL.so.1 +RUN ln -s /opt/vc/lib/libbrcmEGL.so /usr/lib/arm-linux-gnueabihf/libEGL.so.1.0.0 + +RUN rm /usr/lib/arm-linux-gnueabihf/libGLESv2.so.2 +RUN rm /usr/lib/arm-linux-gnueabihf/libGLESv2.so.2.0.0 +RUN ln -s /opt/vc/lib/libbrcmGLESv2.so /usr/lib/arm-linux-gnueabihf/libGLESv2.so +RUN ln -s /opt/vc/lib/libbrcmGLESv2.so /usr/lib/arm-linux-gnueabihf/libGLESv2.so.2 +RUN ln -s /opt/vc/lib/libbrcmGLESv2.so /usr/lib/arm-linux-gnueabihf/libGLESv2.so.2.0.0 +ENV QT_QPA_PLATFORM eglfs + +RUN mkdir /usr/local/share/ScreenlyWebview RUN wget -P /tmp https://github.com/antonmolodykh/Screenly-WebVew/releases/download/0.1/screenly-webview-v0.1.tar.gz RUN tar -xzf /tmp/screenly-webview-v0.1.tar.gz -C /tmp @@ -48,9 +61,9 @@ RUN cp /tmp/ScreenlyWebview /usr/local/bin/ScreenlyWebview # Fix white flickering in omxplayer ENV NOREFRESH 1 -# Copy X11 configs -COPY configs/10-serverflags.conf /usr/share/X11/xorg.conf.d/10-serverflags.conf -COPY configs/10-evdev.conf /usr/share/X11/xorg.conf.d/10-evdev.conf +# Create runtime user +RUN useradd pi -d /home/pi \ + && /usr/sbin/usermod -a -G video pi WORKDIR /home/pi/screenly diff --git a/ansible/roles/system/tasks/main.yml b/ansible/roles/system/tasks/main.yml index 686ee2862..46f93f1eb 100644 --- a/ansible/roles/system/tasks/main.yml +++ b/ansible/roles/system/tasks/main.yml @@ -44,6 +44,30 @@ tags: - touches_boot_partition +- name: Add gpu_mem_256 in config.txt if it doesn't exist + lineinfile: + path: /boot/config.txt + line: gpu_mem_256=96 + when: config_txt.stdout.find('gpu_mem_256') == -1 + tags: + - touches_boot_partition + +- name: Add gpu_mem_512 in config.txt if it doesn't exist + lineinfile: + path: /boot/config.txt + line: gpu_mem_512=128 + when: config_txt.stdout.find('gpu_mem_512') == -1 + tags: + - touches_boot_partition + +- name: Add gpu_mem_1024 in config.txt if it doesn't exist + lineinfile: + path: /boot/config.txt + line: gpu_mem_1024=196 + when: config_txt.stdout.find('gpu_mem_1024') == -1 + tags: + - touches_boot_partition + - name: Backup kernel boot args copy: src: /boot/cmdline.txt diff --git a/bin/start_viewer.sh b/bin/start_viewer.sh index 28dd5b83e..94b2d0706 100644 --- a/bin/start_viewer.sh +++ b/bin/start_viewer.sh @@ -4,19 +4,7 @@ # Prevent it so that the container does not fall trap '' 16 -# Start X -rm -f /tmp/.X0-lock -/usr/bin/X & -export DISPLAY=:0.0 - -# Waiting for X11 -while ! xdpyinfo >/dev/null 2>&1; do - sleep 0.5 -done - -su - pi -c " /usr/bin/matchbox-window-manager -use_titlebar no -use_cursor no &" - -su - pi -c "cd /home/pi/screenly && dbus-run-session python viewer.py &" +su - pi -c "cd /home/pi/screenly && QT_QPA_EGLFS_FORCE888=1 dbus-run-session python viewer.py &" # Waiting for the viewer while true; do diff --git a/configs/10-evdev.conf b/configs/10-evdev.conf deleted file mode 100644 index ba37ec34d..000000000 --- a/configs/10-evdev.conf +++ /dev/null @@ -1,40 +0,0 @@ -# -# Catch-all evdev loader for udev-based systems -# We don't simply match on any device since that also adds accelerometers -# and other devices that we don't really want to use. The list below -# matches everything but joysticks. - -#Section "InputClass" -# Identifier "evdev pointer catchall" -# MatchIsPointer "on" -# MatchDevicePath "/dev/input/event*" -# Driver "evdev" -#EndSection - -Section "InputClass" - Identifier "evdev keyboard catchall" - MatchIsKeyboard "on" - MatchDevicePath "/dev/input/event*" - Driver "evdev" -EndSection - -#Section "InputClass" -# Identifier "evdev touchpad catchall" -# MatchIsTouchpad "on" -# MatchDevicePath "/dev/input/event*" -# Driver "evdev" -#EndSection - -#Section "InputClass" -# Identifier "evdev tablet catchall" -# MatchIsTablet "on" -# MatchDevicePath "/dev/input/event*" -# Driver "evdev" -#EndSection - -#Section "InputClass" -# Identifier "evdev touchscreen catchall" -# MatchIsTouchscreen "on" -# MatchDevicePath "/dev/input/event*" -# Driver "evdev" -#EndSection diff --git a/configs/10-serverflags.conf b/configs/10-serverflags.conf deleted file mode 100644 index c7c69374a..000000000 --- a/configs/10-serverflags.conf +++ /dev/null @@ -1,6 +0,0 @@ -Section "ServerFlags" - Option "BlankTime" "0" - Option "StandbyTime" "0" - Option "SuspendTime" "0" - Option "OffTime" "0" -EndSection diff --git a/viewer.py b/viewer.py index 282c884c5..3d1063900 100755 --- a/viewer.py +++ b/viewer.py @@ -250,7 +250,7 @@ def view_video(uri, duration): if arch in ('armv6l', 'armv7l'): player_args = ['omxplayer', uri] - player_kwargs = {'o': settings['audio_output'], '_bg': True, '_ok_code': [0, 124, 143]} + player_kwargs = {'o': settings['audio_output'], 'layer': 1, '_bg': True, '_ok_code': [0, 124, 143]} else: player_args = ['mplayer', uri, '-nosound'] player_kwargs = {'_bg': True, '_ok_code': [0, 124]} @@ -260,7 +260,7 @@ def view_video(uri, duration): run = sh.Command(player_args[0])(*player_args[1:], **player_kwargs) - browser_bus.loadImage('null') + view_image('null') try: while run.process.alive: watchdog() @@ -399,10 +399,9 @@ def main(): while not path.isfile(HOME + INITIALIZED_FILE): sleep(1) - url = 'http://{0}:{1}/splash_page'.format(LISTEN, PORT) if settings['show_splash'] else 'file://' + BLACK_PAGE - view_webpage(url) - if settings['show_splash']: + url = 'http://{0}:{1}/splash_page'.format(LISTEN, PORT) + view_webpage(url) sleep(SPLASH_DELAY) global scheduler From 9ca653a894ae64023279b3a06f2c36d600d0ae9c Mon Sep 17 00:00:00 2001 From: amolodykh Date: Mon, 4 Jun 2018 18:55:51 +0600 Subject: [PATCH 005/224] Use resin images and Install from experemental branch --- Dockerfile.server | 3 ++- Dockerfile.viewer | 7 ++++--- Dockerfile.websocket_server | 5 +++-- ansible/roles/screenly/tasks/main.yml | 15 +++++++++------ bin/install.sh | 17 +++++++++++++---- 5 files changed, 31 insertions(+), 16 deletions(-) diff --git a/Dockerfile.server b/Dockerfile.server index 6260e9d88..b8e350505 100644 --- a/Dockerfile.server +++ b/Dockerfile.server @@ -1,4 +1,4 @@ -FROM raspbian/stretch +FROM resin/rpi-raspbian:stretch MAINTAINER Anton Molodykh RUN apt-get update && \ @@ -14,6 +14,7 @@ RUN apt-get update && \ python-dev \ python-netifaces \ python-pip \ + python-setuptools \ sqlite3 && \ apt-get clean diff --git a/Dockerfile.viewer b/Dockerfile.viewer index a7ffb238e..848406c9b 100644 --- a/Dockerfile.viewer +++ b/Dockerfile.viewer @@ -1,4 +1,4 @@ -FROM raspbian/stretch +FROM resin/rpi-raspbian:stretch MAINTAINER Anton Molodykh RUN apt-get update && \ @@ -19,6 +19,7 @@ RUN apt-get update && \ python-dev \ python-netifaces \ python-pip \ + python-setuptools \ sqlite3 && \ apt-get clean @@ -31,7 +32,7 @@ RUN pip install -r /tmp/requirements.txt RUN pip install -r /tmp/requirements.viewer.txt # QtBase from packages does not support eglfs -RUN wget -P /tmp https://github.com/antonmolodykh/Screenly-WebVew/releases/download/0.1/qtbase.tar.gz +RUN curl -L https://github.com/Screenly/screenly-ose-webview/releases/download/v0.1/qtbase.tar.gz > /tmp/qtbase.tar.gz RUN tar -xzf /tmp/qtbase.tar.gz -C /usr/local RUN echo "/usr/local/qt5pi/lib" > /etc/ld.so.conf.d/00-qt5pi.conf RUN ldconfig @@ -52,7 +53,7 @@ RUN ln -s /opt/vc/lib/libbrcmGLESv2.so /usr/lib/arm-linux-gnueabihf/libGLESv2.so ENV QT_QPA_PLATFORM eglfs RUN mkdir /usr/local/share/ScreenlyWebview -RUN wget -P /tmp https://github.com/antonmolodykh/Screenly-WebVew/releases/download/0.1/screenly-webview-v0.1.tar.gz +RUN curl -L https://github.com/Screenly/screenly-ose-webview/releases/download/v0.1/screenly-webview-v0.1.tar.gz > /tmp/screenly-webview-v0.1.tar.gz RUN tar -xzf /tmp/screenly-webview-v0.1.tar.gz -C /tmp RUN cp -r /tmp/res /usr/local/share/ScreenlyWebview/res diff --git a/Dockerfile.websocket_server b/Dockerfile.websocket_server index f1b1881b2..67106485b 100644 --- a/Dockerfile.websocket_server +++ b/Dockerfile.websocket_server @@ -1,4 +1,4 @@ -FROM raspbian/stretch +FROM resin/rpi-raspbian:stretch MAINTAINER Anton Molodykh RUN apt-get update && \ @@ -7,7 +7,8 @@ RUN apt-get update && \ curl \ libffi-dev \ python-dev \ - python-pip && \ + python-pip \ + python-setuptools && \ apt-get clean RUN pip install --upgrade pip diff --git a/ansible/roles/screenly/tasks/main.yml b/ansible/roles/screenly/tasks/main.yml index ac026c47a..6687bd7e1 100644 --- a/ansible/roles/screenly/tasks/main.yml +++ b/ansible/roles/screenly/tasks/main.yml @@ -83,10 +83,13 @@ state: absent with_items: "{{ screenly_systemd_units }}" +- debug: + msg: "Use {{ lookup('env', 'DOCKER_TAG') }} version of images." + - name: Create screenly-server container docker_container: - name: screenly-server - image: amolodykh/screenly-server + name: screenly-ose-server + image: "screenly/screenly-ose-server:{{ lookup('env', 'DOCKER_TAG') }}" pull: true state: present recreate: true @@ -100,8 +103,8 @@ - name: Create screenly-viewer container docker_container: - name: screenly-viewer - image: amolodykh/screenly-viewer + name: screenly-ose-viewer + image: "screenly/screenly-ose-viewer:{{ lookup('env', 'DOCKER_TAG') }}" pull: true state: present recreate: true @@ -115,8 +118,8 @@ - name: Create screenly-websocket-server container docker_container: - name: screenly-websocket-server - image: amolodykh/screenly-websocket-server + name: screenly-ose-websocket + image: "screenly/screenly-ose-websocket:{{ lookup('env', 'DOCKER_TAG') }}" pull: true state: present recreate: true diff --git a/bin/install.sh b/bin/install.sh index d61ff8464..7f75bb678 100755 --- a/bin/install.sh +++ b/bin/install.sh @@ -35,13 +35,22 @@ if [ "$?" = "1" ]; then exit 1 fi -echo && read -p "Would you like to use the development branch? You will get the latest features, but things may break. (y/N)" -n 1 -r -s DEV && echo -if [ "$DEV" != 'y' ]; then - BRANCH="production" +echo && read -p "Would you like to use the experimental branch? It contains the last major changes, such as the new browser and migrating to the docker (y/N)" -n 1 -r -s EXP && echo +if [ "$EXP" != 'y' ]; then + echo && read -p "Would you like to use the development branch? You will get the latest features, but things may break. (y/N)" -n 1 -r -s DEV && echo + if [ "$DEV" != 'y' ]; then + export DOCKER_TAG="production" + BRANCH="production" + else + export DOCKER_TAG="latest" + BRANCH="master" + fi else - BRANCH="master" + export DOCKER_TAG="experimental" + BRANCH="experimental" fi + echo && read -p "Would you like to perform a full system upgrade as well? (y/N)" -n 1 -r -s UPGRADE && echo if [ "$UPGRADE" != 'y' ]; then EXTRA_ARGS="--skip-tags enable-ssl,system-upgrade" From 3d34bf013bbe2171c5cae5df0a686f550aed3cc0 Mon Sep 17 00:00:00 2001 From: amolodykh Date: Fri, 22 Jun 2018 15:59:46 +0600 Subject: [PATCH 006/224] Fixes display freezing --- viewer.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/viewer.py b/viewer.py index 3d1063900..fb3e5c501 100755 --- a/viewer.py +++ b/viewer.py @@ -411,6 +411,9 @@ def main(): subscriber.daemon = True subscriber.start() + # We don't want to show splash_page if there are active assets but all of them are not available + view_image(HOME + LOAD_SCREEN) + logging.debug('Entering infinite loop.') while True: asset_loop(scheduler) From 10ef51299f8274a943f1ce3b781f67a7757d1df6 Mon Sep 17 00:00:00 2001 From: amolodykh Date: Fri, 13 Jul 2018 17:20:10 +0600 Subject: [PATCH 007/224] Separate viewer image for each type of device --- Dockerfile.viewer | 9 +++++++-- ansible/roles/screenly/tasks/main.yml | 15 +++++++++++---- ansible/site.yml | 2 ++ bin/install.sh | 8 ++++++++ 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/Dockerfile.viewer b/Dockerfile.viewer index 848406c9b..80ead4859 100644 --- a/Dockerfile.viewer +++ b/Dockerfile.viewer @@ -32,7 +32,10 @@ RUN pip install -r /tmp/requirements.txt RUN pip install -r /tmp/requirements.viewer.txt # QtBase from packages does not support eglfs -RUN curl -L https://github.com/Screenly/screenly-ose-webview/releases/download/v0.1/qtbase.tar.gz > /tmp/qtbase.tar.gz +ARG PI_VERSION=pi1 +RUN curl -L \ + "https://github.com/Screenly/screenly-ose-webview/releases/download/v0.1/qtbase-$PI_VERSION.tar.gz" \ + > /tmp/qtbase.tar.gz RUN tar -xzf /tmp/qtbase.tar.gz -C /usr/local RUN echo "/usr/local/qt5pi/lib" > /etc/ld.so.conf.d/00-qt5pi.conf RUN ldconfig @@ -53,7 +56,9 @@ RUN ln -s /opt/vc/lib/libbrcmGLESv2.so /usr/lib/arm-linux-gnueabihf/libGLESv2.so ENV QT_QPA_PLATFORM eglfs RUN mkdir /usr/local/share/ScreenlyWebview -RUN curl -L https://github.com/Screenly/screenly-ose-webview/releases/download/v0.1/screenly-webview-v0.1.tar.gz > /tmp/screenly-webview-v0.1.tar.gz +RUN curl -L \ + "https://github.com/Screenly/screenly-ose-webview/releases/download/v0.1/screenly-webview-v0.1.tar.gz" \ + > /tmp/screenly-webview-v0.1.tar.gz RUN tar -xzf /tmp/screenly-webview-v0.1.tar.gz -C /tmp RUN cp -r /tmp/res /usr/local/share/ScreenlyWebview/res diff --git a/ansible/roles/screenly/tasks/main.yml b/ansible/roles/screenly/tasks/main.yml index 6687bd7e1..a9bced1ae 100644 --- a/ansible/roles/screenly/tasks/main.yml +++ b/ansible/roles/screenly/tasks/main.yml @@ -83,13 +83,20 @@ state: absent with_items: "{{ screenly_systemd_units }}" +- name: Check /proc/device-tree/model file + command: cat /proc/device-tree/model + register: model_file + +- debug: + msg: "Device type: {{ device_type }}" + - debug: - msg: "Use {{ lookup('env', 'DOCKER_TAG') }} version of images." + msg: "Use {{ docker_tag }} version of images." - name: Create screenly-server container docker_container: name: screenly-ose-server - image: "screenly/screenly-ose-server:{{ lookup('env', 'DOCKER_TAG') }}" + image: "screenly/screenly-ose-server:{{ docker_tag }}" pull: true state: present recreate: true @@ -104,7 +111,7 @@ - name: Create screenly-viewer container docker_container: name: screenly-ose-viewer - image: "screenly/screenly-ose-viewer:{{ lookup('env', 'DOCKER_TAG') }}" + image: "screenly/screenly-ose-viewer:{{ docker_tag }}-{{ device_type }}" pull: true state: present recreate: true @@ -119,7 +126,7 @@ - name: Create screenly-websocket-server container docker_container: name: screenly-ose-websocket - image: "screenly/screenly-ose-websocket:{{ lookup('env', 'DOCKER_TAG') }}" + image: "screenly/screenly-ose-websocket:{{ docker_tag }}" pull: true state: present recreate: true diff --git a/ansible/site.yml b/ansible/site.yml index 7baa75e70..9ba04c450 100644 --- a/ansible/site.yml +++ b/ansible/site.yml @@ -4,6 +4,8 @@ become: yes vars: manage_network: "{{ lookup('env', 'MANAGE_NETWORK') }}" + docker_tag: "{{ lookup('env', 'DOCKER_TAG') }}" + device_type: "{{ lookup('env', 'DEVICE_TYPE') }}" handlers: - name: restart-nginx diff --git a/bin/install.sh b/bin/install.sh index 334b0c3f3..a7b59d074 100755 --- a/bin/install.sh +++ b/bin/install.sh @@ -64,6 +64,14 @@ else EXTRA_ARGS="--skip-tags enable-ssl" fi +if grep -qF "Raspberry Pi 3" /proc/device-tree/model; then + export DEVICE_TYPE="pi3" +elif grep -qF "Raspberry Pi 2" /proc/device-tree/model; then + export DEVICE_TYPE="pi2" +else + export DEVICE_TYPE="pi1" +fi + set -x sudo mkdir -p /etc/ansible echo -e "[local]\nlocalhost ansible_connection=local" | sudo tee /etc/ansible/hosts > /dev/null From d8d4ab720c19294895554d620a693582289ffeea Mon Sep 17 00:00:00 2001 From: amolodykh Date: Fri, 3 Aug 2018 12:07:28 +0600 Subject: [PATCH 008/224] Fixed: containers don't start after experimental update --- ansible/roles/screenly/tasks/main.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ansible/roles/screenly/tasks/main.yml b/ansible/roles/screenly/tasks/main.yml index a9bced1ae..a80bd9ec6 100644 --- a/ansible/roles/screenly/tasks/main.yml +++ b/ansible/roles/screenly/tasks/main.yml @@ -73,6 +73,7 @@ - name: Disable deprecated systemd services systemd: name: "{{ item }}" + state: stopped enabled: no with_items: "{{ screenly_systemd_units }}" when: x_service_exist @@ -98,7 +99,7 @@ name: screenly-ose-server image: "screenly/screenly-ose-server:{{ docker_tag }}" pull: true - state: present + state: started recreate: true restart_policy: unless-stopped network_mode: host @@ -113,7 +114,7 @@ name: screenly-ose-viewer image: "screenly/screenly-ose-viewer:{{ docker_tag }}-{{ device_type }}" pull: true - state: present + state: started recreate: true restart_policy: unless-stopped network_mode: host @@ -128,7 +129,7 @@ name: screenly-ose-websocket image: "screenly/screenly-ose-websocket:{{ docker_tag }}" pull: true - state: present + state: started recreate: true restart_policy: unless-stopped network_mode: host From 5b5396a5fa8a8a28fa0aa5dde670a0b773385317 Mon Sep 17 00:00:00 2001 From: Rusko124 Date: Tue, 9 Oct 2018 14:02:08 +0600 Subject: [PATCH 009/224] add pause for viewer container in ansible-playbook --- ansible/roles/screenly/tasks/main.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ansible/roles/screenly/tasks/main.yml b/ansible/roles/screenly/tasks/main.yml index a80bd9ec6..3833a502f 100644 --- a/ansible/roles/screenly/tasks/main.yml +++ b/ansible/roles/screenly/tasks/main.yml @@ -124,6 +124,9 @@ - /home/pi/.screenly:/home/pi/.screenly - /home/pi/screenly_assets:/home/pi/screenly_assets +- name: Pause screenly-viewer container + command: docker pause screenly-ose-viewer + - name: Create screenly-websocket-server container docker_container: name: screenly-ose-websocket From ae6daed9ff59e043e1ddb5cd088afb6ca51d76e2 Mon Sep 17 00:00:00 2001 From: Rusko124 Date: Wed, 10 Oct 2018 11:35:11 +0600 Subject: [PATCH 010/224] updateMimetype fixes --- static/js/screenly-ose.coffee | 4 ++-- static/js/screenly-ose.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/static/js/screenly-ose.coffee b/static/js/screenly-ose.coffee index cb3ebe0a4..41dc51daf 100644 --- a/static/js/screenly-ose.coffee +++ b/static/js/screenly-ose.coffee @@ -183,7 +183,7 @@ API.View.AddAssetView = class AddAssetView extends Backbone.View maxChunkSize: 5000000 #5 MB url: 'api/v1/file_asset' progressall: (e, data) => if data.loaded and data.total - (@$ '.progress .bar').css 'width', "#{data.loaded/data.total*100}%" + (@$ '.progress .bar').css 'width', "#{data.loaded / data.total * 100}%" add: (e, data) -> (that.$ '.status').hide() (that.$ '.progress').show() @@ -229,7 +229,7 @@ API.View.AddAssetView = class AddAssetView extends Backbone.View updateFileUploadMimetype: (filename) => @updateMimetype filename updateMimetype: (filename) => mt = get_mimetype filename - @$fv 'mimetype', mt if mt + @$fv 'mimetype', if mt then mt else new Asset().defaults()['mimetype'] @change_mimetype() change: (e) => diff --git a/static/js/screenly-ose.js b/static/js/screenly-ose.js index 900b35cf5..24866daa5 100644 --- a/static/js/screenly-ose.js +++ b/static/js/screenly-ose.js @@ -414,9 +414,7 @@ AddAssetView.prototype.updateMimetype = function(filename) { var mt; mt = get_mimetype(filename); - if (mt) { - this.$fv('mimetype', mt); - } + this.$fv('mimetype', mt ? mt : new Asset().defaults()['mimetype']); return this.change_mimetype(); }; @@ -1034,3 +1032,5 @@ })(Backbone.View); }).call(this); + +//# sourceMappingURL=screenly-ose.js.map From fea110d5600cdad6f22e5352cbf4eb7aefc20224 Mon Sep 17 00:00:00 2001 From: Rusko124 Date: Fri, 19 Oct 2018 13:40:15 +0600 Subject: [PATCH 011/224] resin.io support --- Dockerfile.template | 78 +++++++++++++++++++++++++++------------------ bin/start_resin.sh | 75 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 115 insertions(+), 38 deletions(-) diff --git a/Dockerfile.template b/Dockerfile.template index 2b125eed5..07391bd7d 100644 --- a/Dockerfile.template +++ b/Dockerfile.template @@ -1,60 +1,77 @@ -FROM resin/%%RESIN_MACHINE_NAME%%-debian +FROM resin/%%RESIN_MACHINE_NAME%%-debian:stretch MAINTAINER Anton Molodykh RUN apt-get update && \ apt-get -y install \ build-essential \ curl \ + git \ git-core \ libffi-dev \ + libraspberrypi0 \ libssl-dev \ - matchbox \ net-tools \ nginx-light \ omxplayer \ - psmisc \ python-dev \ - python-imaging \ python-netifaces \ - python-simplejson \ + python-pip \ + python-setuptools \ sqlite3 \ - uzbl \ - x11-xserver-utils \ - xserver-xorg && \ + gstreamer0.10-plugins-good \ + libdbus-glib-1-dev \ + libqt5webkit5 \ + libts-dev \ + psmisc && \ apt-get clean -RUN rm /etc/nginx/sites-enabled/default +RUN rm -f /etc/nginx/sites-enabled/default COPY ansible/roles/ssl/files/nginx_resin.conf /etc/nginx/sites-enabled/screenly.conf +RUN pip install --upgrade pip setuptools + # Install Python requirements -ADD requirements.viewer.txt /tmp/requirements.viewer.txt +ADD requirements.txt /tmp/requirements.txt ADD requirements.server.txt /tmp/requirements.server.txt ADD requirements.websocket_server.txt /tmp/requirements.websocket_server.txt -RUN curl -s https://bootstrap.pypa.io/get-pip.py | python && \ - pip install --upgrade -r /tmp/requirements.viewer.txt && \ - pip install --upgrade -r /tmp/requirements.server.txt && \ - pip install --upgrade -r /tmp/requirements.websocket_server.txt +ADD requirements.viewer.txt /tmp/requirements.viewer.txt +RUN pip install -r /tmp/requirements.txt +RUN pip install -r /tmp/requirements.server.txt +RUN pip install -r /tmp/requirements.websocket_server.txt +RUN pip install -r /tmp/requirements.viewer.txt + +# QtBase from packages does not support eglfs +RUN curl -L \ + "https://github.com/Screenly/screenly-ose-webview/releases/download/v0.1/qtbase.tar.gz" \ + > /tmp/qtbase.tar.gz +RUN tar -xzf /tmp/qtbase.tar.gz -C /usr/local +RUN echo "/usr/local/qt5pi/lib" > /etc/ld.so.conf.d/00-qt5pi.conf +RUN ldconfig -# Screenly Websocker Server -COPY ansible/roles/screenly/files/screenly-websocket_server_layer.service /etc/systemd/system/screenly-websocket_server_layer.service -RUN sed -i '/\[Service\]/ a\Environment=HOME=/data' /etc/systemd/system/screenly-websocket_server_layer.service +# Fix symlinks +RUN rm /usr/lib/arm-linux-gnueabihf/libEGL.so.1 +RUN rm /usr/lib/arm-linux-gnueabihf/libEGL.so.1.0.0 +RUN ln -s /opt/vc/lib/libbrcmEGL.so /usr/lib/arm-linux-gnueabihf/libEGL.so +RUN ln -s /opt/vc/lib/libbrcmEGL.so /usr/lib/arm-linux-gnueabihf/libEGL.so.1 +RUN ln -s /opt/vc/lib/libbrcmEGL.so /usr/lib/arm-linux-gnueabihf/libEGL.so.1.0.0 -# Screenly Server -COPY ansible/roles/screenly/files/screenly-web.service /etc/systemd/system/screenly-web.service -RUN sed -i '/\[Service\]/ a\Environment=HOME=/data' /etc/systemd/system/screenly-web.service -RUN sed -i '/\[Service\]/ a\Environment=Environment=LISTEN=0.0.0.0' /etc/systemd/system/screenly-web.service -RUN sed -i '/\[Service\]/ a\Environment=Environment=SWAGGER_HOST=ose.demo.screenlyapp.com' /etc/systemd/system/screenly-web.service +RUN rm /usr/lib/arm-linux-gnueabihf/libGLESv2.so.2 +RUN rm /usr/lib/arm-linux-gnueabihf/libGLESv2.so.2.0.0 +RUN ln -s /opt/vc/lib/libbrcmGLESv2.so /usr/lib/arm-linux-gnueabihf/libGLESv2.so +RUN ln -s /opt/vc/lib/libbrcmGLESv2.so /usr/lib/arm-linux-gnueabihf/libGLESv2.so.2 +RUN ln -s /opt/vc/lib/libbrcmGLESv2.so /usr/lib/arm-linux-gnueabihf/libGLESv2.so.2.0.0 -# X11 -COPY ansible/roles/screenly/files/X.service /etc/systemd/system/X.service +RUN mkdir /usr/local/share/ScreenlyWebview +RUN curl -L \ + "https://github.com/Screenly/screenly-ose-webview/releases/download/v0.1/screenly-webview-v0.1.tar.gz" \ + > /tmp/screenly-webview-v0.1.tar.gz +RUN tar -xzf /tmp/screenly-webview-v0.1.tar.gz -C /tmp -# Matchbox -COPY ansible/roles/screenly/files/matchbox.service /etc/systemd/system/matchbox.service +RUN cp -r /tmp/res /usr/local/share/ScreenlyWebview/res +RUN cp /tmp/ScreenlyWebview /usr/local/bin/ScreenlyWebview -#Screenly Viewer -COPY ansible/roles/screenly/files/screenly-viewer.service /etc/systemd/system/screenly-viewer.service -RUN sed -i '/\[Service\]/ a\Environment=HOME=/data' /etc/systemd/system/screenly-viewer.service -RUN sed -i '/\[Service\]/ a\Environment=DISABLE_UPDATE_CHECK=True' /etc/systemd/system/screenly-viewer.service +# Fix white flickering in omxplayer +ENV NOREFRESH 1 # Enable container init system. ENV INITSYSTEM on @@ -65,7 +82,6 @@ RUN useradd pi -d /home/pi \ # Install config file and file structure RUN mkdir -p /home/pi/screenly -COPY ansible/roles/screenly/files/gtkrc-2.0 /home/pi/.gtkrc-2.0 # Copy in code base COPY . /home/pi/screenly diff --git a/bin/start_resin.sh b/bin/start_resin.sh index eb2b3397c..545cfd753 100755 --- a/bin/start_resin.sh +++ b/bin/start_resin.sh @@ -2,7 +2,6 @@ mkdir -p \ /data/.config \ - /data/.config/uzbl \ /data/.screenly \ /data/screenly \ /data/screenly_assets @@ -10,7 +9,6 @@ mkdir -p \ cp -n ansible/roles/screenly/files/screenly.conf /data/.screenly/screenly.conf cp -n ansible/roles/screenly/files/screenly.db /data/.screenly/screenly.db cp -n loading.png /data/screenly/loading.png -cp -n ansible/roles/screenly/files/uzbl-config /data/.config/uzbl/config-screenly if [ -n "${OVERWRITE_CONFIG}" ]; then echo "Requested to overwrite Screenly config file." @@ -26,13 +24,76 @@ if [ -n "${MANAGEMENT_USER+x}" ] && [ -n "${MANAGEMENT_PASSWORD+x}" ]; then sed -i -e "s/^user=.*/user=${MANAGEMENT_USER}/" -e "s/^password=.*/password=${MANAGEMENT_PASSWORD}/" /data/.screenly/screenly.conf fi -sed -i "/\[Service\]/ a\Environment=RESIN_UUID=${RESIN_DEVICE_UUID}" /etc/systemd/system/screenly-web.service +cat << EOF > /etc/systemd/system/screenly-server.service +[Unit] +Description=Screenly Web UI +After=network-online.target -systemctl start X.service -systemctl start matchbox.service +[Service] +WorkingDirectory=/home/pi/screenly +User=pi + +Environment=HOME=/data +Environment=PYTHONPATH=/home/pi/screenly +Environment=LISTEN=0.0.0.0 +Environment=SWAGGER_HOST=ose.demo.screenlyapp.com +Environment=RESIN_UUID=${RESIN_DEVICE_UUID} + +ExecStartPre=/usr/bin/python /home/pi/screenly/bin/wait.py +ExecStart=/usr/bin/python /home/pi/screenly/server.py +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target +EOF + +cat << EOF > /etc/systemd/system/screenly-viewer.service +[Unit] +Description=Screenly Viewer +After=screenly-server.service + +[Service] +WorkingDirectory=/home/pi/screenly +User=pi + +Environment=HOME=/data +Environment=QT_QPA_EGLFS_FORCE888=1 +Environment=QT_QPA_EGLFS_PHYSICAL_WIDTH=154 +Environment=QT_QPA_EGLFS_PHYSICAL_HEIGHT=86 +Environment=QT_QPA_EGLFS_WIDTH=1280 +Environment=QT_QPA_EGLFS_HEIGHT=1024 +Environment=QT_QPA_PLATFORM=eglfs +Environment=DISABLE_UPDATE_CHECK=True + +ExecStart=/usr/bin/dbus-run-session /usr/bin/python /home/pi/screenly/viewer.py +Restart=on-failure + +[Install] +WantedBy=multi-user.target +EOF + +cat << EOF > /etc/systemd/system/screenly-websocket.service +[Unit] +Description=Websocket Server layer +After=screenly-server.service + +[Service] +WorkingDirectory=/home/pi/screenly +User=pi + +Environment=HOME=/data + +ExecStart=/usr/bin/python /home/pi/screenly/websocket_server_layer.py +Restart=always + +[Install] +WantedBy=multi-user.target +EOF + +systemctl start screenly-server.service systemctl start screenly-viewer.service -systemctl start screenly-web.service -systemctl start screenly-websocket_server_layer.service +systemctl start screenly-websocket.service # By default docker gives us 64MB of shared memory size but to display heavy # pages we need more. From 715739e5434ed28bc16e3a87c3ed505d96b6fc77 Mon Sep 17 00:00:00 2001 From: Rusko124 Date: Thu, 22 Nov 2018 17:03:37 +0600 Subject: [PATCH 012/224] Updated version of screenly-webview to v0.2 --- Dockerfile.template | 6 +++--- Dockerfile.viewer | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Dockerfile.template b/Dockerfile.template index 07391bd7d..6991b058e 100644 --- a/Dockerfile.template +++ b/Dockerfile.template @@ -63,9 +63,9 @@ RUN ln -s /opt/vc/lib/libbrcmGLESv2.so /usr/lib/arm-linux-gnueabihf/libGLESv2.so RUN mkdir /usr/local/share/ScreenlyWebview RUN curl -L \ - "https://github.com/Screenly/screenly-ose-webview/releases/download/v0.1/screenly-webview-v0.1.tar.gz" \ - > /tmp/screenly-webview-v0.1.tar.gz -RUN tar -xzf /tmp/screenly-webview-v0.1.tar.gz -C /tmp + "https://github.com/Screenly/screenly-ose-webview/releases/download/v0.2/screenly-webview-v0.2.tar.gz" \ + > /tmp/screenly-webview-v0.2.tar.gz +RUN tar -xzf /tmp/screenly-webview-v0.2.tar.gz -C /tmp RUN cp -r /tmp/res /usr/local/share/ScreenlyWebview/res RUN cp /tmp/ScreenlyWebview /usr/local/bin/ScreenlyWebview diff --git a/Dockerfile.viewer b/Dockerfile.viewer index 80ead4859..6aa37f0b2 100644 --- a/Dockerfile.viewer +++ b/Dockerfile.viewer @@ -57,9 +57,9 @@ ENV QT_QPA_PLATFORM eglfs RUN mkdir /usr/local/share/ScreenlyWebview RUN curl -L \ - "https://github.com/Screenly/screenly-ose-webview/releases/download/v0.1/screenly-webview-v0.1.tar.gz" \ - > /tmp/screenly-webview-v0.1.tar.gz -RUN tar -xzf /tmp/screenly-webview-v0.1.tar.gz -C /tmp + "https://github.com/Screenly/screenly-ose-webview/releases/download/v0.2/screenly-webview-v0.2.tar.gz" \ + > /tmp/screenly-webview-v0.2.tar.gz +RUN tar -xzf /tmp/screenly-webview-v0.2.tar.gz -C /tmp RUN cp -r /tmp/res /usr/local/share/ScreenlyWebview/res RUN cp /tmp/ScreenlyWebview /usr/local/bin/ScreenlyWebview From 3478099dc0be0bb490aa520c4a4a08f48603c6ef Mon Sep 17 00:00:00 2001 From: Rusko124 Date: Thu, 22 Nov 2018 17:21:32 +0600 Subject: [PATCH 013/224] fix --- Dockerfile.template | 6 +++--- Dockerfile.viewer | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Dockerfile.template b/Dockerfile.template index 6991b058e..a2693dc77 100644 --- a/Dockerfile.template +++ b/Dockerfile.template @@ -63,9 +63,9 @@ RUN ln -s /opt/vc/lib/libbrcmGLESv2.so /usr/lib/arm-linux-gnueabihf/libGLESv2.so RUN mkdir /usr/local/share/ScreenlyWebview RUN curl -L \ - "https://github.com/Screenly/screenly-ose-webview/releases/download/v0.2/screenly-webview-v0.2.tar.gz" \ - > /tmp/screenly-webview-v0.2.tar.gz -RUN tar -xzf /tmp/screenly-webview-v0.2.tar.gz -C /tmp + "https://github.com/Screenly/screenly-ose-webview/releases/download/0.2/screenly-webview-0.2.tar.gz" \ + > /tmp/screenly-webview-0.2.tar.gz +RUN tar -xzf /tmp/screenly-webview-0.2.tar.gz -C /tmp RUN cp -r /tmp/res /usr/local/share/ScreenlyWebview/res RUN cp /tmp/ScreenlyWebview /usr/local/bin/ScreenlyWebview diff --git a/Dockerfile.viewer b/Dockerfile.viewer index 6aa37f0b2..f8517d2e2 100644 --- a/Dockerfile.viewer +++ b/Dockerfile.viewer @@ -57,9 +57,9 @@ ENV QT_QPA_PLATFORM eglfs RUN mkdir /usr/local/share/ScreenlyWebview RUN curl -L \ - "https://github.com/Screenly/screenly-ose-webview/releases/download/v0.2/screenly-webview-v0.2.tar.gz" \ - > /tmp/screenly-webview-v0.2.tar.gz -RUN tar -xzf /tmp/screenly-webview-v0.2.tar.gz -C /tmp + "https://github.com/Screenly/screenly-ose-webview/releases/download/0.2/screenly-webview-0.2.tar.gz" \ + > /tmp/screenly-webview-0.2.tar.gz +RUN tar -xzf /tmp/screenly-webview-0.2.tar.gz -C /tmp RUN cp -r /tmp/res /usr/local/share/ScreenlyWebview/res RUN cp /tmp/ScreenlyWebview /usr/local/bin/ScreenlyWebview From b403ae61a8491e734bd83d0433f4b9acc372401b Mon Sep 17 00:00:00 2001 From: Rusko124 Date: Fri, 22 Mar 2019 14:24:03 +0600 Subject: [PATCH 014/224] Fix: Docker containers are now synchronized with the host --- ansible/roles/screenly/tasks/main.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ansible/roles/screenly/tasks/main.yml b/ansible/roles/screenly/tasks/main.yml index a80bd9ec6..52193897a 100644 --- a/ansible/roles/screenly/tasks/main.yml +++ b/ansible/roles/screenly/tasks/main.yml @@ -108,6 +108,8 @@ - /home/pi/screenly:/home/pi/screenly - /home/pi/.screenly:/home/pi/.screenly - /home/pi/screenly_assets:/home/pi/screenly_assets + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro - name: Create screenly-viewer container docker_container: @@ -123,6 +125,8 @@ - /home/pi/screenly:/home/pi/screenly - /home/pi/.screenly:/home/pi/.screenly - /home/pi/screenly_assets:/home/pi/screenly_assets + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro - name: Create screenly-websocket-server container docker_container: @@ -136,3 +140,5 @@ volumes: - /home/pi/screenly:/home/pi/screenly - /home/pi/.screenly:/home/pi/.screenly + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro From 3e876f04f7e40e2cc2073efd67bc1682909c342a Mon Sep 17 00:00:00 2001 From: Rusko124 Date: Wed, 9 Oct 2019 09:27:13 +0600 Subject: [PATCH 015/224] Changes: webkit browser instead of UZBL --- .travis.yml | 3 +- ansible/roles/screenly/files/X.service | 11 -- ansible/roles/screenly/files/gtkrc-2.0 | 9 -- ansible/roles/screenly/files/matchbox.service | 12 -- .../screenly/files/plymouth-quit-wait.service | 8 -- .../screenly/files/plymouth-quit.service | 8 -- .../screenly/files/screenly-viewer.service | 32 ----- ansible/roles/screenly/files/uzbl-config | 5 - ansible/roles/screenly/tasks/main.yml | 85 ++++++++---- ansible/roles/screenly/vars/main.yml | 8 +- ansible/roles/splashscreen/tasks/main.yml | 12 +- ansible/roles/ssl/tasks/main.yml | 1 + ansible/roles/system/files/10-evdev.conf | 40 ------ .../roles/system/files/10-serverflags.conf | 6 - ansible/roles/system/tasks/main.yml | 125 +++++++++++------- ansible/site.yml | 12 +- bin/start_balena.sh | 2 - bin/start_viewer.sh | 21 +++ docker/Dockerfile.viewer | 74 +++++++++++ docker/Dockerfile.viewer.template | 1 - requirements/requirements.txt | 1 + requirements/requirements.viewer.txt | 14 ++ server.py | 4 +- viewer.py | 124 +++++------------ 24 files changed, 296 insertions(+), 322 deletions(-) delete mode 100644 ansible/roles/screenly/files/X.service delete mode 100644 ansible/roles/screenly/files/gtkrc-2.0 delete mode 100644 ansible/roles/screenly/files/matchbox.service delete mode 100755 ansible/roles/screenly/files/plymouth-quit-wait.service delete mode 100755 ansible/roles/screenly/files/plymouth-quit.service delete mode 100644 ansible/roles/screenly/files/screenly-viewer.service delete mode 100644 ansible/roles/screenly/files/uzbl-config delete mode 100644 ansible/roles/system/files/10-evdev.conf delete mode 100644 ansible/roles/system/files/10-serverflags.conf create mode 100644 bin/start_viewer.sh create mode 100644 docker/Dockerfile.viewer create mode 100644 requirements/requirements.viewer.txt diff --git a/.travis.yml b/.travis.yml index e95c6a3a9..a4a03c414 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,8 +18,7 @@ install: - pip install -r requirements/requirements.txt - pip install -r requirements/requirements.dev.txt before_script: - - mkdir -p ~/.screenly ~/.config/uzbl/ ~/screenly_assets /tmp/USB/cleanup_folder - - cp ansible/roles/screenly/files/uzbl-config ~/.config/uzbl/config-screenly + - mkdir -p ~/.screenly ~/screenly_assets /tmp/USB/cleanup_folder - cp ansible/roles/screenly/files/screenly.conf ~/.screenly/ - cp ansible/roles/screenly/files/screenly.db ~/.screenly/ - sudo cp bin/install.sh /usr/local/sbin/upgrade_screenly.sh diff --git a/ansible/roles/screenly/files/X.service b/ansible/roles/screenly/files/X.service deleted file mode 100644 index b59dfee83..000000000 --- a/ansible/roles/screenly/files/X.service +++ /dev/null @@ -1,11 +0,0 @@ -[Unit] -Description=X11 -After=network.target - -[Service] -ExecStart=/usr/bin/X -User=root -Restart=on-failure - -[Install] -WantedBy=multi-user.target \ No newline at end of file diff --git a/ansible/roles/screenly/files/gtkrc-2.0 b/ansible/roles/screenly/files/gtkrc-2.0 deleted file mode 100644 index 8d399f94f..000000000 --- a/ansible/roles/screenly/files/gtkrc-2.0 +++ /dev/null @@ -1,9 +0,0 @@ -style "uzbl" { - GtkScrollbar::slider-width=0 - GtkScrollbar::trough-border=0 - GtkScrollbar::has-backward-stepper=0 - GtkScrollbar::has-forward-stepper=0 - GtkScrollbar::has-secondary-backward-stepper=0 - GtkScrollbar::has-secondary-forward-stepper=0 -} -widget "Uzbl*" style "uzbl" diff --git a/ansible/roles/screenly/files/matchbox.service b/ansible/roles/screenly/files/matchbox.service deleted file mode 100644 index 6bb0b06df..000000000 --- a/ansible/roles/screenly/files/matchbox.service +++ /dev/null @@ -1,12 +0,0 @@ -[Unit] -Description=X11 -After=X.service - -[Service] -Environment=DISPLAY=:0.0 -ExecStart=/usr/bin/matchbox-window-manager -use_titlebar no -use_cursor no -User=pi -Restart=on-failure - -[Install] -WantedBy=multi-user.target diff --git a/ansible/roles/screenly/files/plymouth-quit-wait.service b/ansible/roles/screenly/files/plymouth-quit-wait.service deleted file mode 100755 index 9099edc41..000000000 --- a/ansible/roles/screenly/files/plymouth-quit-wait.service +++ /dev/null @@ -1,8 +0,0 @@ -[Unit] -Description=Hold until boot process finishes up -After=rc-local.service plymouth-start.service systemd-user-sessions.service screenly-viewer.service - -[Service] -ExecStart=-/bin/plymouth --wait -Type=oneshot -TimeoutSec=0 diff --git a/ansible/roles/screenly/files/plymouth-quit.service b/ansible/roles/screenly/files/plymouth-quit.service deleted file mode 100755 index b24cf2012..000000000 --- a/ansible/roles/screenly/files/plymouth-quit.service +++ /dev/null @@ -1,8 +0,0 @@ -[Unit] -Description=Terminate Plymouth Boot Screen -After=rc-local.service plymouth-start.service systemd-user-sessions.service screenly-viewer.service - -[Service] -ExecStart=-/bin/plymouth quit -Type=oneshot -TimeoutSec=20 diff --git a/ansible/roles/screenly/files/screenly-viewer.service b/ansible/roles/screenly/files/screenly-viewer.service deleted file mode 100644 index e606d80b2..000000000 --- a/ansible/roles/screenly/files/screenly-viewer.service +++ /dev/null @@ -1,32 +0,0 @@ -[Unit] -Description=Screenly Viewer -After=matchbox.service screenly-web.service - -[Service] -WorkingDirectory=/home/pi/screenly -User=pi - -Environment=DISPLAY=:0.0 - -# Fix white flickering in omxplayer -Environment=NOREFRESH=1 - -Environment=PYTHONPATH=/home/pi/screenly - -# Don't activate screensaver -ExecStartPre=/usr/bin/xset s off - -# Disable DPMS (Energy Star) features -ExecStartPre=/usr/bin/xset -dpms - -# Don't blank the video device -ExecStartPre=/usr/bin/xset s noblank - -ExecStart=/usr/bin/python /home/pi/screenly/viewer.py -Restart=on-failure - -ExecStartPost=/bin/rm -f /tmp/uzbl_* -ExecStartPost=/bin/rm -f /tmp/screenly_html/* - -[Install] -WantedBy=multi-user.target diff --git a/ansible/roles/screenly/files/uzbl-config b/ansible/roles/screenly/files/uzbl-config deleted file mode 100644 index 74a3fa5c5..000000000 --- a/ansible/roles/screenly/files/uzbl-config +++ /dev/null @@ -1,5 +0,0 @@ -set show_status = 0 -set on_event = request ON_EVENT - -@on_event LOAD_START js alert=function(e){return true};confirm=function(e){return true};prompt=function(e){return true}; -@on_event LOAD_COMMIT js alert=function(e){return true};confirm=function(e){return true};prompt=function(e){return true}; diff --git a/ansible/roles/screenly/tasks/main.yml b/ansible/roles/screenly/tasks/main.yml index 822f54bcc..bea7d5346 100644 --- a/ansible/roles/screenly/tasks/main.yml +++ b/ansible/roles/screenly/tasks/main.yml @@ -7,7 +7,7 @@ with_items: - .screenly - .config - - .config/uzbl + - screenly_assets - name: Copy Screenly default config copy: @@ -31,20 +31,6 @@ state: absent dest: /home/pi/.screenly/screenly.conf -- name: Copy in GTK config - copy: - owner: pi - group: pi - src: gtkrc-2.0 - dest: /home/pi/.gtkrc-2.0 - -- name: Copy in UZBL config - copy: - owner: pi - group: pi - src: uzbl-config - dest: /home/pi/.config/uzbl/config-screenly - - name: Install pip dependencies pip: requirements=/home/pi/screenly/requirements/requirements.txt @@ -123,22 +109,67 @@ dest: "/etc/systemd/system/{{ item }}" with_items: "{{ screenly_systemd_units }}" -- name: Copy plymouth-quit-wait.service - copy: - src: plymouth-quit-wait.service +- name: Remove plymouth-quit-wait.service + file: + state: absent dest: /lib/systemd/system/plymouth-quit-wait.service - mode: 0644 - owner: root - group: root -- name: Copy plymouth-quit.service - copy: - src: plymouth-quit.service +- name: Remove plymouth-quit.service + file: + state: absent dest: /lib/systemd/system/plymouth-quit.service - mode: 0644 - owner: root - group: root - name: Enable screenly systemd services command: systemctl enable {{ item }} chdir=/etc/systemd/system with_items: "{{ screenly_systemd_units }}" + +- name: Check if deprecated systemd services exists + stat: + path: /etc/systemd/system/X.service + register: x_service + +- set_fact: x_service_exist="{{x_service.stat.exists}}" + +- name: Disable deprecated systemd services + systemd: + name: "{{ item }}" + state: stopped + enabled: no + with_items: "{{ deprecated_screenly_systemd_units }}" + when: x_service_exist + +- name: Remove deprecated systemd units + file: + path: "/etc/systemd/system/{{ item }}" + state: absent + with_items: "{{ deprecated_screenly_systemd_units }}" + +- name: Check /proc/device-tree/model file + command: cat /proc/device-tree/model + register: model_file + +- debug: + msg: "Device type: {{ device_type }}" + +- debug: + msg: "Use {{ docker_tag }} version of images." + +- name: Create screenly-viewer container + docker_container: + name: screenly-ose-viewer + image: "screenly/screenly-ose-viewer:{{ docker_tag }}-{{ device_type }}" + pull: true + state: started + recreate: true + restart_policy: unless-stopped + network_mode: host + privileged: yes + volumes: + - /home/pi/screenly:/home/pi/screenly + - /home/pi/.screenly:/home/pi/.screenly + - /home/pi/screenly_assets:/home/pi/screenly_assets + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + +- name: Pause screenly-viewer container + command: docker pause screenly-ose-viewer diff --git a/ansible/roles/screenly/vars/main.yml b/ansible/roles/screenly/vars/main.yml index aa9688ad5..3c335064e 100644 --- a/ansible/roles/screenly/vars/main.yml +++ b/ansible/roles/screenly/vars/main.yml @@ -1,8 +1,10 @@ screenly_systemd_units: - - X.service - - matchbox.service - screenly-celery.service - - screenly-viewer.service - screenly-web.service - screenly-websocket_server_layer.service - udev-restart.service + +deprecated_screenly_systemd_units: + - screenly-viewer.service + - X.service + - matchbox.service diff --git a/ansible/roles/splashscreen/tasks/main.yml b/ansible/roles/splashscreen/tasks/main.yml index 3018a8416..0b4b944f7 100644 --- a/ansible/roles/splashscreen/tasks/main.yml +++ b/ansible/roles/splashscreen/tasks/main.yml @@ -3,8 +3,9 @@ # If Jessie - name: Installs dependencies (Jessie) apt: - name: - - fbi + name: "{{ item }}" + with_items: + - fbi when: ansible_distribution_major_version|int <= 7 - name: Copies in splash screen @@ -43,9 +44,10 @@ - name: Installs dependencies (not Jessie) apt: - name: - - plymouth - - pix-plym-splash + name: "{{ item }}" + with_items: + - plymouth + - pix-plym-splash when: ansible_distribution_major_version|int > 7 - name: Copies plymouth theme diff --git a/ansible/roles/ssl/tasks/main.yml b/ansible/roles/ssl/tasks/main.yml index ace2021f0..763355af6 100644 --- a/ansible/roles/ssl/tasks/main.yml +++ b/ansible/roles/ssl/tasks/main.yml @@ -78,5 +78,6 @@ - reload systemctl - restart-screenly-websocket_server_layer - restart-screenly-server + - restart-screenly-celery tags: - enable-ssl diff --git a/ansible/roles/system/files/10-evdev.conf b/ansible/roles/system/files/10-evdev.conf deleted file mode 100644 index ba37ec34d..000000000 --- a/ansible/roles/system/files/10-evdev.conf +++ /dev/null @@ -1,40 +0,0 @@ -# -# Catch-all evdev loader for udev-based systems -# We don't simply match on any device since that also adds accelerometers -# and other devices that we don't really want to use. The list below -# matches everything but joysticks. - -#Section "InputClass" -# Identifier "evdev pointer catchall" -# MatchIsPointer "on" -# MatchDevicePath "/dev/input/event*" -# Driver "evdev" -#EndSection - -Section "InputClass" - Identifier "evdev keyboard catchall" - MatchIsKeyboard "on" - MatchDevicePath "/dev/input/event*" - Driver "evdev" -EndSection - -#Section "InputClass" -# Identifier "evdev touchpad catchall" -# MatchIsTouchpad "on" -# MatchDevicePath "/dev/input/event*" -# Driver "evdev" -#EndSection - -#Section "InputClass" -# Identifier "evdev tablet catchall" -# MatchIsTablet "on" -# MatchDevicePath "/dev/input/event*" -# Driver "evdev" -#EndSection - -#Section "InputClass" -# Identifier "evdev touchscreen catchall" -# MatchIsTouchscreen "on" -# MatchDevicePath "/dev/input/event*" -# Driver "evdev" -#EndSection diff --git a/ansible/roles/system/files/10-serverflags.conf b/ansible/roles/system/files/10-serverflags.conf deleted file mode 100644 index c7c69374a..000000000 --- a/ansible/roles/system/files/10-serverflags.conf +++ /dev/null @@ -1,6 +0,0 @@ -Section "ServerFlags" - Option "BlankTime" "0" - Option "StandbyTime" "0" - Option "SuspendTime" "0" - Option "OffTime" "0" -EndSection diff --git a/ansible/roles/system/tasks/main.yml b/ansible/roles/system/tasks/main.yml index d1f49f268..859895894 100644 --- a/ansible/roles/system/tasks/main.yml +++ b/ansible/roles/system/tasks/main.yml @@ -44,6 +44,30 @@ tags: - touches_boot_partition +- name: Add gpu_mem_256 in config.txt if it doesn't exist + lineinfile: + path: /boot/config.txt + line: gpu_mem_256=96 + when: config_txt.stdout.find('gpu_mem_256') == -1 + tags: + - touches_boot_partition + +- name: Add gpu_mem_512 in config.txt if it doesn't exist + lineinfile: + path: /boot/config.txt + line: gpu_mem_512=128 + when: config_txt.stdout.find('gpu_mem_512') == -1 + tags: + - touches_boot_partition + +- name: Add gpu_mem_1024 in config.txt if it doesn't exist + lineinfile: + path: /boot/config.txt + line: gpu_mem_1024=196 + when: config_txt.stdout.find('gpu_mem_1024') == -1 + tags: + - touches_boot_partition + - name: Backup kernel boot args copy: src: /boot/cmdline.txt @@ -144,37 +168,61 @@ - name: Install Screenly dependencies apt: - name: - - console-data - - libffi-dev - - libssl-dev - - matchbox - - omxplayer - - python-dev - - python-gobject - - python-netifaces - - python-simplejson - - rabbitmq-server - - rpi-update - - sqlite3 - - systemd - - uzbl - - x11-xserver-utils - - xserver-xorg + name: "{{ item }}" state: latest - -- name: Install systemd-sysv on Wheezy - command: bash -c 'echo "Yes, do as I say!" | apt-get install -y --force-yes systemd-sysv' - when: ansible_distribution_release == "wheezy" + with_items: + - console-data + - libffi-dev + - libssl-dev + - omxplayer + - python-dev + - python-gobject + - python-netifaces + - python-simplejson + - rabbitmq-server + - rpi-update + - sqlite3 + - systemd - name: Remove deprecated apt dependencies apt: - name: - - supervisor - - lightdm - - lightdm-gtk-greeter - - dphys-swapfile + name: "{{ item }}" state: absent + with_items: + - dphys-swapfile + - lightdm + - lightdm-gtk-greeter + - matchbox + - uzbl + - x11-xserver-utils + - xserver-xorg + +- name: Add docker apt key + apt_key: + url: https://download.docker.com/linux/raspbian/gpg + state: present + +- name : Get raspbian name + command: lsb_release -cs + register: raspbian_name + +- name: Add Docker repo + lineinfile: + path: /etc/apt/sources.list.d/docker.list + create: yes + line: "deb [arch=armhf] https://download.docker.com/linux/raspbian {{ raspbian_name.stdout }} edge" + state: present + +- name: Install Docker + apt: + name: docker-ce + update_cache: yes + install_recommends: no + +- name: Add pi to docker group + user: + name: pi + group: docker - name: Perform system upgrade apt: @@ -209,31 +257,6 @@ owner: root group: root -- name: Copy in evdev - copy: - src: 10-evdev.conf - dest: /usr/share/X11/xorg.conf.d/10-evdev.conf - mode: 0644 - owner: root - group: root - -- name: Disable DPMS - copy: - src: 10-serverflags.conf - dest: /usr/share/X11/xorg.conf.d/10-serverflags.conf - mode: 0644 - owner: root - group: root - -- name: Clear out X11 configs (disables touchpad and other unnecessary things) - file: - path: "/usr/share/X11/xorg.conf.d/{{ item }}" - state: absent - with_items: - - 50-synaptics.conf - - 10-quirks.conf - - 50-wacom.conf - - name: Disable swap command: /sbin/swapoff --all removes=/var/swap diff --git a/ansible/site.yml b/ansible/site.yml index d16b68967..38e335436 100644 --- a/ansible/site.yml +++ b/ansible/site.yml @@ -4,6 +4,8 @@ become: yes vars: manage_network: "{{ lookup('env', 'MANAGE_NETWORK') }}" + docker_tag: "{{ lookup('env', 'DOCKER_TAG') }}" + device_type: "{{ lookup('env', 'DEVICE_TYPE') }}" handlers: - name: restart-nginx @@ -20,14 +22,8 @@ - name: restart-screenly-server command: systemctl restart screenly-web.service - - name: restart-x-server - command: "systemctl restart {{ item }}" - with_items: - - X.service - - matchbox.service - - - name: restart-screenly-viewer - command: systemctl restart screenly-viewer.service + - name: restart-screenly-celery + command: systemctl restart screenly-celery.service roles: - system diff --git a/bin/start_balena.sh b/bin/start_balena.sh index 5a2a8d453..2f2210898 100755 --- a/bin/start_balena.sh +++ b/bin/start_balena.sh @@ -3,7 +3,6 @@ run_setup () { mkdir -p \ /data/.config \ - /data/.config/uzbl \ /data/.screenly \ /data/screenly \ /data/screenly_assets @@ -11,7 +10,6 @@ run_setup () { cp -n /tmp/screenly/ansible/roles/screenly/files/screenly.conf /data/.screenly/screenly.conf cp -n /tmp/screenly/ansible/roles/screenly/files/default_assets.yml /data/.screenly/default_assets.yml cp -n /tmp/screenly/ansible/roles/screenly/files/screenly.db /data/.screenly/screenly.db - cp -n /tmp/screenly/ansible/roles/screenly/files/uzbl-config /data/.config/uzbl/config-screenly cp -rf /tmp/screenly/* /data/screenly/ diff --git a/bin/start_viewer.sh b/bin/start_viewer.sh new file mode 100644 index 000000000..94b2d0706 --- /dev/null +++ b/bin/start_viewer.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# SUGUSR1 from the viewer is also sent to the container +# Prevent it so that the container does not fall +trap '' 16 + +su - pi -c "cd /home/pi/screenly && QT_QPA_EGLFS_FORCE888=1 dbus-run-session python viewer.py &" + +# Waiting for the viewer +while true; do + PID=$(pidof python) + if [ "$?" == '0' ]; then + break + fi + sleep 0.5 +done + +# Exit when the viewer falls +while kill -0 "$PID"; do + sleep 1 +done diff --git a/docker/Dockerfile.viewer b/docker/Dockerfile.viewer new file mode 100644 index 000000000..8493a0323 --- /dev/null +++ b/docker/Dockerfile.viewer @@ -0,0 +1,74 @@ +FROM resin/rpi-raspbian:stretch + +RUN apt-get update && \ + apt-get -y install \ + build-essential \ + curl \ + git-core \ + gstreamer0.10-plugins-good \ + libdbus-glib-1-dev \ + libffi-dev \ + libqt5webkit5 \ + libraspberrypi0 \ + libssl-dev \ + libts-dev \ + net-tools \ + omxplayer \ + psmisc \ + python-dev \ + python-gobject \ + python-netifaces \ + python-pip \ + python-setuptools \ + sqlite3 && \ + apt-get clean + +RUN pip install --upgrade pip + +# Install Python requirements +ADD requirements/requirements.viewer.txt /tmp/requirements.viewer.txt +RUN pip install -r /tmp/requirements.viewer.txt + +# QtBase from packages does not support eglfs +ARG PI_VERSION=pi1 +RUN curl -L \ + "https://github.com/Screenly/screenly-ose-webview/releases/download/v0.1/qtbase-$PI_VERSION.tar.gz" \ + > /tmp/qtbase.tar.gz +RUN tar -xzf /tmp/qtbase.tar.gz -C /usr/local +RUN echo "/usr/local/qt5pi/lib" > /etc/ld.so.conf.d/00-qt5pi.conf +RUN ldconfig + +# Fix symlinks +RUN rm /usr/lib/arm-linux-gnueabihf/libEGL.so.1 +RUN rm /usr/lib/arm-linux-gnueabihf/libEGL.so.1.0.0 +RUN ln -s /opt/vc/lib/libbrcmEGL.so /usr/lib/arm-linux-gnueabihf/libEGL.so +RUN ln -s /opt/vc/lib/libbrcmEGL.so /usr/lib/arm-linux-gnueabihf/libEGL.so.1 +RUN ln -s /opt/vc/lib/libbrcmEGL.so /usr/lib/arm-linux-gnueabihf/libEGL.so.1.0.0 + +RUN rm /usr/lib/arm-linux-gnueabihf/libGLESv2.so.2 +RUN rm /usr/lib/arm-linux-gnueabihf/libGLESv2.so.2.0.0 +RUN ln -s /opt/vc/lib/libbrcmGLESv2.so /usr/lib/arm-linux-gnueabihf/libGLESv2.so +RUN ln -s /opt/vc/lib/libbrcmGLESv2.so /usr/lib/arm-linux-gnueabihf/libGLESv2.so.2 +RUN ln -s /opt/vc/lib/libbrcmGLESv2.so /usr/lib/arm-linux-gnueabihf/libGLESv2.so.2.0.0 + +ENV QT_QPA_PLATFORM eglfs + +RUN mkdir /usr/local/share/ScreenlyWebview +RUN curl -L \ + "https://github.com/Screenly/screenly-ose-webview/releases/download/0.2/screenly-webview-0.2.tar.gz" \ + > /tmp/screenly-webview-0.2.tar.gz +RUN tar -xzf /tmp/screenly-webview-0.2.tar.gz -C /tmp + +RUN cp -r /tmp/res /usr/local/share/ScreenlyWebview/res +RUN cp /tmp/ScreenlyWebview /usr/local/bin/ScreenlyWebview + +# Fix white flickering in omxplayer +ENV NOREFRESH 1 + +# Create runtime user +RUN useradd pi -d /home/pi \ + && /usr/sbin/usermod -a -G video pi + +WORKDIR /home/pi/screenly + +CMD ["bash", "bin/start_viewer.sh"] diff --git a/docker/Dockerfile.viewer.template b/docker/Dockerfile.viewer.template index cba9d8ccf..966224012 100644 --- a/docker/Dockerfile.viewer.template +++ b/docker/Dockerfile.viewer.template @@ -18,7 +18,6 @@ RUN apt-get update && \ libraspberrypi0 \ ifupdown \ sqlite3 \ - uzbl \ x11-xserver-utils \ xserver-xorg && \ apt-get clean diff --git a/requirements/requirements.txt b/requirements/requirements.txt index b7ed21f61..ac59c87f9 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -6,6 +6,7 @@ certifi==2018.4.16 cffi==1.7.0 click==6.7 configparser==3.5.0 +docker-py==1.10.6 Flask-Cors==3.0.4 flask-restful-swagger-2==0.35 flask-swagger-ui==3.0.12 diff --git a/requirements/requirements.viewer.txt b/requirements/requirements.viewer.txt new file mode 100644 index 000000000..382c08c72 --- /dev/null +++ b/requirements/requirements.viewer.txt @@ -0,0 +1,14 @@ +certifi==2018.4.16 +configparser==3.5.0 +cryptography==2.2.2 +flask==0.12.2 +idna==2.6 +mixpanel==4.3.2 +netifaces==0.10.4 +pydbus==0.6.0 +python-dateutil==2.4.2 +pytz==2012d +pyzmq==16.0.2 +requests[security]==2.18.4 +sh==1.08 +uptime==2.0.2 diff --git a/server.py b/server.py index 2a0b89ca3..69768e098 100755 --- a/server.py +++ b/server.py @@ -1447,7 +1447,7 @@ def get(self): viewlog = None try: viewlog = [line.decode('utf-8') for line in - check_output(['sudo', 'systemctl', 'status', 'screenly-viewer.service', '-n', '20']).split('\n')] + check_output(['docker', 'logs', 'screenly-ose-viewer', '--tail', '20']).split('\n')] except: pass @@ -1742,7 +1742,7 @@ def settings_page(): def system_info(): try: viewlog = [line.decode('utf-8') for line in - check_output(['sudo', 'systemctl', 'status', 'screenly-viewer.service', '-n', '20']).split('\n')] + check_output(['docker', 'logs', 'screenly-ose-viewer', '--tail', '20']).split('\n')] except Exception: viewlog = None diff --git a/viewer.py b/viewer.py index 4b098b0d1..3689625f9 100755 --- a/viewer.py +++ b/viewer.py @@ -16,12 +16,12 @@ from random import shuffle from threading import Thread from requests import get as req_get -from signal import alarm, signal, SIGALRM, SIGUSR1 +from signal import signal, SIGALRM, SIGUSR1 from time import sleep from settings import settings, LISTEN, PORT, ZmqConsumer -from lib import assets_helper, html_templates +from lib import assets_helper from lib import db from lib.diagnostics import get_git_branch, get_git_short_hash from lib.github import fetch_remote_hash, remote_branch_available @@ -40,14 +40,12 @@ INITIALIZED_FILE = '/.screenly/initialized' BLACK_PAGE = '/tmp/screenly_html/black_page.html' WATCHDOG_PATH = '/tmp/screenly.watchdog' -SCREENLY_HTML = '/tmp/screenly_html/' LOAD_SCREEN = '/screenly/static/img/loading.png' # relative to $HOME -UZBLRC = '/.config/uzbl/config-screenly' # relative to $HOME -INTRO = '/screenly/intro-template.html' current_browser_url = None browser = None loop_is_stopped = False +browser_bus = None VIDEO_TIMEOUT = 20 # secs @@ -212,8 +210,7 @@ def update_playlist(self): # Try to keep the same position in the play list. E.g. if a new asset is added to the end of the list, we # don't want to start over from the beginning. self.index = self.index % len(self.assets) if self.assets else 0 - logging.debug('update_playlist done, count %s, counter %s, index %s, deadline %s', len(self.assets), - self.counter, self.index, self.deadline) + logging.debug('update_playlist done, count %s, counter %s, index %s, deadline %s', len(self.assets), self.counter, self.index, self.deadline) def get_db_mtime(self): # get database file last modification time @@ -255,84 +252,35 @@ def watchdog(): utime(WATCHDOG_PATH, None) -def load_browser(url=None): - global browser, current_browser_url +def load_browser(): + global browser logging.info('Loading browser...') - if browser: - logging.info('killing previous uzbl %s', browser.pid) - browser.process.kill() + browser = sh.Command('ScreenlyWebview')(_bg=True, _err_to_out=True) + while 'Screenly service start' not in browser.process.stdout: + sleep(1) - if url is not None: - current_browser_url = url - # --config=- read commands (and config) from stdin - # --print-events print events to stdout - browser = sh.Command('uzbl-browser')(print_events=True, config='-', uri=current_browser_url, _bg=True) - logging.info('Browser loading %s. Running as PID %s.', current_browser_url, browser.pid) - - uzbl_rc = 'set ssl_verify = {}\n'.format('1' if settings['verify_ssl'] else '0') - with open(HOME + UZBLRC) as f: # load uzbl.rc - uzbl_rc = f.read() + uzbl_rc - browser_send(uzbl_rc) - - -def browser_get_event(): - alarm(10) - try: - event = browser.next() - except SigalrmException: - return None - alarm(0) - return event - - -def browser_send(command, cb=lambda _: True): - if not (browser is None) and browser.process.alive: - while not browser.process._pipe_queue.empty(): # flush stdout - browser_get_event() +def view_webpage(uri): + global current_browser_url - browser.process.stdin.put(command + '\n') - while True: # loop until cb returns True - try: - browser_event = browser_get_event() - except StopIteration: - break - if not browser_event: - break - if cb(browser_event): - break - else: - logging.info('browser found dead, restarting') + if browser is None or not browser.process.alive: load_browser() + if current_browser_url is not uri: + browser_bus.loadPage(uri) + current_browser_url = uri + logging.info('current url is {0}'.format(current_browser_url)) -def browser_clear(force=False): - """Load a black page. Default cb waits for the page to load.""" - browser_url('file://' + BLACK_PAGE, force=force, - cb=lambda buf: 'LOAD_FINISH' in buf and BLACK_PAGE in buf) - - -def browser_url(url, cb=lambda _: True, force=False): +def view_image(uri): global current_browser_url - if url == current_browser_url and not force: - logging.debug('Already showing %s, reloading it.', current_browser_url) - else: - current_browser_url = url - - """Uzbl handles full URI format incorrect: scheme://uname:passwd@domain:port/path - We need to escape @""" - escaped_url = current_browser_url.replace('@', '\\@') - - browser_send('uri ' + escaped_url, cb=cb) - logging.info('current url is %s', current_browser_url) - - -def view_image(uri): - browser_clear() - browser_send('js window.setimg("{0}")'.format(uri), - cb=lambda b: 'COMMAND_EXECUTED' in b and 'setimg' in b) + if browser is None or not browser.process.alive: + load_browser() + if current_browser_url is not uri: + browser_bus.loadImage(uri) + current_browser_url = uri + logging.info('current url is {0}'.format(current_browser_url)) def view_video(uri, duration): @@ -340,7 +288,7 @@ def view_video(uri, duration): if arch in ('armv6l', 'armv7l'): player_args = ['omxplayer', uri] - player_kwargs = {'o': settings['audio_output'], '_bg': True, '_ok_code': [0, 124, 143]} + player_kwargs = {'o': settings['audio_output'], 'layer': 1, '_bg': True, '_ok_code': [0, 124, 143]} else: player_args = ['mplayer', uri, '-nosound'] player_kwargs = {'_bg': True, '_ok_code': [0, 124]} @@ -350,7 +298,7 @@ def view_video(uri, duration): run = sh.Command(player_args[0])(*player_args[1:], **player_kwargs) - browser_clear(force=True) + view_image('null') try: while run.process.alive: watchdog() @@ -451,9 +399,7 @@ def asset_loop(scheduler): if 'image' in mime: view_image(uri) elif 'web' in mime: - # FIXME If we want to force periodic reloads of repeated web assets, force=True could be used here. - # See e38e6fef3a70906e7f8739294ffd523af6ce66be. - browser_url(uri) + view_webpage(uri) elif 'video' or 'streaming' in mime: view_video(uri, asset['duration']) else: @@ -470,7 +416,7 @@ def asset_loop(scheduler): def setup(): - global HOME, arch, db_conn + global HOME, arch, db_conn, browser_bus HOME = getenv('HOME', '/home/pi') arch = machine() @@ -480,8 +426,9 @@ def setup(): load_settings() db_conn = db.conn(settings['database']) - sh.mkdir(SCREENLY_HTML, p=True) - html_templates.black_page(BLACK_PAGE) + load_browser() + bus = pydbus.SessionBus() + browser_bus = bus.get('screenly.webview', '/Screenly') def setup_hotspot(): @@ -508,7 +455,7 @@ def setup_hotspot(): if not path.isfile(HOME + INITIALIZED_FILE) and not gateways().get('default'): if len(wireless_connections) == 0: url = 'http://{0}/hotspot'.format(LISTEN) - load_browser(url=url) + view_webpage(url) # Wait until the network is configured while not path.isfile(HOME + INITIALIZED_FILE) and not gateways().get('default'): @@ -549,15 +496,12 @@ def main(): scheduler = Scheduler() - if is_balena_app(): - load_browser() - else: + if not is_balena_app(): setup_hotspot() - url = 'http://{0}:{1}/splash-page'.format(LISTEN, PORT) if settings['show_splash'] else 'file://' + BLACK_PAGE - browser_url(url=url) - if settings['show_splash']: + url = 'http://{0}:{1}/splash-page'.format(LISTEN, PORT) if settings['show_splash'] else 'file://' + BLACK_PAGE + view_webpage(url) sleep(SPLASH_DELAY) # We don't want to show splash-page if there are active assets but all of them are not available From dd114aec3cfcf6a930631c082612752c4e1e5792 Mon Sep 17 00:00:00 2001 From: Rusko124 Date: Wed, 9 Oct 2019 10:35:15 +0600 Subject: [PATCH 016/224] Changes: viewer_test --- tests/viewer_test.py | 53 ++++++-------------------------------------- 1 file changed, 7 insertions(+), 46 deletions(-) diff --git a/tests/viewer_test.py b/tests/viewer_test.py index 33c51058f..519d84039 100644 --- a/tests/viewer_test.py +++ b/tests/viewer_test.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from nose.tools import ok_, eq_ +from nose.tools import eq_ from nose.plugins.attrib import attr import mock import unittest @@ -24,9 +24,6 @@ def setUp(self): self.m_cmd = mock.Mock(name='m_cmd') self.p_cmd = mock.patch.object(self.u.sh, 'Command', self.m_cmd) - self.m_send = mock.Mock(name='m_send') - self.p_send = mock.patch.object(self.u, 'browser_send', self.m_send) - self.m_killall = mock.Mock(name='killall') self.p_killall = mock.patch.object(self.u.sh, 'killall', self.m_killall) @@ -52,55 +49,19 @@ def test_empty(self): self.u.main() -class TestBrowserSend(ViewerTestCase): - def test_send(self): - self.p_cmd.start() - self.p_send.start() - self.u.setup() - self.u.load_browser() - self.p_send.stop() - self.p_cmd.stop() - - m_put = mock.Mock(name='uzbl_put') - self.m_cmd.return_value.return_value.process.stdin.put = m_put - - self.u.browser_send('test_cmd') - m_put.assert_called_once_with('test_cmd\n') - - self.u.browser_send('event TITLE 标题') - m_put.assert_called_with('event TITLE \xe6\xa0\x87\xe9\xa2\x98\n') - - def test_dead(self): - self.p_loadb.start() - self.u.browser_send(None) - self.m_loadb.assert_called_once() - self.p_loadb.stop() - - -class TestBrowserClear(ViewerTestCase): - def test_clear(self): - with mock.patch.object(self.u, 'browser_url', mock.Mock()) as m_url: - self.u.setup() - self.u.browser_clear() - m_url.assert_called_once() - - class TestLoadBrowser(ViewerTestCase): + @mock.patch('pydbus.SessionBus', mock.MagicMock()) def test_setup(self): + self.p_loadb.start() self.u.setup() - ok_(os.path.isdir(self.u.SCREENLY_HTML)) + self.p_loadb.stop() - def load_browser(self): - m_uzbl = mock.Mock(name='uzbl') - self.m_cmd.return_value = m_uzbl + def test_load_browser(self): + self.m_cmd.return_value.return_value.process.stdout = 'Screenly service start' self.p_cmd.start() - self.p_send.start() self.u.load_browser() - self.p_send.stop() self.p_cmd.stop() - self.m_cmd.assert_called_once_with('uzbl-browser') - m_uzbl.assert_called_once_with(print_events=True, config='-', uri=None, _bg=True) - m_send.assert_called_once() + self.m_cmd.assert_called_once_with('ScreenlyWebview') class TestSignalHandlers(ViewerTestCase): From e8afedfb956a311a5187e41f0399af8435f38516 Mon Sep 17 00:00:00 2001 From: Rusko124 Date: Wed, 9 Oct 2019 11:30:39 +0600 Subject: [PATCH 017/224] Fix: viewers logs in UI --- ansible/roles/screenly/tasks/main.yml | 1 + server.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ansible/roles/screenly/tasks/main.yml b/ansible/roles/screenly/tasks/main.yml index bea7d5346..031c9d6b2 100644 --- a/ansible/roles/screenly/tasks/main.yml +++ b/ansible/roles/screenly/tasks/main.yml @@ -164,6 +164,7 @@ restart_policy: unless-stopped network_mode: host privileged: yes + log_driver: journald volumes: - /home/pi/screenly:/home/pi/screenly - /home/pi/.screenly:/home/pi/.screenly diff --git a/server.py b/server.py index 69768e098..bb6145a1a 100755 --- a/server.py +++ b/server.py @@ -1447,7 +1447,7 @@ def get(self): viewlog = None try: viewlog = [line.decode('utf-8') for line in - check_output(['docker', 'logs', 'screenly-ose-viewer', '--tail', '20']).split('\n')] + check_output(['journalctl', '-b', 'CONTAINER_NAME=screenly-ose-viewer', '-n', '20']).split('\n')] except: pass @@ -1742,7 +1742,7 @@ def settings_page(): def system_info(): try: viewlog = [line.decode('utf-8') for line in - check_output(['docker', 'logs', 'screenly-ose-viewer', '--tail', '20']).split('\n')] + check_output(['journalctl', '-b', 'CONTAINER_NAME=screenly-ose-viewer', '-n', '20']).split('\n')] except Exception: viewlog = None From e32cea1f58d556d2e4f4faf5bae1cd6574be4af0 Mon Sep 17 00:00:00 2001 From: Rusko124 Date: Mon, 14 Oct 2019 09:59:11 +0600 Subject: [PATCH 018/224] Edits: initial balena changes --- bin/start_balena.sh | 32 +++++++++---------- docker-compose.yml | 2 +- docker/Dockerfile.viewer.template | 53 ++++++++++++++++++++++++------- 3 files changed, 57 insertions(+), 30 deletions(-) diff --git a/bin/start_balena.sh b/bin/start_balena.sh index 2f2210898..154cf5f30 100755 --- a/bin/start_balena.sh +++ b/bin/start_balena.sh @@ -34,10 +34,7 @@ run_viewer () { while true; do - /usr/bin/X 0<&- &>/dev/null & - /usr/bin/matchbox-window-manager -use_titlebar no -use_cursor no 0<&- &>/dev/null & - - error=$(/usr/bin/xset s off 2>&1 | grep -c "unable to open display") + error=$(curl 127.0.0.1:8080 2>&1 | grep -c "Failed to connect") if [[ "$error" -eq 0 ]]; then break fi @@ -46,22 +43,23 @@ run_viewer () { sleep 1 done - /usr/bin/xset -dpms - /usr/bin/xset s noblank - - while true; do + trap '' 16 - error=$(curl 127.0.0.1:8080 2>&1 | grep -c "Failed to connect") - if [[ "$error" -eq 0 ]]; then - break - fi + cd /data/screenly && QT_QPA_EGLFS_FORCE888=1 HOME=/data dbus-run-session python viewer.py & - echo "Still continue..." - sleep 1 + # Waiting for the viewer + while true; do + PID=$(pidof python) + if [ "$?" == '0' ]; then + break + fi + sleep 0.5 done - cd /data/screenly - /usr/bin/python viewer.py + # Exit when the viewer falls + while kill -0 "$PID"; do + sleep 1 + done } run_server () { @@ -98,4 +96,4 @@ fi if [[ "$SCREENLYSERVICE" = "celery" ]]; then run_celery -fi \ No newline at end of file +fi diff --git a/docker-compose.yml b/docker-compose.yml index 05fb30400..954667189 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,11 +21,11 @@ services: depends_on: - screenly-server environment: - - DISPLAY=:0.0 - HOME=/data - NOREFRESH=1 - PYTHONPATH=/data/screenly - SCREENLYSERVICE=viewer + - QT_QPA_PLATFORM=eglfs network_mode: "host" privileged: true volumes: diff --git a/docker/Dockerfile.viewer.template b/docker/Dockerfile.viewer.template index 966224012..b5005d388 100644 --- a/docker/Dockerfile.viewer.template +++ b/docker/Dockerfile.viewer.template @@ -1,35 +1,64 @@ -FROM balenalib/%%BALENA_MACHINE_NAME%%-debian:jessie +FROM balenalib/%%BALENA_MACHINE_NAME%%-debian:stretch RUN apt-get update && \ apt-get -y install \ build-essential \ curl \ git-core \ + gstreamer1.0-plugins-good \ + libdbus-glib-1-dev \ libffi-dev \ + libqt5webkit5 \ + libraspberrypi0 \ libssl-dev \ - matchbox \ + libts-dev \ net-tools \ omxplayer \ psmisc \ python-dev \ - python-imaging \ + python-gobject \ python-netifaces \ - python-simplejson \ - libraspberrypi0 \ - ifupdown \ - sqlite3 \ - x11-xserver-utils \ - xserver-xorg && \ + python-pip \ + python-setuptools \ + sqlite3 && \ apt-get clean # Install Python requirements -ADD requirements/requirements.txt /tmp/requirements.txt +ADD requirements/requirements.viewer.txt /tmp/requirements.txt RUN curl -s https://bootstrap.pypa.io/get-pip.py | python && \ pip install --upgrade -r /tmp/requirements.txt -COPY ansible/roles/screenly/files/gtkrc-2.0 /data/.gtkrc-2.0 +# QtBase from packages does not support eglfs +ARG PI_VERSION=pi3 +RUN curl -L \ + "https://github.com/Screenly/screenly-ose-webview/releases/download/v0.1/qtbase-$PI_VERSION.tar.gz" \ + > /tmp/qtbase.tar.gz +RUN tar -xzf /tmp/qtbase.tar.gz -C /usr/local +RUN echo "/usr/local/qt5pi/lib" > /etc/ld.so.conf.d/00-qt5pi.conf +RUN ldconfig + +# Fix symlinks +RUN rm -f /usr/lib/arm-linux-gnueabihf/libEGL.so.1 +RUN rm -f /usr/lib/arm-linux-gnueabihf/libEGL.so.1.0.0 +RUN ln -s /opt/vc/lib/libbrcmEGL.so /usr/lib/arm-linux-gnueabihf/libEGL.so +RUN ln -s /opt/vc/lib/libbrcmEGL.so /usr/lib/arm-linux-gnueabihf/libEGL.so.1 +RUN ln -s /opt/vc/lib/libbrcmEGL.so /usr/lib/arm-linux-gnueabihf/libEGL.so.1.0.0 + +RUN rm -f /usr/lib/arm-linux-gnueabihf/libGLESv2.so.2 +RUN rm -f /usr/lib/arm-linux-gnueabihf/libGLESv2.so.2.0.0 +RUN ln -s /opt/vc/lib/libbrcmGLESv2.so /usr/lib/arm-linux-gnueabihf/libGLESv2.so +RUN ln -s /opt/vc/lib/libbrcmGLESv2.so /usr/lib/arm-linux-gnueabihf/libGLESv2.so.2 +RUN ln -s /opt/vc/lib/libbrcmGLESv2.so /usr/lib/arm-linux-gnueabihf/libGLESv2.so.2.0.0 + +RUN mkdir /usr/local/share/ScreenlyWebview +RUN curl -L \ + "https://github.com/Screenly/screenly-ose-webview/releases/download/0.2/screenly-webview-0.2.tar.gz" \ + > /tmp/screenly-webview-0.2.tar.gz +RUN tar -xzf /tmp/screenly-webview-0.2.tar.gz -C /tmp + +RUN cp -r /tmp/res /usr/local/share/ScreenlyWebview/res +RUN cp /tmp/ScreenlyWebview /usr/local/bin/ScreenlyWebview COPY . /tmp/screenly -CMD ["bash", "chmod 777 /dev/vchiq"] CMD ["bash", "/tmp/screenly/bin/start_balena.sh"] From 21269ed3d50fa01666b3edd0d64d37b59bd18840 Mon Sep 17 00:00:00 2001 From: Emyll Almonte Date: Tue, 14 Jan 2020 13:16:06 -0500 Subject: [PATCH 019/224] Update install.sh adding autoremove and version.md file creation for screenly details.. --- bin/install.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bin/install.sh b/bin/install.sh index a7b59d074..8726e915f 100755 --- a/bin/install.sh +++ b/bin/install.sh @@ -98,6 +98,7 @@ ansible-playbook site.yml $EXTRA_ARGS sudo apt-get autoclean sudo apt-get clean +sudo apt autoremove -y sudo find /usr/share/doc -depth -type f ! -name copyright -delete sudo find /usr/share/doc -empty -delete sudo rm -rf /usr/share/man /usr/share/groff /usr/share/info /usr/share/lintian /usr/share/linda /var/cache/man @@ -106,6 +107,8 @@ sudo find /usr/share/locale -mindepth 1 -maxdepth 1 ! -name 'en*' ! -name 'de*' cd ~/screenly && git rev-parse HEAD > ~/.screenly/latest_screenly_sha +echo -e "Screenly version: $(git rev-parse --abbrev-ref HEAD)@$(git rev-parse --short HEAD)\n$(lsb_release -a)" > ~/version.md + set +x echo "Installation completed." From 52e5323b1e8015f9ae17bbacc5b6df77e7f482e2 Mon Sep 17 00:00:00 2001 From: Emyll Almonte Date: Fri, 13 Mar 2020 22:34:05 -0400 Subject: [PATCH 020/224] Update main.yml updating due to ansible deprecation with loop and {{ items }} --- ansible/roles/system/tasks/main.yml | 40 ++++++++++++++--------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/ansible/roles/system/tasks/main.yml b/ansible/roles/system/tasks/main.yml index 46f93f1eb..12405273a 100644 --- a/ansible/roles/system/tasks/main.yml +++ b/ansible/roles/system/tasks/main.yml @@ -168,32 +168,30 @@ - name: Install Screenly dependencies apt: - name: "{{ item }}" + name: + - console-data + - libffi-dev + - libssl-dev + - python-dev + - python-netifaces + - rpi-update + - systemd state: latest - with_items: - - console-data - - libffi-dev - - libssl-dev - - python-dev - - python-netifaces - - rpi-update - - systemd - name: Remove deprecated apt dependencies apt: - name: "{{ item }}" + name: + - dphys-swapfile + - lightdm + - lightdm-gtk-greeter + - matchbox + - omxplayer + - sqlite3 + - supervisor + - uzbl + - x11-xserver-utils + - xserver-xorg state: absent - with_items: - - dphys-swapfile - - lightdm - - lightdm-gtk-greeter - - matchbox - - omxplayer - - sqlite3 - - supervisor - - uzbl - - x11-xserver-utils - - xserver-xorg - name: Add docker apt key apt_key: From 5f3f0e7458b6f7e712366631f14b9e112a81ba7a Mon Sep 17 00:00:00 2001 From: Emyll Almonte Date: Wed, 21 Oct 2020 18:34:41 -0400 Subject: [PATCH 021/224] Update main.yml --- ansible/roles/screenly/tasks/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ansible/roles/screenly/tasks/main.yml b/ansible/roles/screenly/tasks/main.yml index bd1759c91..7292c6d3d 100644 --- a/ansible/roles/screenly/tasks/main.yml +++ b/ansible/roles/screenly/tasks/main.yml @@ -50,8 +50,8 @@ - cron: name: Cleanup screenly_assets - minute: 0 - hour: 1 + minute: "0" + hour: "1" job: "/usr/local/bin/screenly_utils.sh cleanup" user: pi From b81b4aa8f96871eb9e5e819d4d6b2746497f5bee Mon Sep 17 00:00:00 2001 From: Emyll Almonte Date: Wed, 21 Oct 2020 18:42:13 -0400 Subject: [PATCH 022/224] Update main.yml --- ansible/roles/screenly/tasks/main.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ansible/roles/screenly/tasks/main.yml b/ansible/roles/screenly/tasks/main.yml index 7292c6d3d..70ee66216 100644 --- a/ansible/roles/screenly/tasks/main.yml +++ b/ansible/roles/screenly/tasks/main.yml @@ -48,8 +48,9 @@ owner: root group: root -- cron: - name: Cleanup screenly_assets +- name: Create screenly_assets cleanup cron job + cron: + name: "Cleanup screenly_assets" minute: "0" hour: "1" job: "/usr/local/bin/screenly_utils.sh cleanup" From a9eb810a5d97dc9274cba4a8885b7b26dc8555d1 Mon Sep 17 00:00:00 2001 From: Emyll Almonte Date: Wed, 21 Oct 2020 18:59:53 -0400 Subject: [PATCH 023/224] Update main.yml --- ansible/roles/network/tasks/main.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ansible/roles/network/tasks/main.yml b/ansible/roles/network/tasks/main.yml index f5e6da676..09ab62fb3 100644 --- a/ansible/roles/network/tasks/main.yml +++ b/ansible/roles/network/tasks/main.yml @@ -13,7 +13,7 @@ command: systemctl disable screenly-net-watchdog.timer when: screenly_network_manager_exist -- name: Remove network manger and watchdog +- name: Remove network manager and watchdog file: state: absent path: "/usr/sbin/{{ item }}" @@ -60,16 +60,16 @@ - set_fact: resin_wifi_version_file_exist="{{resin_wifi_version_file.stat.exists}}" when: ansible_distribution_major_version|int >= 9 -- name: Download resin-wifi-connect release +- name: Download balena wifi-connect release get_url: - url: "https://github.com/resin-io/resin-wifi-connect/releases/download/v{{ resin_wifi_connect_version }}/wifi-connect-v{{ resin_wifi_connect_version }}-linux-rpi.tar.gz" + url: "https://github.com/balena-io/wifi-connect/releases/download/v{{ resin_wifi_connect_version }}/wifi-connect-v{{ resin_wifi_connect_version }}-linux-rpi.tar.gz" dest: /home/pi/resin-wifi-connect.tar.gz when: - ansible_distribution_major_version|int >= 9 - not resin_wifi_version_file_exist - manage_network|bool == true -- name: Unarchive resin-wifi-connect release +- name: Unarchive balena wifi-connect release unarchive: src: /home/pi/resin-wifi-connect.tar.gz dest: /home/pi @@ -100,7 +100,7 @@ - not resin_wifi_version_file_exist - manage_network|bool == true -- name: Touch resin-wifi-connect version file +- name: Touch balena wifi-connect version file file: state: touch path: "/usr/local/share/wifi-connect/ui/{{ resin_wifi_connect_version }}" From 3ed526fbcd31e45fbc0dc6e852e70a262b588a4e Mon Sep 17 00:00:00 2001 From: Emyll Almonte Date: Wed, 21 Oct 2020 19:20:43 -0400 Subject: [PATCH 024/224] Update main.yml --- ansible/roles/splashscreen/tasks/main.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/ansible/roles/splashscreen/tasks/main.yml b/ansible/roles/splashscreen/tasks/main.yml index ebab3410a..0ba9b9454 100644 --- a/ansible/roles/splashscreen/tasks/main.yml +++ b/ansible/roles/splashscreen/tasks/main.yml @@ -3,9 +3,7 @@ # If Jessie - name: Installs dependencies (Jessie) apt: - name: "{{ item }}" - with_items: - - fbi + name: fbi when: ansible_distribution_major_version|int <= 7 - name: Copies in splash screen @@ -44,9 +42,7 @@ - name: Installs dependencies (not Jessie) apt: - name: "{{ item }}" - with_items: - - plymouth + name: plymouth when: ansible_distribution_major_version|int > 7 - name: Copies plymouth theme From 66894eb980789c895be95dc69dbf1bcd0ea78e15 Mon Sep 17 00:00:00 2001 From: Rusko124 Date: Tue, 3 Nov 2020 13:58:30 +0600 Subject: [PATCH 025/224] Edits: ansible tasks and travis.yml --- .travis.yml | 57 +++++++++++++------ .../roles/screenly/files/screenly-web.service | 15 +++++ .../screenly-websocket_server_layer.service | 11 ++++ 3 files changed, 65 insertions(+), 18 deletions(-) create mode 100644 ansible/roles/screenly/files/screenly-web.service create mode 100644 ansible/roles/screenly/files/screenly-websocket_server_layer.service diff --git a/.travis.yml b/.travis.yml index b37864c3c..a4a03c414 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,29 +1,50 @@ language: python python: - "2.7" - -services: - - docker - +virtualenv: + system_site_packages: true +addons: + firefox: "45.0" + apt: + update: true + packages: + - net-tools + - mplayer + - ffmpeg + - python-gobject + - python-setuptools install: - - docker build . -f ./docker/Dockerfile.travis -t screenly/travis-ci - - docker run --name travis-ci -v $TRAVIS_BUILD_DIR:/root/screenly -td screenly/travis-ci /bin/bash - + - pip install -U pip + - pip install -r requirements/requirements.txt + - pip install -r requirements/requirements.dev.txt before_script: - - docker exec travis-ci bash -c "echo -e '[local]\nlocalhost ansible_connection=local' > ansible/localhost" - - docker exec travis-ci bash -c "python tests/rtmplite/rtmp.py -r /tmp/" & - - sleep 3 - - docker exec travis-ci bash -c "python server.py" & - - sleep 3 - - docker exec travis-ci bash -c "Xvfb :99 -ac" & + - mkdir -p ~/.screenly ~/screenly_assets /tmp/USB/cleanup_folder + - cp ansible/roles/screenly/files/screenly.conf ~/.screenly/ + - cp ansible/roles/screenly/files/screenly.db ~/.screenly/ + - sudo cp bin/install.sh /usr/local/sbin/upgrade_screenly.sh + - sudo chmod 0700 /usr/local/sbin/upgrade_screenly.sh + - echo -e "[local]\nlocalhost ansible_connection=local" > ansible/localhost + - curl https://www.screenly.io/upload/ose-logo.png > /tmp/image.png + - cp /tmp/image.png /tmp/USB/image.png + - cp /tmp/image.png /tmp/USB/cleanup_folder/image.png + - curl https://www.screenly.io/upload/ose-logo.png > ~/screenly_assets/image.tmp + - curl https://www.screenly.io/upload/big_buck_bunny_720p_10mb.flv > /tmp/video.flv + - cp tests/assets/asset.mov /tmp/asset.mov + - export DISPLAY=:99.0 + - /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16 + - sudo cp tests/config/ffserver.conf /etc/ffserver.conf + - /usr/bin/ffserver -f /etc/ffserver.conf & - sleep 3 + - python server.py & + - sleep 3 # give xvfb some time to start + script: - - docker exec -ti travis-ci bash -c "find . ! -path '*/rtmplite/*' -name \*.py -exec pep8 --ignore=E402,E501,E731 {} +" - - docker exec -ti travis-ci bash -c "nosetests -v -a '!fixme' --with-isolation" - - docker exec -ti travis-ci bash -c "ansible-playbook --syntax-check -i ansible/localhost ansible/site.yml" - - python -m SimpleHTTPServer 8080 & + - find . ! -path "*/rtmplite/*" -name \*.py -exec pep8 --ignore=E402,E501,E731 {} + + - nosetests -v -a '!fixme' + - ansible-playbook --syntax-check -i ansible/localhost ansible/site.yml + - python -m SimpleHTTPServer 8081 & - sleep 3 - bash static/spec/runner.sh sudo: false -dist: trusty +dist: xenial diff --git a/ansible/roles/screenly/files/screenly-web.service b/ansible/roles/screenly/files/screenly-web.service new file mode 100644 index 000000000..15051195c --- /dev/null +++ b/ansible/roles/screenly/files/screenly-web.service @@ -0,0 +1,15 @@ +[Unit] +Description=Screenly Web UI +After=network-online.target + +[Service] +WorkingDirectory=/home/pi/screenly +User=pi +ExecStartPre=/usr/bin/python /home/pi/screenly/bin/wait.py +ExecStart=/usr/bin/python /home/pi/screenly/server.py +Restart=always +RestartSec=5 +Environment=PYTHONPATH=/home/pi/screenly + +[Install] +WantedBy=multi-user.target diff --git a/ansible/roles/screenly/files/screenly-websocket_server_layer.service b/ansible/roles/screenly/files/screenly-websocket_server_layer.service new file mode 100644 index 000000000..e196768c0 --- /dev/null +++ b/ansible/roles/screenly/files/screenly-websocket_server_layer.service @@ -0,0 +1,11 @@ +[Unit] +Description=Websocket Server layer + +[Service] +WorkingDirectory=/home/pi/screenly +User=pi +ExecStart=/usr/bin/python /home/pi/screenly/websocket_server_layer.py +Restart=always + +[Install] +WantedBy=multi-user.target \ No newline at end of file From b77e7da33a174edffa7e7dc0b1a393abfad5e470 Mon Sep 17 00:00:00 2001 From: Rusko124 Date: Tue, 3 Nov 2020 15:14:22 +0600 Subject: [PATCH 026/224] Fix: ansible screenly role --- ansible/roles/screenly/tasks/main.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/ansible/roles/screenly/tasks/main.yml b/ansible/roles/screenly/tasks/main.yml index d9917e37b..1c6353424 100644 --- a/ansible/roles/screenly/tasks/main.yml +++ b/ansible/roles/screenly/tasks/main.yml @@ -110,7 +110,6 @@ src: "{{ item }}" dest: "/etc/systemd/system/{{ item }}" with_items: "{{ screenly_systemd_units }}" - when: x_service_exist - name: Remove plymouth-quit-wait.service file: From ab91057f357b9e3f8a9378a87c46eec325af8b18 Mon Sep 17 00:00:00 2001 From: Rusko124 Date: Tue, 3 Nov 2020 17:34:29 +0600 Subject: [PATCH 027/224] Fix for successfull running --- ansible/roles/screenly/tasks/main.yml | 2 +- package-lock.json | 6 + package.json | 3 + static/js/screenly-ose.js | 1672 ++++++++++++------------- static/js/screenly-ose.js.map | 5 +- viewer.py | 2 +- 6 files changed, 841 insertions(+), 849 deletions(-) diff --git a/ansible/roles/screenly/tasks/main.yml b/ansible/roles/screenly/tasks/main.yml index 1c6353424..ad60aa60d 100644 --- a/ansible/roles/screenly/tasks/main.yml +++ b/ansible/roles/screenly/tasks/main.yml @@ -159,7 +159,7 @@ - name: Create screenly-viewer container docker_container: name: screenly-ose-viewer - image: "screenly/screenly-ose-viewer:{{ docker_tag }}-{{ device_type }}" + image: "ruskodevelop/screenly-ose-viewer:latest" pull: true state: started recreate: true diff --git a/package-lock.json b/package-lock.json index 705a75280..ffe186c6d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,12 @@ "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.3.1.tgz", "integrity": "sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag==" }, + "coffee-script": { + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.7.tgz", + "integrity": "sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==", + "dev": true + }, "css-toggle-switch": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/css-toggle-switch/-/css-toggle-switch-4.1.0.tgz", diff --git a/package.json b/package.json index 48535251a..7599217fc 100644 --- a/package.json +++ b/package.json @@ -4,5 +4,8 @@ "dependencies": { "bootstrap": "^4.3.1", "css-toggle-switch": "^4.1.0" + }, + "devDependencies": { + "coffee-script": "^1.12.7" } } diff --git a/static/js/screenly-ose.js b/static/js/screenly-ose.js index 57aa7d188..7a474c663 100644 --- a/static/js/screenly-ose.js +++ b/static/js/screenly-ose.js @@ -1,10 +1,14 @@ -// Generated by CoffeeScript 2.5.1 +// Generated by CoffeeScript 1.12.7 + +/* screenly-ose ui */ + (function() { - /* screenly-ose ui */ var API, AddAssetView, App, Asset, AssetRowView, Assets, AssetsView, EditAssetView, dateSettings, date_to, delay, domains, durationSecondsToHumanReadable, getMimetype, get_filename, get_template, insertWbr, mimetypes, now, truncate_str, url_test, viduris, - indexOf = [].indexOf, - boundMethodCheck = function(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new Error('Bound instance method accessed before binding'); } }, - hasProp = {}.hasOwnProperty; + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, + bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty, + slice = [].slice; $().ready(function() { return $('#subsribe-form-container').popover({ @@ -30,11 +34,10 @@ dateSettings.datepickerFormat = dateFormat; - dateSettings.fullDate = `${dateSettings.date} ${dateSettings.fullTime}`; + dateSettings.fullDate = dateSettings.date + " " + dateSettings.fullTime; API.date_to = date_to = function(d) { var dd; - // Cross-browser UTC to localtime conversion dd = moment.utc(d).local(); return { string: function() { @@ -54,7 +57,7 @@ }; get_template = function(name) { - return _.template(($(`#${name}-template`)).html()); + return _.template(($("#" + name + "-template")).html()); }; delay = function(wait, fn) { @@ -122,82 +125,79 @@ return (v.replace(/\//g, '/')).replace(/\&/g, '&'); }; - // Tell Backbone to send its saves as JSON-encoded. Backbone.emulateJSON = false; - // Models - API.Asset = Asset = (function() { - class Asset extends Backbone.Model { - constructor() { - super(...arguments); - this.active = this.active.bind(this); - this.backup = this.backup.bind(this); - this.rollback = this.rollback.bind(this); - this.old_name = this.old_name.bind(this); - } + API.Asset = Asset = (function(superClass) { + extend(Asset, superClass); - defaults() { - return { - name: '', - mimetype: 'webpage', - uri: '', - is_active: 1, - start_date: '', - end_date: '', - duration: defaultDuration, - is_enabled: 0, - is_processing: 0, - nocache: 0, - play_order: 0, - skip_asset_check: 0 - }; - } + function Asset() { + this.old_name = bind(this.old_name, this); + this.rollback = bind(this.rollback, this); + this.backup = bind(this.backup, this); + this.active = bind(this.active, this); + return Asset.__super__.constructor.apply(this, arguments); + } - active() { - var at, end_date, start_date; - boundMethodCheck(this, Asset); - if (this.get('is_enabled') && this.get('start_date') && this.get('end_date')) { - at = now(); - start_date = new Date(this.get('start_date')); - end_date = new Date(this.get('end_date')); - return (start_date <= at && at <= end_date); - } else { - return false; - } - } + Asset.prototype.idAttribute = "asset_id"; - backup() { - boundMethodCheck(this, Asset); - return this.backup_attributes = this.toJSON(); - } + Asset.prototype.fields = 'name mimetype uri start_date end_date duration skip_asset_check'.split(' '); - rollback() { - boundMethodCheck(this, Asset); - if (this.backup_attributes) { - this.set(this.backup_attributes); - return this.backup_attributes = void 0; - } - } + Asset.prototype.defaults = function() { + return { + name: '', + mimetype: 'webpage', + uri: '', + is_active: 1, + start_date: '', + end_date: '', + duration: defaultDuration, + is_enabled: 0, + is_processing: 0, + nocache: 0, + play_order: 0, + skip_asset_check: 0 + }; + }; - old_name() { - boundMethodCheck(this, Asset); - if (this.backup_attributes) { - return this.backup_attributes.name; - } + Asset.prototype.active = function() { + var at, end_date, start_date; + if (this.get('is_enabled') && this.get('start_date') && this.get('end_date')) { + at = now(); + start_date = new Date(this.get('start_date')); + end_date = new Date(this.get('end_date')); + return (start_date <= at && at <= end_date); + } else { + return false; } + }; + Asset.prototype.backup = function() { + return this.backup_attributes = this.toJSON(); }; - Asset.prototype.idAttribute = "asset_id"; + Asset.prototype.rollback = function() { + if (this.backup_attributes) { + this.set(this.backup_attributes); + return this.backup_attributes = void 0; + } + }; - Asset.prototype.fields = 'name mimetype uri start_date end_date duration skip_asset_check'.split(' '); + Asset.prototype.old_name = function() { + if (this.backup_attributes) { + return this.backup_attributes.name; + } + }; return Asset; - }).call(this); + })(Backbone.Model); + + API.Assets = Assets = (function(superClass) { + extend(Assets, superClass); - API.Assets = Assets = (function() { - class Assets extends Backbone.Collection {}; + function Assets() { + return Assets.__super__.constructor.apply(this, arguments); + } Assets.prototype.url = "/api/v1.2/assets"; @@ -207,812 +207,798 @@ return Assets; - }).call(this); + })(Backbone.Collection); - // Views API.View = {}; - API.View.AddAssetView = AddAssetView = (function() { - class AddAssetView extends Backbone.View { - constructor() { - super(...arguments); - this.$f = this.$f.bind(this); - this.$fv = this.$fv.bind(this); - this.initialize = this.initialize.bind(this); - this.viewmodel = this.viewmodel.bind(this); - this.save = this.save.bind(this); - this.toggleSkipAssetCheck = this.toggleSkipAssetCheck.bind(this); - this.change_mimetype = this.change_mimetype.bind(this); - this.clickTabNavUpload = this.clickTabNavUpload.bind(this); - this.clickTabNavUri = this.clickTabNavUri.bind(this); - this.updateUriMimetype = this.updateUriMimetype.bind(this); - this.updateFileUploadMimetype = this.updateFileUploadMimetype.bind(this); - this.updateMimetype = this.updateMimetype.bind(this); - this.change = this.change.bind(this); - this.validate = this.validate.bind(this); - this.cancel = this.cancel.bind(this); - this.destroyFileUploadWidget = this.destroyFileUploadWidget.bind(this); - } + API.View.AddAssetView = AddAssetView = (function(superClass) { + extend(AddAssetView, superClass); + + function AddAssetView() { + this.destroyFileUploadWidget = bind(this.destroyFileUploadWidget, this); + this.cancel = bind(this.cancel, this); + this.validate = bind(this.validate, this); + this.change = bind(this.change, this); + this.updateMimetype = bind(this.updateMimetype, this); + this.updateFileUploadMimetype = bind(this.updateFileUploadMimetype, this); + this.updateUriMimetype = bind(this.updateUriMimetype, this); + this.clickTabNavUri = bind(this.clickTabNavUri, this); + this.clickTabNavUpload = bind(this.clickTabNavUpload, this); + this.change_mimetype = bind(this.change_mimetype, this); + this.toggleSkipAssetCheck = bind(this.toggleSkipAssetCheck, this); + this.save = bind(this.save, this); + this.viewmodel = bind(this.viewmodel, this); + this.initialize = bind(this.initialize, this); + this.$fv = bind(this.$fv, this); + this.$f = bind(this.$f, this); + return AddAssetView.__super__.constructor.apply(this, arguments); + } - $f(field) { - boundMethodCheck(this, AddAssetView); - return this.$(`[name='${field}']`); - } + AddAssetView.prototype.$f = function(field) { + return this.$("[name='" + field + "']"); + }; - $fv(field, ...val) { - boundMethodCheck(this, AddAssetView); - return (this.$f(field)).val(...val); // get or set filed value - } + AddAssetView.prototype.$fv = function() { + var field, ref, val; + field = arguments[0], val = 2 <= arguments.length ? slice.call(arguments, 1) : []; + return (ref = this.$f(field)).val.apply(ref, val); + }; - initialize(oprions) { - var d, deadline, deadlines, tag; - boundMethodCheck(this, AddAssetView); - ($('body')).append(this.$el.html(get_template('asset-modal'))); - (this.$el.children(":first")).modal(); - (this.$('.cancel')).val('Back to Assets'); - deadlines = { - start: now(), - end: (moment().add('days', 30)).toDate() - }; - for (tag in deadlines) { - if (!hasProp.call(deadlines, tag)) continue; - deadline = deadlines[tag]; - d = date_to(deadline); - this.$fv(`${tag}_date_date`, d.date()); - this.$fv(`${tag}_date_time`, d.time()); - } - return false; + AddAssetView.prototype.initialize = function(oprions) { + var d, deadline, deadlines, tag; + ($('body')).append(this.$el.html(get_template('asset-modal'))); + (this.$el.children(":first")).modal(); + (this.$('.cancel')).val('Back to Assets'); + deadlines = { + start: now(), + end: (moment().add('days', 30)).toDate() + }; + for (tag in deadlines) { + if (!hasProp.call(deadlines, tag)) continue; + deadline = deadlines[tag]; + d = date_to(deadline); + this.$fv(tag + "_date_date", d.date()); + this.$fv(tag + "_date_time", d.time()); } + return false; + }; - viewmodel(model) { - var field, k, l, len, len1, ref, ref1, results, which; - boundMethodCheck(this, AddAssetView); - ref = ['start', 'end']; - for (k = 0, len = ref.length; k < len; k++) { - which = ref[k]; - this.$fv(`${which}_date`, (moment((this.$fv(`${which}_date_date`)) + " " + (this.$fv(`${which}_date_time`)), dateSettings.fullDate)).toDate().toISOString()); - } - ref1 = model.fields; - results = []; - for (l = 0, len1 = ref1.length; l < len1; l++) { - field = ref1[l]; - if (!(this.$f(field)).prop('disabled')) { - results.push(model.set(field, this.$fv(field), { - silent: true - })); - } + AddAssetView.prototype.viewmodel = function(model) { + var field, k, l, len, len1, ref, ref1, results, which; + ref = ['start', 'end']; + for (k = 0, len = ref.length; k < len; k++) { + which = ref[k]; + this.$fv(which + "_date", (moment((this.$fv(which + "_date_date")) + " " + (this.$fv(which + "_date_time")), dateSettings.fullDate)).toDate().toISOString()); + } + ref1 = model.fields; + results = []; + for (l = 0, len1 = ref1.length; l < len1; l++) { + field = ref1[l]; + if (!(this.$f(field)).prop('disabled')) { + results.push(model.set(field, this.$fv(field), { + silent: true + })); } - return results; } + return results; + }; - save(e) { - var model, save; - boundMethodCheck(this, AddAssetView); - if ((this.$fv('uri')) === '') { - return false; - } - if ((this.$('#tab-uri')).hasClass('active')) { - model = new Asset({}, { - collection: API.assets - }); - this.$fv('mimetype', ''); - this.updateUriMimetype(); - this.viewmodel(model); - model.set({ - name: model.get('uri') - }, { - silent: true - }); - save = model.save(); - (this.$('input')).prop('disabled', true); - save.done((data) => { + AddAssetView.prototype.events = { + 'change': 'change', + 'click #save-asset': 'save', + 'click .cancel': 'cancel', + 'hidden.bs.modal': 'destroyFileUploadWidget', + 'click .tabnav-uri': 'clickTabNavUri', + 'click .tabnav-file_upload': 'clickTabNavUpload', + 'change .is_enabled-skip_asset_check_checkbox': 'toggleSkipAssetCheck' + }; + + AddAssetView.prototype.save = function(e) { + var model, save; + if ((this.$fv('uri')) === '') { + return false; + } + if ((this.$('#tab-uri')).hasClass('active')) { + model = new Asset({}, { + collection: API.assets + }); + this.$fv('mimetype', ''); + this.updateUriMimetype(); + this.viewmodel(model); + model.set({ + name: model.get('uri') + }, { + silent: true + }); + save = model.save(); + (this.$('input')).prop('disabled', true); + save.done((function(_this) { + return function(data) { model.id = data.asset_id; - (this.$el.children(":first")).modal('hide'); + (_this.$el.children(":first")).modal('hide'); _.extend(model.attributes, data); return model.collection.add(model); - }); - save.fail(() => { - (this.$('input')).prop('disabled', false); + }; + })(this)); + save.fail((function(_this) { + return function() { + (_this.$('input')).prop('disabled', false); return model.destroy(); - }); - } - return false; + }; + })(this)); } + return false; + }; - toggleSkipAssetCheck(e) { - boundMethodCheck(this, AddAssetView); - return this.$fv('skip_asset_check', parseInt(this.$fv('skip_asset_check')) === 1 ? 0 : 1); - } + AddAssetView.prototype.toggleSkipAssetCheck = function(e) { + return this.$fv('skip_asset_check', parseInt(this.$fv('skip_asset_check')) === 1 ? 0 : 1); + }; - change_mimetype() { - boundMethodCheck(this, AddAssetView); - if ((this.$fv('mimetype')) === "video") { - return this.$fv('duration', 0); - } else if ((this.$fv('mimetype')) === "streaming") { - return this.$fv('duration', defaultStreamingDuration); - } else { - return this.$fv('duration', defaultDuration); - } + AddAssetView.prototype.change_mimetype = function() { + if ((this.$fv('mimetype')) === "video") { + return this.$fv('duration', 0); + } else if ((this.$fv('mimetype')) === "streaming") { + return this.$fv('duration', defaultStreamingDuration); + } else { + return this.$fv('duration', defaultDuration); } + }; - clickTabNavUpload(e) { - var that; - boundMethodCheck(this, AddAssetView); - if (!(this.$('#tab-file_upload')).hasClass('active')) { - (this.$('ul.nav-tabs li')).removeClass('active show'); - (this.$('.tab-pane')).removeClass('active'); - (this.$('.tabnav-file_upload')).addClass('active show'); - (this.$('#tab-file_upload')).addClass('active'); - (this.$('.uri')).hide(); - (this.$('.skip_asset_check_checkbox')).hide(); - (this.$('#save-asset')).hide(); - that = this; - (this.$("[name='file_upload']")).fileupload({ - autoUpload: false, - sequentialUploads: true, - maxChunkSize: 5000000, //5 MB - url: 'api/v1/file_asset', - progressall: (e, data) => { + AddAssetView.prototype.clickTabNavUpload = function(e) { + var that; + if (!(this.$('#tab-file_upload')).hasClass('active')) { + (this.$('ul.nav-tabs li')).removeClass('active show'); + (this.$('.tab-pane')).removeClass('active'); + (this.$('.tabnav-file_upload')).addClass('active show'); + (this.$('#tab-file_upload')).addClass('active'); + (this.$('.uri')).hide(); + (this.$('.skip_asset_check_checkbox')).hide(); + (this.$('#save-asset')).hide(); + that = this; + (this.$("[name='file_upload']")).fileupload({ + autoUpload: false, + sequentialUploads: true, + maxChunkSize: 5000000, + url: 'api/v1/file_asset', + progressall: (function(_this) { + return function(e, data) { if (data.loaded && data.total) { - return (this.$('.progress .bar')).css('width', `${data.loaded / data.total * 100}%`); + return (_this.$('.progress .bar')).css('width', (data.loaded / data.total * 100) + "%"); } - }, - add: function(e, data) { - var filename, model; - (that.$('.status')).hide(); - (that.$('.progress')).show(); - model = new Asset({}, { - collection: API.assets + }; + })(this), + add: function(e, data) { + var filename, model; + (that.$('.status')).hide(); + (that.$('.progress')).show(); + model = new Asset({}, { + collection: API.assets + }); + filename = data['files'][0]['name']; + that.$fv('name', filename); + that.updateFileUploadMimetype(filename); + that.viewmodel(model); + return data.submit().success(function(uri) { + var save; + model.set({ + uri: uri + }, { + silent: true + }); + save = model.save(); + save.done(function(data) { + model.id = data.asset_id; + _.extend(model.attributes, data); + return model.collection.add(model); }); - filename = data['files'][0]['name']; - that.$fv('name', filename); - that.updateFileUploadMimetype(filename); - that.viewmodel(model); - return data.submit().success(function(uri) { - var save; - model.set({ - uri: uri - }, { - silent: true - }); - save = model.save(); - save.done(function(data) { - model.id = data.asset_id; - _.extend(model.attributes, data); - return model.collection.add(model); - }); - return save.fail(function() { - return model.destroy(); - }); - }).error(function() { + return save.fail(function() { return model.destroy(); }); - }, - stop: function(e) { - (that.$('.progress')).hide(); - return (that.$('.progress .bar')).css('width', "0"); - }, - done: function(e, data) { - (that.$('.status')).show(); - (that.$('.status')).html('Upload completed.'); - return setTimeout(function() { - return (that.$('.status')).fadeOut('slow'); - }, 5000); - } - }); - } - return false; + }).error(function() { + return model.destroy(); + }); + }, + stop: function(e) { + (that.$('.progress')).hide(); + return (that.$('.progress .bar')).css('width', "0"); + }, + done: function(e, data) { + (that.$('.status')).show(); + (that.$('.status')).html('Upload completed.'); + return setTimeout(function() { + return (that.$('.status')).fadeOut('slow'); + }, 5000); + } + }); } + return false; + }; - clickTabNavUri(e) { // TODO: clean - boundMethodCheck(this, AddAssetView); - if (!(this.$('#tab-uri')).hasClass('active')) { - (this.$("[name='file_upload']")).fileupload('destroy'); - (this.$('ul.nav-tabs li')).removeClass('active show'); - (this.$('.tab-pane')).removeClass('active'); - (this.$('.tabnav-uri')).addClass('active show'); - (this.$('#tab-uri')).addClass('active'); - (this.$('#save-asset')).show(); - (this.$('.uri')).show(); - (this.$('.skip_asset_check_checkbox')).show(); - (this.$('.status')).hide(); - return (this.$f('uri')).focus(); - } + AddAssetView.prototype.clickTabNavUri = function(e) { + if (!(this.$('#tab-uri')).hasClass('active')) { + (this.$("[name='file_upload']")).fileupload('destroy'); + (this.$('ul.nav-tabs li')).removeClass('active show'); + (this.$('.tab-pane')).removeClass('active'); + (this.$('.tabnav-uri')).addClass('active show'); + (this.$('#tab-uri')).addClass('active'); + (this.$('#save-asset')).show(); + (this.$('.uri')).show(); + (this.$('.skip_asset_check_checkbox')).show(); + (this.$('.status')).hide(); + return (this.$f('uri')).focus(); } + }; - updateUriMimetype() { - boundMethodCheck(this, AddAssetView); - return this.updateMimetype(this.$fv('uri')); - } + AddAssetView.prototype.updateUriMimetype = function() { + return this.updateMimetype(this.$fv('uri')); + }; - updateFileUploadMimetype(filename) { - boundMethodCheck(this, AddAssetView); - return this.updateMimetype(filename); - } + AddAssetView.prototype.updateFileUploadMimetype = function(filename) { + return this.updateMimetype(filename); + }; - updateMimetype(filename) { - var mt; - boundMethodCheck(this, AddAssetView); - mt = getMimetype(filename); - this.$fv('mimetype', mt ? mt : new Asset().defaults()['mimetype']); - return this.change_mimetype(); - } + AddAssetView.prototype.updateMimetype = function(filename) { + var mt; + mt = getMimetype(filename); + this.$fv('mimetype', mt ? mt : new Asset().defaults()['mimetype']); + return this.change_mimetype(); + }; - change(e) { - boundMethodCheck(this, AddAssetView); - this._change || (this._change = _.throttle((() => { - this.validate(); + AddAssetView.prototype.change = function(e) { + this._change || (this._change = _.throttle(((function(_this) { + return function() { + _this.validate(); return true; - }), 500)); - return this._change(...arguments); - } - - validate(e) { - var errors, field, fn, k, len, results, that, v, validators; - boundMethodCheck(this, AddAssetView); - that = this; - validators = { - uri: function(v) { - if (v) { - if (((that.$('#tab-uri')).hasClass('active')) && !url_test(v)) { - return 'please enter a valid URL'; - } - } - } }; - errors = (function() { - var results; - results = []; - for (field in validators) { - fn = validators[field]; - if (v = fn(this.$fv(field))) { - results.push([field, v]); + })(this)), 500)); + return this._change.apply(this, arguments); + }; + + AddAssetView.prototype.validate = function(e) { + var errors, field, fn, k, len, ref, results, that, v, validators; + that = this; + validators = { + uri: function(v) { + if (v) { + if (((that.$('#tab-uri')).hasClass('active')) && !url_test(v)) { + return 'please enter a valid URL'; } } - return results; - }).call(this); - (this.$(".form-group .help-inline.invalid-feedback")).remove(); - (this.$(".form-group .form-control")).removeClass('is-invalid'); - (this.$('[type=submit]')).prop('disabled', false); + } + }; + errors = (function() { + var results; results = []; - for (k = 0, len = errors.length; k < len; k++) { - [field, v] = errors[k]; - (this.$('[type=submit]')).prop('disabled', true); - (this.$(`.form-group.${field} .form-control`)).addClass('is-invalid'); - results.push((this.$(`.form-group.${field} .controls`)).append($(`${v}`))); + for (field in validators) { + fn = validators[field]; + if (v = fn(this.$fv(field))) { + results.push([field, v]); + } } return results; + }).call(this); + (this.$(".form-group .help-inline.invalid-feedback")).remove(); + (this.$(".form-group .form-control")).removeClass('is-invalid'); + (this.$('[type=submit]')).prop('disabled', false); + results = []; + for (k = 0, len = errors.length; k < len; k++) { + ref = errors[k], field = ref[0], v = ref[1]; + (this.$('[type=submit]')).prop('disabled', true); + (this.$(".form-group." + field + " .form-control")).addClass('is-invalid'); + results.push((this.$(".form-group." + field + " .controls")).append($("" + v + ""))); } + return results; + }; - cancel(e) { - boundMethodCheck(this, AddAssetView); - return (this.$el.children(":first")).modal('hide'); - } + AddAssetView.prototype.cancel = function(e) { + return (this.$el.children(":first")).modal('hide'); + }; - destroyFileUploadWidget(e) { - boundMethodCheck(this, AddAssetView); - if ((this.$('#tab-file_upload')).hasClass('active')) { - return (this.$("[name='file_upload']")).fileupload('destroy'); - } + AddAssetView.prototype.destroyFileUploadWidget = function(e) { + if ((this.$('#tab-file_upload')).hasClass('active')) { + return (this.$("[name='file_upload']")).fileupload('destroy'); } + }; + return AddAssetView; + + })(Backbone.View); + + API.View.EditAssetView = EditAssetView = (function(superClass) { + extend(EditAssetView, superClass); + + function EditAssetView() { + this.setDisabledDatepicker = bind(this.setDisabledDatepicker, this); + this.setLoopDateTime = bind(this.setLoopDateTime, this); + this.displayAdvanced = bind(this.displayAdvanced, this); + this.toggleAdvanced = bind(this.toggleAdvanced, this); + this.cancel = bind(this.cancel, this); + this.validate = bind(this.validate, this); + this.change = bind(this.change, this); + this.save = bind(this.save, this); + this.changeLoopTimes = bind(this.changeLoopTimes, this); + this.viewmodel = bind(this.viewmodel, this); + this.render = bind(this.render, this); + this.initialize = bind(this.initialize, this); + this.$fv = bind(this.$fv, this); + this.$f = bind(this.$f, this); + return EditAssetView.__super__.constructor.apply(this, arguments); + } + + EditAssetView.prototype.$f = function(field) { + return this.$("[name='" + field + "']"); }; - AddAssetView.prototype.events = { - 'change': 'change', - 'click #save-asset': 'save', - 'click .cancel': 'cancel', - 'hidden.bs.modal': 'destroyFileUploadWidget', - 'click .tabnav-uri': 'clickTabNavUri', - 'click .tabnav-file_upload': 'clickTabNavUpload', - 'change .is_enabled-skip_asset_check_checkbox': 'toggleSkipAssetCheck' + EditAssetView.prototype.$fv = function() { + var field, ref, val; + field = arguments[0], val = 2 <= arguments.length ? slice.call(arguments, 1) : []; + return (ref = this.$f(field)).val.apply(ref, val); }; - return AddAssetView; + EditAssetView.prototype.initialize = function(options) { + ($('body')).append(this.$el.html(get_template('asset-modal'))); + (this.$('input.time')).timepicker({ + minuteStep: 5, + showInputs: true, + disableFocus: true, + showMeridian: dateSettings.showMeridian + }); + (this.$('input[name="nocache"]')).prop('checked', this.model.get('nocache')); + (this.$('.modal-header .close')).remove(); + (this.$el.children(":first")).modal(); + this.model.backup(); + this.model.bind('change', this.render); + this.render(); + this.validate(); + return false; + }; - }).call(this); - - API.View.EditAssetView = EditAssetView = (function() { - class EditAssetView extends Backbone.View { - constructor() { - super(...arguments); - this.$f = this.$f.bind(this); - this.$fv = this.$fv.bind(this); - this.initialize = this.initialize.bind(this); - this.render = this.render.bind(this); - this.viewmodel = this.viewmodel.bind(this); - this.changeLoopTimes = this.changeLoopTimes.bind(this); - this.save = this.save.bind(this); - this.change = this.change.bind(this); - this.validate = this.validate.bind(this); - this.cancel = this.cancel.bind(this); - this.toggleAdvanced = this.toggleAdvanced.bind(this); - this.displayAdvanced = this.displayAdvanced.bind(this); - this.setLoopDateTime = this.setLoopDateTime.bind(this); - this.setDisabledDatepicker = this.setDisabledDatepicker.bind(this); + EditAssetView.prototype.render = function() { + var d, f, field, k, l, len, len1, len2, m, ref, ref1, ref2, which; + this.undelegateEvents(); + ref = 'mimetype uri file_upload'.split(' '); + for (k = 0, len = ref.length; k < len; k++) { + f = ref[k]; + (this.$(f)).attr('disabled', true); } - - $f(field) { - boundMethodCheck(this, EditAssetView); - return this.$(`[name='${field}']`); + (this.$('#modalLabel')).text("Edit Asset"); + (this.$('.asset-location')).hide(); + (this.$('.uri')).hide(); + (this.$('.skip_asset_check_checkbox')).hide(); + (this.$('.asset-location.edit')).show(); + (this.$('.mime-select')).prop('disabled', 'true'); + if ((this.model.get('mimetype')) === 'video') { + (this.$f('duration')).prop('disabled', true); } - - $fv(field, ...val) { - boundMethodCheck(this, EditAssetView); - return (this.$f(field)).val(...val); // get or set filed value + ref1 = this.model.fields; + for (l = 0, len1 = ref1.length; l < len1; l++) { + field = ref1[l]; + if ((this.$fv(field)) !== this.model.get(field)) { + this.$fv(field, this.model.get(field)); + } } - - initialize(options) { - boundMethodCheck(this, EditAssetView); - ($('body')).append(this.$el.html(get_template('asset-modal'))); - (this.$('input.time')).timepicker({ - minuteStep: 5, - showInputs: true, - disableFocus: true, - showMeridian: dateSettings.showMeridian + (this.$('.uri-text')).html(insertWbr(truncate_str(this.model.get('uri')))); + ref2 = ['start', 'end']; + for (m = 0, len2 = ref2.length; m < len2; m++) { + which = ref2[m]; + d = date_to(this.model.get(which + "_date")); + this.$fv(which + "_date_date", d.date()); + (this.$f(which + "_date_date")).datepicker({ + autoclose: true, + format: dateSettings.datepickerFormat }); - (this.$('input[name="nocache"]')).prop('checked', this.model.get('nocache')); - (this.$('.modal-header .close')).remove(); - (this.$el.children(":first")).modal(); - this.model.backup(); - this.model.bind('change', this.render); - this.render(); - this.validate(); - return false; + (this.$f(which + "_date_date")).datepicker('setValue', d.date()); + this.$fv(which + "_date_time", d.time()); } + this.displayAdvanced(); + this.delegateEvents(); + return false; + }; - render() { - var d, f, field, k, l, len, len1, len2, m, ref, ref1, ref2, which; - boundMethodCheck(this, EditAssetView); - this.undelegateEvents(); - ref = 'mimetype uri file_upload'.split(' '); - for (k = 0, len = ref.length; k < len; k++) { - f = ref[k]; - (this.$(f)).attr('disabled', true); - } - (this.$('#modalLabel')).text("Edit Asset"); - (this.$('.asset-location')).hide(); - (this.$('.uri')).hide(); - (this.$('.skip_asset_check_checkbox')).hide(); - (this.$('.asset-location.edit')).show(); - (this.$('.mime-select')).prop('disabled', 'true'); - if ((this.model.get('mimetype')) === 'video') { - (this.$f('duration')).prop('disabled', true); - } - ref1 = this.model.fields; - for (l = 0, len1 = ref1.length; l < len1; l++) { - field = ref1[l]; - if ((this.$fv(field)) !== this.model.get(field)) { - this.$fv(field, this.model.get(field)); - } - } - (this.$('.uri-text')).html(insertWbr(truncate_str(this.model.get('uri')))); - ref2 = ['start', 'end']; - for (m = 0, len2 = ref2.length; m < len2; m++) { - which = ref2[m]; - d = date_to(this.model.get(`${which}_date`)); - this.$fv(`${which}_date_date`, d.date()); - (this.$f(`${which}_date_date`)).datepicker({ - autoclose: true, - format: dateSettings.datepickerFormat - }); - (this.$f(`${which}_date_date`)).datepicker('setValue', d.date()); - this.$fv(`${which}_date_time`, d.time()); - } - this.displayAdvanced(); - this.delegateEvents(); - return false; + EditAssetView.prototype.viewmodel = function() { + var field, k, l, len, len1, ref, ref1, results, which; + ref = ['start', 'end']; + for (k = 0, len = ref.length; k < len; k++) { + which = ref[k]; + this.$fv(which + "_date", (moment((this.$fv(which + "_date_date")) + " " + (this.$fv(which + "_date_time")), dateSettings.fullDate)).toDate().toISOString()); } - - viewmodel() { - var field, k, l, len, len1, ref, ref1, results, which; - boundMethodCheck(this, EditAssetView); - ref = ['start', 'end']; - for (k = 0, len = ref.length; k < len; k++) { - which = ref[k]; - this.$fv(`${which}_date`, (moment((this.$fv(`${which}_date_date`)) + " " + (this.$fv(`${which}_date_time`)), dateSettings.fullDate)).toDate().toISOString()); - } - ref1 = this.model.fields; - results = []; - for (l = 0, len1 = ref1.length; l < len1; l++) { - field = ref1[l]; - if (!(this.$f(field)).prop('disabled')) { - results.push(this.model.set(field, this.$fv(field), { - silent: true - })); - } + ref1 = this.model.fields; + results = []; + for (l = 0, len1 = ref1.length; l < len1; l++) { + field = ref1[l]; + if (!(this.$f(field)).prop('disabled')) { + results.push(this.model.set(field, this.$fv(field), { + silent: true + })); } - return results; } + return results; + }; - changeLoopTimes() { - var current_date, end_date; - boundMethodCheck(this, EditAssetView); - current_date = new Date(); - end_date = new Date(); - switch (this.$('#loop_times').val()) { - case "day": - this.setLoopDateTime(date_to(current_date), date_to(end_date.setDate(current_date.getDate() + 1))); - break; - case "week": - this.setLoopDateTime(date_to(current_date), date_to(end_date.setDate(current_date.getDate() + 7))); - break; - case "month": - this.setLoopDateTime(date_to(current_date), date_to(end_date.setMonth(current_date.getMonth() + 1))); - break; - case "year": - this.setLoopDateTime(date_to(current_date), date_to(end_date.setFullYear(current_date.getFullYear() + 1))); - break; - case "forever": - this.setLoopDateTime(date_to(current_date), date_to(end_date.setFullYear(9999))); - break; - case "manual": - this.setDisabledDatepicker(false); - (this.$("#manul_date")).show(); - return; - default: - return; - } - this.setDisabledDatepicker(true); - return (this.$("#manul_date")).hide(); + EditAssetView.prototype.events = { + 'click #save-asset': 'save', + 'click .cancel': 'cancel', + 'change': 'change', + 'keyup': 'change', + 'click .advanced-toggle': 'toggleAdvanced' + }; + + EditAssetView.prototype.changeLoopTimes = function() { + var current_date, end_date; + current_date = new Date(); + end_date = new Date(); + switch (this.$('#loop_times').val()) { + case "day": + this.setLoopDateTime(date_to(current_date), date_to(end_date.setDate(current_date.getDate() + 1))); + break; + case "week": + this.setLoopDateTime(date_to(current_date), date_to(end_date.setDate(current_date.getDate() + 7))); + break; + case "month": + this.setLoopDateTime(date_to(current_date), date_to(end_date.setMonth(current_date.getMonth() + 1))); + break; + case "year": + this.setLoopDateTime(date_to(current_date), date_to(end_date.setFullYear(current_date.getFullYear() + 1))); + break; + case "forever": + this.setLoopDateTime(date_to(current_date), date_to(end_date.setFullYear(9999))); + break; + case "manual": + this.setDisabledDatepicker(false); + (this.$("#manul_date")).show(); + return; + default: + return; } + this.setDisabledDatepicker(true); + return (this.$("#manul_date")).hide(); + }; - save(e) { - var save; - boundMethodCheck(this, EditAssetView); - this.viewmodel(); - save = null; - this.model.set('nocache', (this.$('input[name="nocache"]')).prop('checked') ? 1 : 0); - if (!this.model.get('name')) { - if (this.model.old_name()) { - this.model.set({ - name: this.model.old_name() - }, { - silent: true - }); - } else if (getMimetype(this.model.get('uri'))) { - this.model.set({ - name: get_filename(this.model.get('uri')) - }, { - silent: true - }); - } else { - this.model.set({ - name: this.model.get('uri') - }, { - silent: true - }); - } + EditAssetView.prototype.save = function(e) { + var save; + this.viewmodel(); + save = null; + this.model.set('nocache', (this.$('input[name="nocache"]')).prop('checked') ? 1 : 0); + if (!this.model.get('name')) { + if (this.model.old_name()) { + this.model.set({ + name: this.model.old_name() + }, { + silent: true + }); + } else if (getMimetype(this.model.get('uri'))) { + this.model.set({ + name: get_filename(this.model.get('uri')) + }, { + silent: true + }); + } else { + this.model.set({ + name: this.model.get('uri') + }, { + silent: true + }); } - save = this.model.save(); - (this.$('input, select')).prop('disabled', true); - save.done((data) => { - this.model.id = data.asset_id; - if (!this.model.collection) { - this.collection.add(this.model); - } - (this.$el.children(":first")).modal('hide'); - return _.extend(this.model.attributes, data); - }); - save.fail(() => { - (this.$('.progress')).hide(); - return (this.$('input, select')).prop('disabled', false); - }); - return false; } + save = this.model.save(); + (this.$('input, select')).prop('disabled', true); + save.done((function(_this) { + return function(data) { + _this.model.id = data.asset_id; + if (!_this.model.collection) { + _this.collection.add(_this.model); + } + (_this.$el.children(":first")).modal('hide'); + return _.extend(_this.model.attributes, data); + }; + })(this)); + save.fail((function(_this) { + return function() { + (_this.$('.progress')).hide(); + return (_this.$('input, select')).prop('disabled', false); + }; + })(this)); + return false; + }; - change(e) { - boundMethodCheck(this, EditAssetView); - this._change || (this._change = _.throttle((() => { - this.changeLoopTimes(); - this.viewmodel(); - this.model.trigger('change'); - this.validate(e); + EditAssetView.prototype.change = function(e) { + this._change || (this._change = _.throttle(((function(_this) { + return function() { + _this.changeLoopTimes(); + _this.viewmodel(); + _this.model.trigger('change'); + _this.validate(e); return true; - }), 500)); - return this._change(...arguments); - } + }; + })(this)), 500)); + return this._change.apply(this, arguments); + }; - validate(e) { - var errors, field, fn, k, len, results, that, v, validators; - boundMethodCheck(this, EditAssetView); - that = this; - validators = { - duration: (v) => { - if (('video' !== this.model.get('mimetype')) && (!(_.isNumber(v * 1)) || v * 1 < 1)) { + EditAssetView.prototype.validate = function(e) { + var errors, field, fn, k, len, ref, results, that, v, validators; + that = this; + validators = { + duration: (function(_this) { + return function(v) { + if (('video' !== _this.model.get('mimetype')) && (!(_.isNumber(v * 1)) || v * 1 < 1)) { return 'Please enter a valid number.'; } - }, - end_date: (v) => { + }; + })(this), + end_date: (function(_this) { + return function(v) { var end_date, ref, start_date; - if (!((new Date(this.$fv('start_date'))) < (new Date(this.$fv('end_date'))))) { + if (!((new Date(_this.$fv('start_date'))) < (new Date(_this.$fv('end_date'))))) { if (((ref = $(e != null ? e.target : void 0)) != null ? ref.attr("name") : void 0) === "start_date_date") { - start_date = new Date(this.$fv('start_date')); - end_date = new Date(start_date.getTime() + Math.max(parseInt(this.$fv('duration')), 60) * 1000); - this.setLoopDateTime(date_to(start_date), date_to(end_date)); + start_date = new Date(_this.$fv('start_date')); + end_date = new Date(start_date.getTime() + Math.max(parseInt(_this.$fv('duration')), 60) * 1000); + _this.setLoopDateTime(date_to(start_date), date_to(end_date)); return; } return 'End date should be after start date.'; } - } - }; - errors = (function() { - var results; - results = []; - for (field in validators) { - fn = validators[field]; - if (v = fn(this.$fv(field))) { - results.push([field, v]); - } - } - return results; - }).call(this); - (this.$(".form-group .help-inline.invalid-feedback")).remove(); - (this.$(".form-group .form-control")).removeClass('is-invalid'); - (this.$('[type=submit]')).prop('disabled', false); + }; + })(this) + }; + errors = (function() { + var results; results = []; - for (k = 0, len = errors.length; k < len; k++) { - [field, v] = errors[k]; - (this.$('[type=submit]')).prop('disabled', true); - (this.$(`.form-group.${field} .form-control`)).addClass('is-invalid'); - results.push((this.$(`.form-group.${field} .controls`)).append($(`${v}`))); + for (field in validators) { + fn = validators[field]; + if (v = fn(this.$fv(field))) { + results.push([field, v]); + } } return results; + }).call(this); + (this.$(".form-group .help-inline.invalid-feedback")).remove(); + (this.$(".form-group .form-control")).removeClass('is-invalid'); + (this.$('[type=submit]')).prop('disabled', false); + results = []; + for (k = 0, len = errors.length; k < len; k++) { + ref = errors[k], field = ref[0], v = ref[1]; + (this.$('[type=submit]')).prop('disabled', true); + (this.$(".form-group." + field + " .form-control")).addClass('is-invalid'); + results.push((this.$(".form-group." + field + " .controls")).append($("" + v + ""))); } + return results; + }; - cancel(e) { - boundMethodCheck(this, EditAssetView); - this.model.rollback(); - return (this.$el.children(":first")).modal('hide'); - } - - toggleAdvanced() { - boundMethodCheck(this, EditAssetView); - (this.$('.fa-play')).toggleClass('rotated'); - (this.$('.fa-play')).toggleClass('unrotated'); - return (this.$('.collapse-advanced')).collapse('toggle'); - } - - displayAdvanced() { - var edit, has_nocache, img; - boundMethodCheck(this, EditAssetView); - img = 'image' === this.$fv('mimetype'); - edit = url_test(this.model.get('uri')); - has_nocache = img && edit; - return (this.$('.advanced-accordion')).toggle(has_nocache === true); - } + EditAssetView.prototype.cancel = function(e) { + this.model.rollback(); + return (this.$el.children(":first")).modal('hide'); + }; - setLoopDateTime(start_date, end_date) { - boundMethodCheck(this, EditAssetView); - this.$fv("start_date_date", start_date.date()); - (this.$f("start_date_date")).datepicker({ - autoclose: true, - format: dateSettings.datepickerFormat - }); - (this.$f("start_date_date")).datepicker('setDate', moment(start_date.date(), dateSettings.date).toDate()); - this.$fv("start_date_time", start_date.time()); - this.$fv("end_date_date", end_date.date()); - (this.$f("end_date_date")).datepicker({ - autoclose: true, - format: dateSettings.datepickerFormat - }); - (this.$f("end_date_date")).datepicker('setDate', moment(end_date.date(), dateSettings.date).toDate()); - this.$fv("end_date_time", end_date.time()); - (this.$(".form-group .help-inline.invalid-feedback")).remove(); - (this.$(".form-group .form-control")).removeClass('is-invalid'); - return (this.$('[type=submit]')).prop('disabled', false); - } + EditAssetView.prototype.toggleAdvanced = function() { + (this.$('.fa-play')).toggleClass('rotated'); + (this.$('.fa-play')).toggleClass('unrotated'); + return (this.$('.collapse-advanced')).collapse('toggle'); + }; - setDisabledDatepicker(b) { - var k, len, ref, results, which; - boundMethodCheck(this, EditAssetView); - ref = ['start', 'end']; - results = []; - for (k = 0, len = ref.length; k < len; k++) { - which = ref[k]; - (this.$f(`${which}_date_date`)).attr('disabled', b); - results.push((this.$f(`${which}_date_time`)).attr('disabled', b)); - } - return results; - } + EditAssetView.prototype.displayAdvanced = function() { + var edit, has_nocache, img; + img = 'image' === this.$fv('mimetype'); + edit = url_test(this.model.get('uri')); + has_nocache = img && edit; + return (this.$('.advanced-accordion')).toggle(has_nocache === true); + }; + EditAssetView.prototype.setLoopDateTime = function(start_date, end_date) { + this.$fv("start_date_date", start_date.date()); + (this.$f("start_date_date")).datepicker({ + autoclose: true, + format: dateSettings.datepickerFormat + }); + (this.$f("start_date_date")).datepicker('setDate', moment(start_date.date(), dateSettings.date).toDate()); + this.$fv("start_date_time", start_date.time()); + this.$fv("end_date_date", end_date.date()); + (this.$f("end_date_date")).datepicker({ + autoclose: true, + format: dateSettings.datepickerFormat + }); + (this.$f("end_date_date")).datepicker('setDate', moment(end_date.date(), dateSettings.date).toDate()); + this.$fv("end_date_time", end_date.time()); + (this.$(".form-group .help-inline.invalid-feedback")).remove(); + (this.$(".form-group .form-control")).removeClass('is-invalid'); + return (this.$('[type=submit]')).prop('disabled', false); }; - EditAssetView.prototype.events = { - 'click #save-asset': 'save', - 'click .cancel': 'cancel', - 'change': 'change', - 'keyup': 'change', - 'click .advanced-toggle': 'toggleAdvanced' + EditAssetView.prototype.setDisabledDatepicker = function(b) { + var k, len, ref, results, which; + ref = ['start', 'end']; + results = []; + for (k = 0, len = ref.length; k < len; k++) { + which = ref[k]; + (this.$f(which + "_date_date")).attr('disabled', b); + results.push((this.$f(which + "_date_time")).attr('disabled', b)); + } + return results; }; return EditAssetView; - }).call(this); - - API.View.AssetRowView = AssetRowView = (function() { - class AssetRowView extends Backbone.View { - constructor() { - super(...arguments); - this.initialize = this.initialize.bind(this); - this.render = this.render.bind(this); - this.toggleIsEnabled = this.toggleIsEnabled.bind(this); - this.setEnabled = this.setEnabled.bind(this); - this.download = this.download.bind(this); - this.edit = this.edit.bind(this); - this.delete = this.delete.bind(this); - this.showPopover = this.showPopover.bind(this); - this.hidePopover = this.hidePopover.bind(this); - } - - initialize(options) { - boundMethodCheck(this, AssetRowView); - return this.template = get_template('asset-row'); - } + })(Backbone.View); + + API.View.AssetRowView = AssetRowView = (function(superClass) { + extend(AssetRowView, superClass); + + function AssetRowView() { + this.hidePopover = bind(this.hidePopover, this); + this.showPopover = bind(this.showPopover, this); + this["delete"] = bind(this["delete"], this); + this.edit = bind(this.edit, this); + this.download = bind(this.download, this); + this.setEnabled = bind(this.setEnabled, this); + this.toggleIsEnabled = bind(this.toggleIsEnabled, this); + this.render = bind(this.render, this); + this.initialize = bind(this.initialize, this); + return AssetRowView.__super__.constructor.apply(this, arguments); + } - render() { - var json; - boundMethodCheck(this, AssetRowView); - this.$el.html(this.template(_.extend(json = this.model.toJSON(), { - name: insertWbr(truncate_str(json.name)), // word break urls at slashes - duration: durationSecondsToHumanReadable(json.duration), - start_date: (date_to(json.start_date)).string(), - end_date: (date_to(json.end_date)).string() - }))); - this.$el.prop('id', this.model.get('asset_id')); - (this.$(".delete-asset-button")).popover({ - content: get_template('confirm-delete') - }); - (this.$(".toggle input")).prop("checked", this.model.get('is_enabled')); - (this.$(".asset-icon")).addClass((function() { - switch (this.model.get("mimetype")) { - case "video": - return "fas fa-video"; - case "streaming": - return "fas fa-video"; - case "image": - return "far fa-image"; - case "webpage": - return "fas fa-globe-americas"; - default: - return ""; - } - }).call(this)); - if ((this.model.get("is_processing")) === 1) { - (this.$('input, button')).prop('disabled', true); - (this.$(".asset-toggle")).html(get_template('processing-message')); - } - return this.el; - } + AssetRowView.prototype.tagName = "tr"; - toggleIsEnabled(e) { - var save, val; - boundMethodCheck(this, AssetRowView); - val = (1 + this.model.get('is_enabled')) % 2; - this.model.set({ - is_enabled: val - }); - this.setEnabled(false); - save = this.model.save(); - save.done(() => { - return this.setEnabled(true); - }); - save.fail(() => { - this.model.set(this.model.previousAttributes(), { - silent: true // revert changes - }); - this.setEnabled(true); - return this.render(); - }); - return true; - } + AssetRowView.prototype.initialize = function(options) { + return this.template = get_template('asset-row'); + }; - setEnabled(enabled) { - boundMethodCheck(this, AssetRowView); - if (enabled) { - this.$el.removeClass('warning'); - this.delegateEvents(); - return (this.$('input, button')).prop('disabled', false); - } else { - this.hidePopover(); - this.undelegateEvents(); - this.$el.addClass('warning'); - return (this.$('input, button')).prop('disabled', true); + AssetRowView.prototype.render = function() { + var json; + this.$el.html(this.template(_.extend(json = this.model.toJSON(), { + name: insertWbr(truncate_str(json.name)), + duration: durationSecondsToHumanReadable(json.duration), + start_date: (date_to(json.start_date)).string(), + end_date: (date_to(json.end_date)).string() + }))); + this.$el.prop('id', this.model.get('asset_id')); + (this.$(".delete-asset-button")).popover({ + content: get_template('confirm-delete') + }); + (this.$(".toggle input")).prop("checked", this.model.get('is_enabled')); + (this.$(".asset-icon")).addClass((function() { + switch (this.model.get("mimetype")) { + case "video": + return "fas fa-video"; + case "streaming": + return "fas fa-video"; + case "image": + return "far fa-image"; + case "webpage": + return "fas fa-globe-americas"; + default: + return ""; } + }).call(this)); + if ((this.model.get("is_processing")) === 1) { + (this.$('input, button')).prop('disabled', true); + (this.$(".asset-toggle")).html(get_template('processing-message')); } + return this.el; + }; - download(e) { - var r; - boundMethodCheck(this, AssetRowView); - r = $.get('/api/v1/assets/' + this.model.id + '/content').success(function(result) { - var a, blob, content, fn, mimetype, url; - switch (result['type']) { - case 'url': - return window.open(result['url']); - case 'file': - content = base64js.toByteArray(result['content']); - mimetype = result['mimetype']; - fn = result['filename']; - blob = new Blob([content], { - type: mimetype - }); - url = URL.createObjectURL(blob); - a = document.createElement('a'); - document.body.appendChild(a); - a.download = fn; - a.href = url; - a.click(); - URL.revokeObjectURL(url); - return a.remove(); - } - }); - return false; - } + AssetRowView.prototype.events = { + 'change .is_enabled-toggle input': 'toggleIsEnabled', + 'click .download-asset-button': 'download', + 'click .edit-asset-button': 'edit', + 'click .delete-asset-button': 'showPopover' + }; - edit(e) { - boundMethodCheck(this, AssetRowView); - new EditAssetView({ - model: this.model - }); - return false; - } + AssetRowView.prototype.toggleIsEnabled = function(e) { + var save, val; + val = (1 + this.model.get('is_enabled')) % 2; + this.model.set({ + is_enabled: val + }); + this.setEnabled(false); + save = this.model.save(); + save.done((function(_this) { + return function() { + return _this.setEnabled(true); + }; + })(this)); + save.fail((function(_this) { + return function() { + _this.model.set(_this.model.previousAttributes(), { + silent: true + }); + _this.setEnabled(true); + return _this.render(); + }; + })(this)); + return true; + }; - delete(e) { - var xhr; - boundMethodCheck(this, AssetRowView); + AssetRowView.prototype.setEnabled = function(enabled) { + if (enabled) { + this.$el.removeClass('warning'); + this.delegateEvents(); + return (this.$('input, button')).prop('disabled', false); + } else { this.hidePopover(); - if ((xhr = this.model.destroy()) === !false) { - xhr.done(() => { - return this.remove(); - }); - } else { - this.remove(); - } - return false; + this.undelegateEvents(); + this.$el.addClass('warning'); + return (this.$('input, button')).prop('disabled', true); } + }; - showPopover() { - boundMethodCheck(this, AssetRowView); - if (!($('.popover')).length) { - (this.$(".delete-asset-button")).popover('show'); - ($('.confirm-delete')).click(this.delete); - ($(window)).one('click', this.hidePopover); + AssetRowView.prototype.download = function(e) { + var r; + r = $.get('/api/v1/assets/' + this.model.id + '/content').success(function(result) { + var a, blob, content, fn, mimetype, url; + switch (result['type']) { + case 'url': + return window.open(result['url']); + case 'file': + content = base64js.toByteArray(result['content']); + mimetype = result['mimetype']; + fn = result['filename']; + blob = new Blob([content], { + type: mimetype + }); + url = URL.createObjectURL(blob); + a = document.createElement('a'); + document.body.appendChild(a); + a.download = fn; + a.href = url; + a.click(); + URL.revokeObjectURL(url); + return a.remove(); } - return false; - } + }); + return false; + }; - hidePopover() { - boundMethodCheck(this, AssetRowView); - (this.$(".delete-asset-button")).popover('hide'); - return false; - } + AssetRowView.prototype.edit = function(e) { + new EditAssetView({ + model: this.model + }); + return false; + }; + AssetRowView.prototype["delete"] = function(e) { + var xhr; + this.hidePopover(); + if ((xhr = this.model.destroy()) === !false) { + xhr.done((function(_this) { + return function() { + return _this.remove(); + }; + })(this)); + } else { + this.remove(); + } + return false; }; - AssetRowView.prototype.tagName = "tr"; + AssetRowView.prototype.showPopover = function() { + if (!($('.popover')).length) { + (this.$(".delete-asset-button")).popover('show'); + ($('.confirm-delete')).click(this["delete"]); + ($(window)).one('click', this.hidePopover); + } + return false; + }; - AssetRowView.prototype.events = { - 'change .is_enabled-toggle input': 'toggleIsEnabled', - 'click .download-asset-button': 'download', - 'click .edit-asset-button': 'edit', - 'click .delete-asset-button': 'showPopover' + AssetRowView.prototype.hidePopover = function() { + (this.$(".delete-asset-button")).popover('hide'); + return false; }; return AssetRowView; - }).call(this); + })(Backbone.View); + + API.View.AssetsView = AssetsView = (function(superClass) { + extend(AssetsView, superClass); - API.View.AssetsView = AssetsView = class AssetsView extends Backbone.View { - constructor() { - super(...arguments); - this.initialize = this.initialize.bind(this); - this.update_order = this.update_order.bind(this); - this.render = this.render.bind(this); + function AssetsView() { + this.render = bind(this.render, this); + this.update_order = bind(this.update_order, this); + this.initialize = bind(this.initialize, this); + return AssetsView.__super__.constructor.apply(this, arguments); } - initialize(options) { + AssetsView.prototype.initialize = function(options) { var event, k, len, ref; - boundMethodCheck(this, AssetsView); ref = 'reset add remove sync'.split(' '); for (k = 0, len = ref.length; k < len; k++) { event = ref[k]; @@ -1024,11 +1010,10 @@ helper: 'clone', update: this.update_order }); - } + }; - update_order() { + AssetsView.prototype.update_order = function() { var active, el, i, id, k, l, len, len1, ref; - boundMethodCheck(this, AssetsView); active = (this.$('#active-assets')).sortable('toArray'); for (i = k = 0, len = active.length; k < len; i = ++k) { id = active[i]; @@ -1042,112 +1027,100 @@ return $.post('/api/v1/assets/order', { ids: ((this.$('#active-assets')).sortable('toArray')).join(',') }); - } + }; - render() { + AssetsView.prototype.render = function() { var k, l, len, len1, len2, m, ref, ref1, ref2, which; - boundMethodCheck(this, AssetsView); this.collection.sort(); ref = ['active', 'inactive']; for (k = 0, len = ref.length; k < len; k++) { which = ref[k]; - (this.$(`#${which}-assets`)).html(''); + (this.$("#" + which + "-assets")).html(''); } - this.collection.each((model) => { - which = model.active() ? 'active' : 'inactive'; - return (this.$(`#${which}-assets`)).append((new AssetRowView({ - model: model - })).render()); - }); + this.collection.each((function(_this) { + return function(model) { + which = model.active() ? 'active' : 'inactive'; + return (_this.$("#" + which + "-assets")).append((new AssetRowView({ + model: model + })).render()); + }; + })(this)); ref1 = ['active', 'inactive']; for (l = 0, len1 = ref1.length; l < len1; l++) { which = ref1[l]; - if ((this.$(`#${which}-assets tr`)).length === 0) { - (this.$(`#${which}-assets-section .table-assets-help-text`)).show(); + if ((this.$("#" + which + "-assets tr")).length === 0) { + (this.$("#" + which + "-assets-section .table-assets-help-text")).show(); } else { - (this.$(`#${which}-assets-section .table-assets-help-text`)).hide(); + (this.$("#" + which + "-assets-section .table-assets-help-text")).hide(); } } ref2 = ['inactive', 'active']; for (m = 0, len2 = ref2.length; m < len2; m++) { which = ref2[m]; - this.$(`.${which}-table thead`).toggle(!!(this.$(`#${which}-assets tr`).length)); + this.$("." + which + "-table thead").toggle(!!(this.$("#" + which + "-assets tr").length)); } this.update_order(); return this.el; - } + }; - }; + return AssetsView; - API.App = App = (function() { - class App extends Backbone.View { - constructor() { - super(...arguments); - this.initialize = this.initialize.bind(this); - } + })(Backbone.View); - initialize() { - var address, error, k, len, results, ws; - boundMethodCheck(this, App); - ($(window)).ajaxError(function(e, r) { - var err, j; - ($('#request-error')).html((get_template('request-error'))()); - if ((j = $.parseJSON(r.responseText)) && (err = j.error)) { - ($('#request-error .msg')).text('Server Error: ' + err); - } + API.App = App = (function(superClass) { + extend(App, superClass); + + function App() { + this.initialize = bind(this.initialize, this); + return App.__super__.constructor.apply(this, arguments); + } + + App.prototype.initialize = function() { + var address, error, k, len, results, ws; + ($(window)).ajaxError(function(e, r) { + var err, j; + ($('#request-error')).html((get_template('request-error'))()); + if ((j = $.parseJSON(r.responseText)) && (err = j.error)) { + ($('#request-error .msg')).text('Server Error: ' + err); + } + ($('#request-error')).show(); + return setTimeout(function() { + return ($('#request-error')).fadeOut('slow'); + }, 5000); + }); + ($(window)).ajaxSuccess(function(event, request, settings) { + if ((settings.url === new Assets().url) && (settings.type === 'POST')) { + ($('#request-error')).html((get_template('request-success'))()); + ($('#request-error .msg')).text('Asset has been successfully uploaded.'); ($('#request-error')).show(); return setTimeout(function() { return ($('#request-error')).fadeOut('slow'); }, 5000); - }); - ($(window)).ajaxSuccess(function(event, request, settings) { - if ((settings.url === new Assets().url) && (settings.type === 'POST')) { - ($('#request-error')).html((get_template('request-success'))()); - ($('#request-error .msg')).text('Asset has been successfully uploaded.'); - ($('#request-error')).show(); - return setTimeout(function() { - return ($('#request-error')).fadeOut('slow'); - }, 5000); - } - }); - (API.assets = new Assets()).fetch(); - API.assetsView = new AssetsView({ - collection: API.assets, - el: this.$('#assets') - }); - results = []; - for (k = 0, len = wsAddresses.length; k < len; k++) { - address = wsAddresses[k]; - try { - ws = new WebSocket(address); - results.push(ws.onmessage = function(x) { - var model, save; - model = API.assets.get(x.data); - if (model) { - return save = model.fetch(); - } - }); - } catch (error1) { - error = error1; - results.push(false); - } } - return results; - } - - add(e) { - new AddAssetView(); - return false; - } - - previous(e) { - return $.get('/api/v1/assets/control/previous'); - } - - next(e) { - return $.get('/api/v1/assets/control/next'); + }); + (API.assets = new Assets()).fetch(); + API.assetsView = new AssetsView({ + collection: API.assets, + el: this.$('#assets') + }); + results = []; + for (k = 0, len = wsAddresses.length; k < len; k++) { + address = wsAddresses[k]; + try { + ws = new WebSocket(address); + results.push(ws.onmessage = function(x) { + var model, save; + model = API.assets.get(x.data); + if (model) { + return save = model.fetch(); + } + }); + } catch (error1) { + error = error1; + results.push(false); + } } - + return results; }; App.prototype.events = { @@ -1156,9 +1129,22 @@ 'click #next-asset-button': 'next' }; + App.prototype.add = function(e) { + new AddAssetView; + return false; + }; + + App.prototype.previous = function(e) { + return $.get('/api/v1/assets/control/previous'); + }; + + App.prototype.next = function(e) { + return $.get('/api/v1/assets/control/next'); + }; + return App; - }).call(this); + })(Backbone.View); }).call(this); diff --git a/static/js/screenly-ose.js.map b/static/js/screenly-ose.js.map index 438dddd71..4662dc79a 100644 --- a/static/js/screenly-ose.js.map +++ b/static/js/screenly-ose.js.map @@ -6,8 +6,5 @@ "screenly-ose.coffee" ], "names": [], - "mappings": "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", - "sourcesContent": [ - "### screenly-ose ui ###\n\n$().ready ->\n $('#subsribe-form-container').popover content: get_template 'subscribe-form'\n\n\nAPI = (window.Screenly ||= {}) # exports\n\ndateSettings = {}\n\nif use24HourClock\n dateSettings.time = \"HH:mm\"\n dateSettings.fullTime = \"HH:mm:ss\"\n dateSettings.showMeridian = false\nelse\n dateSettings.time = \"hh:mm A\"\n dateSettings.fullTime = \"hh:mm:ss A\"\n dateSettings.showMeridian = true\n\ndateSettings.date = dateFormat.toUpperCase()\ndateSettings.datepickerFormat = dateFormat\n\ndateSettings.fullDate = \"#{dateSettings.date} #{dateSettings.fullTime}\"\n\n\nAPI.date_to = date_to = (d) ->\n # Cross-browser UTC to localtime conversion\n dd = moment.utc(d).local()\n string: -> dd.format dateSettings.fullDate\n date: -> dd.format dateSettings.date\n time: -> dd.format dateSettings.time\n\nnow = -> new Date()\n\nget_template = (name) -> _.template ($ \"##{name}-template\").html()\ndelay = (wait, fn) -> _.delay fn, wait\n\nmimetypes = [ [('jpg jpeg png pnm gif bmp'.split ' '), 'image']\n [('avi mkv mov mpg mpeg mp4 ts flv'.split ' '), 'video']]\nviduris = ('rtsp rtmp'.split ' ')\ndomains = [ [('www.youtube.com youtu.be'.split ' '), 'youtube_asset']]\n\n\ngetMimetype = (filename) ->\n scheme = (_.first filename.split ':').toLowerCase()\n match = scheme in viduris\n if match\n return 'streaming'\n\n domain = (_.first ((_.last filename.split '//').toLowerCase()).split '/')\n mt = _.find domains, (mt) -> domain in mt[0]\n if mt and domain in mt[0]\n return mt[1]\n\n ext = (_.last filename.split '.').toLowerCase()\n mt = _.find mimetypes, (mt) -> ext in mt[0]\n if mt\n return mt[1]\n\ndurationSecondsToHumanReadable = (secs) ->\n durationString = \"\"\n secInt = parseInt(secs)\n\n if ((hours = Math.floor(secInt / 3600)) > 0)\n durationString += hours + \" hours \"\n if ((minutes = Math.floor(secInt / 60) % 60) > 0)\n durationString += minutes + \" min \"\n if ((seconds = (secInt % 60)) > 0)\n durationString += seconds + \" sec\"\n\n return durationString\n\nurl_test = (v) -> /(http|https|rtsp|rtmp):\\/\\/[\\w-]+(\\.?[\\w-]+)+([\\w.,@?^=%&:\\/~+#-]*[\\w@?^=%&\\/~+#-])?/.test v\nget_filename = (v) -> (v.replace /[\\/\\\\\\s]+$/g, '').replace /^.*[\\\\\\/]/g, ''\ntruncate_str = (v) -> v.replace /(.{100})..+/, \"$1...\"\ninsertWbr = (v) -> (v.replace /\\//g, '/').replace /\\&/g, '&'\n\n# Tell Backbone to send its saves as JSON-encoded.\nBackbone.emulateJSON = off\n\n# Models\nAPI.Asset = class Asset extends Backbone.Model\n idAttribute: \"asset_id\"\n fields: 'name mimetype uri start_date end_date duration skip_asset_check'.split ' '\n defaults: ->\n name: ''\n mimetype: 'webpage'\n uri: ''\n is_active: 1\n start_date: ''\n end_date: ''\n duration: defaultDuration\n is_enabled: 0\n is_processing: 0\n nocache: 0\n play_order: 0\n skip_asset_check: 0\n active: =>\n if @get('is_enabled') and @get('start_date') and @get('end_date')\n at = now()\n start_date = new Date(@get('start_date'));\n end_date = new Date(@get('end_date'));\n return start_date <= at <= end_date\n else\n return false\n\n backup: =>\n @backup_attributes = @toJSON()\n\n rollback: =>\n if @backup_attributes\n @set @backup_attributes\n @backup_attributes = undefined\n old_name: =>\n if @backup_attributes\n return @backup_attributes.name\n\n\nAPI.Assets = class Assets extends Backbone.Collection\n url: \"/api/v1.2/assets\"\n model: Asset\n comparator: 'play_order'\n\n\n# Views\nAPI.View = {};\n\nAPI.View.AddAssetView = class AddAssetView extends Backbone.View\n $f: (field) => @$ \"[name='#{field}']\" # get field element\n $fv: (field, val...) => (@$f field).val val... # get or set filed value\n\n initialize: (oprions) =>\n ($ 'body').append @$el.html get_template 'asset-modal'\n (@$el.children \":first\").modal()\n (@$ '.cancel').val 'Back to Assets'\n\n deadlines = start: now(), end: (moment().add 'days', 30).toDate()\n for own tag, deadline of deadlines\n d = date_to deadline\n @.$fv \"#{tag}_date_date\", d.date()\n @.$fv \"#{tag}_date_time\", d.time()\n\n no\n\n viewmodel:(model) =>\n for which in ['start', 'end']\n @$fv \"#{which}_date\", (moment (@$fv \"#{which}_date_date\") + \" \" + (@$fv \"#{which}_date_time\"), dateSettings.fullDate).toDate().toISOString()\n for field in model.fields when not (@$f field).prop 'disabled'\n model.set field, (@$fv field), silent:yes\n\n events:\n 'change': 'change'\n 'click #save-asset': 'save'\n 'click .cancel': 'cancel'\n 'hidden.bs.modal': 'destroyFileUploadWidget'\n 'click .tabnav-uri': 'clickTabNavUri'\n 'click .tabnav-file_upload': 'clickTabNavUpload'\n 'change .is_enabled-skip_asset_check_checkbox': 'toggleSkipAssetCheck'\n\n save: (e) =>\n if ((@$fv 'uri') == '')\n return no\n if (@$ '#tab-uri').hasClass 'active'\n model = new Asset {}, {collection: API.assets}\n @$fv 'mimetype', ''\n @updateUriMimetype()\n @viewmodel model\n model.set {name: model.get 'uri'}, silent:yes\n save = model.save()\n\n (@$ 'input').prop 'disabled', on\n save.done (data) =>\n model.id = data.asset_id\n (@$el.children \":first\").modal 'hide'\n _.extend model.attributes, data\n model.collection.add model\n save.fail =>\n (@$ 'input').prop 'disabled', off\n model.destroy()\n no\n\n toggleSkipAssetCheck: (e) =>\n @$fv 'skip_asset_check', if parseInt((@$fv 'skip_asset_check')) == 1 then 0 else 1\n\n change_mimetype: =>\n if (@$fv 'mimetype') == \"video\"\n @$fv 'duration', 0\n else if (@$fv 'mimetype') == \"streaming\"\n @$fv 'duration', defaultStreamingDuration\n else\n @$fv 'duration', defaultDuration\n\n clickTabNavUpload: (e) =>\n if not (@$ '#tab-file_upload').hasClass 'active'\n (@$ 'ul.nav-tabs li').removeClass 'active show'\n (@$ '.tab-pane').removeClass 'active'\n (@$ '.tabnav-file_upload').addClass 'active show'\n (@$ '#tab-file_upload').addClass 'active'\n (@$ '.uri').hide()\n (@$ '.skip_asset_check_checkbox').hide()\n (@$ '#save-asset').hide()\n that = this\n (@$ \"[name='file_upload']\").fileupload\n autoUpload: false\n sequentialUploads: true\n maxChunkSize: 5000000 #5 MB\n url: 'api/v1/file_asset'\n progressall: (e, data) => if data.loaded and data.total\n (@$ '.progress .bar').css 'width', \"#{data.loaded / data.total * 100}%\"\n add: (e, data) ->\n (that.$ '.status').hide()\n (that.$ '.progress').show()\n\n model = new Asset {}, {collection: API.assets}\n filename = data['files'][0]['name']\n that.$fv 'name', filename\n that.updateFileUploadMimetype(filename)\n that.viewmodel model\n\n data.submit()\n .success (uri) ->\n model.set {uri: uri}, silent:yes\n\n save = model.save()\n save.done (data) ->\n model.id = data.asset_id\n _.extend model.attributes, data\n model.collection.add model\n save.fail ->\n model.destroy()\n .error ->\n model.destroy()\n stop: (e) ->\n (that.$ '.progress').hide()\n (that.$ '.progress .bar').css 'width', \"0\"\n done: (e, data) ->\n (that.$ '.status').show()\n (that.$ '.status').html 'Upload completed.'\n setTimeout ->\n (that.$ '.status').fadeOut('slow')\n , 5000\n no\n\n clickTabNavUri: (e) => # TODO: clean\n if not (@$ '#tab-uri').hasClass 'active'\n (@$ \"[name='file_upload']\").fileupload 'destroy'\n (@$ 'ul.nav-tabs li').removeClass 'active show'\n (@$ '.tab-pane').removeClass 'active'\n (@$ '.tabnav-uri').addClass 'active show'\n (@$ '#tab-uri').addClass 'active'\n (@$ '#save-asset').show()\n (@$ '.uri').show()\n (@$ '.skip_asset_check_checkbox').show()\n (@$ '.status').hide()\n (@$f 'uri').focus()\n\n updateUriMimetype: => @updateMimetype @$fv 'uri'\n updateFileUploadMimetype: (filename) => @updateMimetype filename\n updateMimetype: (filename) =>\n mt = getMimetype filename\n @$fv 'mimetype', if mt then mt else new Asset().defaults()['mimetype']\n @change_mimetype()\n\n change: (e) =>\n @_change ||= _.throttle (=>\n @validate()\n yes), 500\n @_change arguments...\n\n validate: (e) =>\n that = this\n validators =\n uri: (v) ->\n if v\n if ((that.$ '#tab-uri').hasClass 'active') and not url_test v\n 'please enter a valid URL'\n errors = ([field, v] for field, fn of validators when v = fn (@$fv field))\n\n (@$ \".form-group .help-inline.invalid-feedback\").remove()\n (@$ \".form-group .form-control\").removeClass 'is-invalid'\n (@$ '[type=submit]').prop 'disabled', no\n for [field, v] in errors\n (@$ '[type=submit]').prop 'disabled', yes\n (@$ \".form-group.#{field} .form-control\").addClass 'is-invalid'\n (@$ \".form-group.#{field} .controls\").append \\\n $ (\"#{v}\")\n\n cancel: (e) =>\n (@$el.children \":first\").modal 'hide'\n\n destroyFileUploadWidget: (e) =>\n if (@$ '#tab-file_upload').hasClass 'active'\n (@$ \"[name='file_upload']\").fileupload 'destroy'\n\n\nAPI.View.EditAssetView = class EditAssetView extends Backbone.View\n $f: (field) => @$ \"[name='#{field}']\" # get field element\n $fv: (field, val...) => (@$f field).val val... # get or set filed value\n\n initialize: (options) =>\n ($ 'body').append @$el.html get_template 'asset-modal'\n (@$ 'input.time').timepicker\n minuteStep: 5, showInputs: yes, disableFocus: yes, showMeridian: dateSettings.showMeridian\n\n (@$ 'input[name=\"nocache\"]').prop 'checked', @model.get 'nocache'\n (@$ '.modal-header .close').remove()\n (@$el.children \":first\").modal()\n\n @model.backup()\n\n @model.bind 'change', @render\n\n @render()\n @validate()\n no\n\n render: () =>\n @undelegateEvents()\n (@$ f).attr 'disabled', on for f in 'mimetype uri file_upload'.split ' '\n (@$ '#modalLabel').text \"Edit Asset\"\n (@$ '.asset-location').hide(); (@$ '.uri').hide(); (@$ '.skip_asset_check_checkbox').hide()\n (@$ '.asset-location.edit').show()\n (@$ '.mime-select').prop('disabled', 'true')\n\n if (@model.get 'mimetype') == 'video'\n (@$f 'duration').prop 'disabled', on\n\n for field in @model.fields\n if (@$fv field) != @model.get field\n @$fv field, @model.get field\n (@$ '.uri-text').html insertWbr truncate_str (@model.get 'uri')\n\n for which in ['start', 'end']\n d = date_to @model.get \"#{which}_date\"\n @$fv \"#{which}_date_date\", d.date()\n (@$f \"#{which}_date_date\").datepicker autoclose: yes, format: dateSettings.datepickerFormat\n (@$f \"#{which}_date_date\").datepicker 'setValue', d.date()\n @$fv \"#{which}_date_time\", d.time()\n\n @displayAdvanced()\n @delegateEvents()\n no\n\n viewmodel: =>\n for which in ['start', 'end']\n @$fv \"#{which}_date\", (moment (@$fv \"#{which}_date_date\") + \" \" + (@$fv \"#{which}_date_time\"), dateSettings.fullDate).toDate().toISOString()\n for field in @model.fields when not (@$f field).prop 'disabled'\n @model.set field, (@$fv field), silent:yes\n\n events:\n 'click #save-asset': 'save'\n 'click .cancel': 'cancel'\n 'change': 'change'\n 'keyup': 'change'\n 'click .advanced-toggle': 'toggleAdvanced'\n\n changeLoopTimes: =>\n current_date = new Date()\n end_date = new Date()\n\n switch @$('#loop_times').val()\n when \"day\"\n @setLoopDateTime (date_to current_date), (date_to end_date.setDate(current_date.getDate() + 1))\n when \"week\"\n @setLoopDateTime (date_to current_date), (date_to end_date.setDate(current_date.getDate() + 7))\n when \"month\"\n @setLoopDateTime (date_to current_date), (date_to end_date.setMonth(current_date.getMonth() + 1))\n when \"year\"\n @setLoopDateTime (date_to current_date), (date_to end_date.setFullYear(current_date.getFullYear() + 1))\n when \"forever\"\n @setLoopDateTime (date_to current_date), (date_to end_date.setFullYear(9999))\n when \"manual\"\n @setDisabledDatepicker(false)\n (@$ \"#manul_date\").show()\n return\n else\n return\n @setDisabledDatepicker(true)\n (@$ \"#manul_date\").hide()\n\n save: (e) =>\n @viewmodel()\n save = null\n @model.set 'nocache', if (@$ 'input[name=\"nocache\"]').prop 'checked' then 1 else 0\n\n if not @model.get 'name'\n if @model.old_name()\n @model.set {name: @model.old_name()}, silent:yes\n else if getMimetype @model.get 'uri'\n @model.set {name: get_filename @model.get 'uri'}, silent:yes\n else\n @model.set {name: @model.get 'uri'}, silent:yes\n save = @model.save()\n\n (@$ 'input, select').prop 'disabled', on\n save.done (data) =>\n @model.id = data.asset_id\n @collection.add @model if not @model.collection\n (@$el.children \":first\").modal 'hide'\n _.extend @model.attributes, data\n save.fail =>\n (@$ '.progress').hide()\n (@$ 'input, select').prop 'disabled', off\n no\n\n change: (e) =>\n @_change ||= _.throttle (=>\n @changeLoopTimes()\n @viewmodel()\n @model.trigger 'change'\n @validate(e)\n yes), 500\n @_change arguments...\n\n validate: (e) =>\n that = this\n validators =\n duration: (v) =>\n if ('video' isnt @model.get 'mimetype') and (not (_.isNumber v*1 ) or v*1 < 1)\n 'Please enter a valid number.'\n end_date: (v) =>\n unless (new Date @$fv 'start_date') < (new Date @$fv 'end_date')\n if $(e?.target)?.attr(\"name\") == \"start_date_date\"\n start_date = new Date @$fv 'start_date'\n end_date = new Date(start_date.getTime() + Math.max(parseInt(@$fv 'duration'), 60) * 1000)\n @setLoopDateTime (date_to start_date), (date_to end_date)\n return\n\n 'End date should be after start date.'\n errors = ([field, v] for field, fn of validators when v = fn (@$fv field))\n\n (@$ \".form-group .help-inline.invalid-feedback\").remove()\n (@$ \".form-group .form-control\").removeClass 'is-invalid'\n (@$ '[type=submit]').prop 'disabled', no\n for [field, v] in errors\n (@$ '[type=submit]').prop 'disabled', yes\n (@$ \".form-group.#{field} .form-control\").addClass 'is-invalid'\n (@$ \".form-group.#{field} .controls\").append \\\n $ (\"#{v}\")\n\n\n cancel: (e) =>\n @model.rollback()\n (@$el.children \":first\").modal 'hide'\n\n toggleAdvanced: =>\n (@$ '.fa-play').toggleClass 'rotated'\n (@$ '.fa-play').toggleClass 'unrotated'\n (@$ '.collapse-advanced').collapse 'toggle'\n\n displayAdvanced: =>\n img = 'image' is @$fv 'mimetype'\n edit = url_test @model.get 'uri'\n has_nocache = img and edit\n (@$ '.advanced-accordion').toggle has_nocache is on\n\n setLoopDateTime: (start_date, end_date) =>\n @$fv \"start_date_date\", start_date.date()\n (@$f \"start_date_date\").datepicker autoclose: yes, format: dateSettings.datepickerFormat\n (@$f \"start_date_date\").datepicker 'setDate', moment(start_date.date(), dateSettings.date).toDate()\n @$fv \"start_date_time\", start_date.time()\n @$fv \"end_date_date\", end_date.date()\n (@$f \"end_date_date\").datepicker autoclose: yes, format: dateSettings.datepickerFormat\n (@$f \"end_date_date\").datepicker 'setDate', moment(end_date.date(), dateSettings.date).toDate()\n @$fv \"end_date_time\", end_date.time()\n\n (@$ \".form-group .help-inline.invalid-feedback\").remove()\n (@$ \".form-group .form-control\").removeClass 'is-invalid'\n (@$ '[type=submit]').prop 'disabled', no\n\n setDisabledDatepicker: (b) =>\n for which in ['start', 'end']\n (@$f \"#{which}_date_date\").attr 'disabled', b\n (@$f \"#{which}_date_time\").attr 'disabled', b\n\nAPI.View.AssetRowView = class AssetRowView extends Backbone.View\n tagName: \"tr\"\n\n initialize: (options) =>\n @template = get_template 'asset-row'\n\n render: =>\n @$el.html @template _.extend json = @model.toJSON(),\n name: insertWbr truncate_str json.name # word break urls at slashes\n duration: durationSecondsToHumanReadable(json.duration)\n start_date: (date_to json.start_date).string()\n end_date: (date_to json.end_date).string()\n @$el.prop 'id', @model.get 'asset_id'\n (@$ \".delete-asset-button\").popover content: get_template 'confirm-delete'\n (@$ \".toggle input\").prop \"checked\", @model.get 'is_enabled'\n (@$ \".asset-icon\").addClass switch @model.get \"mimetype\"\n when \"video\" then \"fas fa-video\"\n when \"streaming\" then \"fas fa-video\"\n when \"image\" then \"far fa-image\"\n when \"webpage\" then \"fas fa-globe-americas\"\n else \"\"\n\n if (@model.get \"is_processing\") == 1\n (@$ 'input, button').prop 'disabled', on\n (@$ \".asset-toggle\").html get_template 'processing-message'\n\n @el\n\n events:\n 'change .is_enabled-toggle input': 'toggleIsEnabled'\n 'click .download-asset-button': 'download'\n 'click .edit-asset-button': 'edit'\n 'click .delete-asset-button': 'showPopover'\n\n toggleIsEnabled: (e) =>\n val = (1 + @model.get 'is_enabled') % 2\n @model.set is_enabled: val\n @setEnabled off\n save = @model.save()\n save.done => @setEnabled on\n save.fail =>\n @model.set @model.previousAttributes(), silent:yes # revert changes\n @setEnabled on\n @render()\n yes\n\n setEnabled: (enabled) => if enabled\n @$el.removeClass 'warning'\n @delegateEvents()\n (@$ 'input, button').prop 'disabled', off\n else\n @hidePopover()\n @undelegateEvents()\n @$el.addClass 'warning'\n (@$ 'input, button').prop 'disabled', on\n\n download: (e) =>\n r = $.get '/api/v1/assets/' + @model.id + '/content'\n .success (result) ->\n switch result['type']\n when 'url'\n window.open(result['url'])\n when 'file'\n content = base64js.toByteArray(result['content'])\n\n mimetype = result['mimetype']\n fn = result['filename']\n\n blob = new Blob([content], {type: mimetype})\n url = URL.createObjectURL(blob)\n\n a = document.createElement('a')\n document.body.appendChild(a)\n a.download = fn\n a.href = url\n a.click()\n\n URL.revokeObjectURL(url)\n a.remove()\n no\n\n edit: (e) =>\n new EditAssetView model: @model\n no\n\n delete: (e) =>\n @hidePopover()\n if (xhr = @model.destroy()) is not false\n xhr.done => @remove()\n else\n @remove()\n no\n\n showPopover: =>\n if not ($ '.popover').length\n (@$ \".delete-asset-button\").popover 'show'\n ($ '.confirm-delete').click @delete\n ($ window).one 'click', @hidePopover\n no\n\n hidePopover: =>\n (@$ \".delete-asset-button\").popover 'hide'\n no\n\n\nAPI.View.AssetsView = class AssetsView extends Backbone.View\n initialize: (options) =>\n @collection.bind event, @render for event in ('reset add remove sync'.split ' ')\n @sorted = (@$ '#active-assets').sortable\n containment: 'parent'\n axis: 'y'\n helper: 'clone'\n update: @update_order\n\n update_order: =>\n active = (@$ '#active-assets').sortable 'toArray'\n\n @collection.get(id).set('play_order', i) for id, i in active\n @collection.get(el.id).set('play_order', active.length) for el in (@$ '#inactive-assets tr').toArray()\n\n $.post '/api/v1/assets/order', ids: ((@$ '#active-assets').sortable 'toArray').join ','\n\n render: =>\n @collection.sort()\n\n (@$ \"##{which}-assets\").html '' for which in ['active', 'inactive']\n\n @collection.each (model) =>\n which = if model.active() then 'active' else 'inactive'\n (@$ \"##{which}-assets\").append (new AssetRowView model: model).render()\n\n for which in ['active', 'inactive']\n if (@$ \"##{which}-assets tr\").length == 0\n (@$ \"##{which}-assets-section .table-assets-help-text\").show()\n else\n (@$ \"##{which}-assets-section .table-assets-help-text\").hide()\n\n for which in ['inactive', 'active']\n @$(\".#{which}-table thead\").toggle !!(@$(\"##{which}-assets tr\").length)\n\n @update_order()\n\n @el\n\n\nAPI.App = class App extends Backbone.View\n initialize: =>\n ($ window).ajaxError (e,r) ->\n ($ '#request-error').html (get_template 'request-error')()\n if (j = $.parseJSON r.responseText) and (err = j.error)\n ($ '#request-error .msg').text 'Server Error: ' + err\n ($ '#request-error').show()\n setTimeout ->\n ($ '#request-error').fadeOut('slow')\n , 5000\n ($ window).ajaxSuccess (event, request, settings) ->\n if (settings.url == new Assets().url) and (settings.type == 'POST')\n ($ '#request-error').html (get_template 'request-success')()\n ($ '#request-error .msg').text 'Asset has been successfully uploaded.'\n ($ '#request-error').show()\n setTimeout ->\n ($ '#request-error').fadeOut('slow')\n , 5000\n\n (API.assets = new Assets()).fetch()\n API.assetsView = new AssetsView\n collection: API.assets\n el: @$ '#assets'\n\n for address in wsAddresses\n try\n ws = new WebSocket address\n ws.onmessage = (x) ->\n model = API.assets.get(x.data)\n if model\n save = model.fetch()\n catch error\n no\n\n events:\n 'click .add-asset-button': 'add',\n 'click #previous-asset-button': 'previous',\n 'click #next-asset-button': 'next'\n\n add: (e) ->\n new AddAssetView\n no\n\n previous: (e) ->\n $.get '/api/v1/assets/control/previous'\n\n next: (e) ->\n $.get '/api/v1/assets/control/next'\n" - ] + "mappings": "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" } \ No newline at end of file diff --git a/viewer.py b/viewer.py index 3309528c6..96710fd09 100755 --- a/viewer.py +++ b/viewer.py @@ -432,7 +432,7 @@ def setup(): def setup_hotspot(): - bus = pydbus.SystemBus() + bus = pydbus.SessionBus() pattern_include = re.compile("wlan*") pattern_exclude = re.compile("ScreenlyOSE-*") From e3e69117ff6471febe3fa51ecbe7ed1353c51013 Mon Sep 17 00:00:00 2001 From: Rusko124 Date: Thu, 5 Nov 2020 13:57:12 +0600 Subject: [PATCH 028/224] Edits: docker-compose in the ansible task --- .../screenly/files/screenly-celery.service | 13 --- .../roles/screenly/files/screenly-web.service | 15 --- .../screenly-websocket_server_layer.service | 11 --- ansible/roles/screenly/tasks/main.yml | 92 +++++++++++++++---- ansible/roles/screenly/vars/main.yml | 6 +- ansible/roles/ssl/tasks/main.yml | 13 --- ansible/roles/system/tasks/main.yml | 2 +- docker-compose.demo.yml | 2 + docker/Dockerfile.celery.dev | 7 +- docker/Dockerfile.server | 43 +++++++++ docker/Dockerfile.websocket | 29 ++++++ lib/utils.py | 4 + requirements/requirements.host.txt | 2 + server.py | 3 +- templates/settings.html | 2 +- 15 files changed, 164 insertions(+), 80 deletions(-) delete mode 100644 ansible/roles/screenly/files/screenly-celery.service delete mode 100644 ansible/roles/screenly/files/screenly-web.service delete mode 100644 ansible/roles/screenly/files/screenly-websocket_server_layer.service create mode 100644 docker/Dockerfile.server create mode 100644 docker/Dockerfile.websocket create mode 100644 requirements/requirements.host.txt diff --git a/ansible/roles/screenly/files/screenly-celery.service b/ansible/roles/screenly/files/screenly-celery.service deleted file mode 100644 index 82b5ebe63..000000000 --- a/ansible/roles/screenly/files/screenly-celery.service +++ /dev/null @@ -1,13 +0,0 @@ -[Unit] -Description=Screenly celery worker -After=redis-server.service - -[Service] -WorkingDirectory=/home/pi/screenly -User=pi -ExecStart=/usr/local/bin/celery worker -A server.celery -B -n worker@screenly --loglevel=info --schedule /home/pi/.screenly/celerybeat-schedule -Restart=always -RestartSec=5 - -[Install] -WantedBy=multi-user.target diff --git a/ansible/roles/screenly/files/screenly-web.service b/ansible/roles/screenly/files/screenly-web.service deleted file mode 100644 index 15051195c..000000000 --- a/ansible/roles/screenly/files/screenly-web.service +++ /dev/null @@ -1,15 +0,0 @@ -[Unit] -Description=Screenly Web UI -After=network-online.target - -[Service] -WorkingDirectory=/home/pi/screenly -User=pi -ExecStartPre=/usr/bin/python /home/pi/screenly/bin/wait.py -ExecStart=/usr/bin/python /home/pi/screenly/server.py -Restart=always -RestartSec=5 -Environment=PYTHONPATH=/home/pi/screenly - -[Install] -WantedBy=multi-user.target diff --git a/ansible/roles/screenly/files/screenly-websocket_server_layer.service b/ansible/roles/screenly/files/screenly-websocket_server_layer.service deleted file mode 100644 index e196768c0..000000000 --- a/ansible/roles/screenly/files/screenly-websocket_server_layer.service +++ /dev/null @@ -1,11 +0,0 @@ -[Unit] -Description=Websocket Server layer - -[Service] -WorkingDirectory=/home/pi/screenly -User=pi -ExecStart=/usr/bin/python /home/pi/screenly/websocket_server_layer.py -Restart=always - -[Install] -WantedBy=multi-user.target \ No newline at end of file diff --git a/ansible/roles/screenly/tasks/main.yml b/ansible/roles/screenly/tasks/main.yml index ad60aa60d..77d8dc575 100644 --- a/ansible/roles/screenly/tasks/main.yml +++ b/ansible/roles/screenly/tasks/main.yml @@ -33,7 +33,7 @@ - name: Install pip dependencies pip: - requirements: /home/pi/screenly/requirements/requirements.txt + requirements: /home/pi/screenly/requirements/requirements.host.txt extra_args: "--no-cache-dir --upgrade" - name: Create default assets database if does not exists @@ -138,7 +138,6 @@ state: stopped enabled: no with_items: "{{ deprecated_screenly_systemd_units }}" - when: x_service_exist - name: Remove deprecated systemd units file: @@ -156,23 +155,78 @@ - debug: msg: "Use {{ docker_tag }} version of images." -- name: Create screenly-viewer container - docker_container: - name: screenly-ose-viewer - image: "ruskodevelop/screenly-ose-viewer:latest" - pull: true - state: started - recreate: true - restart_policy: unless-stopped - network_mode: host - privileged: yes - log_driver: journald - volumes: - - /home/pi/screenly:/home/pi/screenly - - /home/pi/.screenly:/home/pi/.screenly - - /home/pi/screenly_assets:/home/pi/screenly_assets - - /etc/timezone:/etc/timezone:ro - - /etc/localtime:/etc/localtime:ro +- name: Run docker-compose + docker_compose: + project_name: screenly + definition: + version: "2" + services: + screenly-server: + image: ruskodevelop/screenly-ose-server:latest + container_name: screenly-server + entrypoint: python server.py + restart: unless-stopped + network_mode: "host" + volumes: + - /home/pi/screenly_assets:/home/pi/screenly_assets + - /home/pi/screenly:/home/pi/screenly + - /home/pi/.screenly:/home/pi/.screenly + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + + screenly-viewer: + image: ruskodevelop/screenly-ose-viewer:latest + container_name: screenly-viewer + privileged: true + restart: unless-stopped + network_mode: "host" + volumes: + - /home/pi/screenly_assets:/home/pi/screenly_assets + - /home/pi/screenly:/home/pi/screenly + - /home/pi/.screenly:/home/pi/.screenly + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + depends_on: + - screenly-server + + screenly-websocket: + image: ruskodevelop/screenly-ose-websocket:latest + container_name: screenly-websocket + restart: unless-stopped + network_mode: "host" + volumes: + - /home/pi/screenly_assets:/home/pi/screenly_assets + - /home/pi/screenly:/home/pi/screenly + - /home/pi/.screenly:/home/pi/.screenly + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + depends_on: + - screenly-server + + screenly-celery: + image: ruskodevelop/screenly-ose-server:latest + container_name: screenly-celery + entrypoint: celery worker -A server.celery -B -n worker@screenly --loglevel=info --schedule /tmp/celerybeat-schedule + network_mode: "host" + restart: unless-stopped + volumes: + - /home/pi/screenly_assets:/home/pi/screenly_assets + - /home/pi/screenly:/home/pi/screenly + - /home/pi/.screenly:/home/pi/.screenly + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + depends_on: + - screenly-server + - redis + + redis: + image: redis:alpine + restart: unless-stopped + ports: + - "6379:6379" + + pull: yes + recreate: smart - name: Pause screenly-viewer container command: docker pause screenly-ose-viewer diff --git a/ansible/roles/screenly/vars/main.yml b/ansible/roles/screenly/vars/main.yml index 3c335064e..fd9e1958f 100644 --- a/ansible/roles/screenly/vars/main.yml +++ b/ansible/roles/screenly/vars/main.yml @@ -1,10 +1,10 @@ screenly_systemd_units: - - screenly-celery.service - - screenly-web.service - - screenly-websocket_server_layer.service - udev-restart.service deprecated_screenly_systemd_units: + - screenly-celery.service + - screenly-web.service + - screenly-websocket_server_layer.service - screenly-viewer.service - X.service - matchbox.service diff --git a/ansible/roles/ssl/tasks/main.yml b/ansible/roles/ssl/tasks/main.yml index 763355af6..48126ce66 100644 --- a/ansible/roles/ssl/tasks/main.yml +++ b/ansible/roles/ssl/tasks/main.yml @@ -68,16 +68,3 @@ tags: - enable-ssl when: not no_use_ssl_parameter - -- name: Modifies screenly-web service to only listen on localhost - lineinfile: - regexp: '^.*LISTEN.*' - state: absent - dest: /etc/systemd/system/screenly-web.service - notify: - - reload systemctl - - restart-screenly-websocket_server_layer - - restart-screenly-server - - restart-screenly-celery - tags: - - enable-ssl diff --git a/ansible/roles/system/tasks/main.yml b/ansible/roles/system/tasks/main.yml index 1a06efb8e..927043859 100644 --- a/ansible/roles/system/tasks/main.yml +++ b/ansible/roles/system/tasks/main.yml @@ -177,7 +177,6 @@ - python-gobject - python-netifaces - python-simplejson - - redis-server - rpi-update - sqlite3 - systemd @@ -192,6 +191,7 @@ - matchbox - pix-plym-splash - rabbitmq-server + - redis-server - supervisor - uzbl - x11-xserver-utils diff --git a/docker-compose.demo.yml b/docker-compose.demo.yml index 7b843e2e8..b66da1968 100644 --- a/docker-compose.demo.yml +++ b/docker-compose.demo.yml @@ -14,6 +14,7 @@ services: - ./:/home/pi/screenly:ro - screenly-assets-volume:/home/pi/screenly_assets - screenly-volume:/home/pi/.screenly + screenly-celery: image: screenly/ose-dev-celery:latest restart: always @@ -27,6 +28,7 @@ services: - ./:/home/pi/screenly:ro - screenly-assets-volume:/home/pi/screenly_assets - screenly-volume:/home/pi/.screenly + redis: image: redis:alpine logging: diff --git a/docker/Dockerfile.celery.dev b/docker/Dockerfile.celery.dev index a64733314..b195f332c 100644 --- a/docker/Dockerfile.celery.dev +++ b/docker/Dockerfile.celery.dev @@ -8,13 +8,14 @@ RUN apt-get update && \ git \ libffi-dev \ libssl-dev \ + lsb-release \ mplayer \ net-tools \ procps \ + python-pip \ + python-setuptools \ python-dev \ python-gobject \ - python-setuptools \ - python-pip \ python-pil \ python-simplejson \ sqlite3 \ @@ -23,7 +24,7 @@ RUN apt-get update && \ # Install Python requirements ADD requirements/requirements.txt /tmp/requirements.txt -RUN pip install -r /tmp/requirements.txt +RUN pip install --no-cache-dir -r /tmp/requirements.txt RUN useradd pi diff --git a/docker/Dockerfile.server b/docker/Dockerfile.server new file mode 100644 index 000000000..459f10e0d --- /dev/null +++ b/docker/Dockerfile.server @@ -0,0 +1,43 @@ +FROM resin/rpi-raspbian:buster + +RUN apt-get update && \ + apt-get -y install --no-install-recommends \ + build-essential \ + curl \ + ffmpeg \ + git \ + libffi-dev \ + libssl-dev \ + lsb-release \ + mplayer \ + net-tools \ + procps \ + python-pip \ + python-setuptools \ + python-dev \ + python-gobject \ + python-pil \ + python-simplejson \ + sqlite3 \ + && \ + apt-get clean + +# Install Python requirements +ADD requirements/requirements.txt /tmp/requirements.txt +RUN pip install --no-cache-dir -r /tmp/requirements.txt + +# Create runtime user +RUN useradd pi + +# Install config file and file structure +RUN mkdir -p /home/pi/.screenly /home/pi/screenly /home/pi/screenly_assets +COPY ansible/roles/screenly/files/default_assets.yml /home/pi/.screenly/default_assets.yml +COPY ansible/roles/screenly/files/screenly.conf /home/pi/.screenly/screenly.conf +RUN chown -R pi:pi /home/pi/.screenly /home/pi/screenly_assets + +USER pi +WORKDIR /home/pi/screenly + +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 diff --git a/docker/Dockerfile.websocket b/docker/Dockerfile.websocket new file mode 100644 index 000000000..cd4014b11 --- /dev/null +++ b/docker/Dockerfile.websocket @@ -0,0 +1,29 @@ +FROM resin/rpi-raspbian:buster + +RUN apt-get update && \ + apt-get -y install --no-install-recommends \ + build-essential \ + curl \ + libffi-dev \ + python-setuptools \ + python-pip \ + python-dev && \ + apt-get clean + +RUN pip install --upgrade pip + +# Install Python requirements +ADD requirements/requirements-websocket.txt /tmp/requirements.txt +RUN pip install --no-cache-dir -r /tmp/requirements.txt + +# Create runtime user +RUN useradd pi + +# Install config file and file structure +RUN mkdir -p /home/pi/.screenly /home/pi/screenly /home/pi/screenly_assets +RUN chown -R pi:pi /home/pi/.screenly /home/pi/screenly_assets + +USER pi +WORKDIR /home/pi/screenly + +CMD python websocket_server_layer.py diff --git a/lib/utils.py b/lib/utils.py index d6adb7a03..bcbc0c19b 100644 --- a/lib/utils.py +++ b/lib/utils.py @@ -331,6 +331,10 @@ def generate_perfect_paper_password(pw_length=10, has_symbols=True): return "".join(random.SystemRandom().choice(ppp_letters) for _ in range(pw_length)) +def is_docker(): + return os.path.isfile('/.dockerenv') + + def is_balena_app(): """ Checks the application is running on Balena Cloud diff --git a/requirements/requirements.host.txt b/requirements/requirements.host.txt new file mode 100644 index 000000000..a3d2b8919 --- /dev/null +++ b/requirements/requirements.host.txt @@ -0,0 +1,2 @@ +docker==4.3.1 +docker-compose==1.26.2 diff --git a/server.py b/server.py index b1e9ebd09..617c57698 100755 --- a/server.py +++ b/server.py @@ -42,7 +42,7 @@ from lib import queries from lib.auth import authorized -from lib.utils import generate_perfect_paper_password +from lib.utils import generate_perfect_paper_password, is_docker from lib.utils import get_active_connections, remove_connection from lib.utils import get_node_ip, get_node_mac_address from lib.utils import get_video_duration @@ -1740,6 +1740,7 @@ def settings_page(): 'user': settings['user'], 'need_current_password': bool(settings['auth_backend']), 'is_balena': is_balena_app(), + 'is_docker': is_docker(), 'auth_backend': settings['auth_backend'], 'auth_backends': auth_backends }) diff --git a/templates/settings.html b/templates/settings.html index ef382febf..0bb36c579 100644 --- a/templates/settings.html +++ b/templates/settings.html @@ -227,7 +227,7 @@