<commit_before>import sys
import numpy as np
from PySide import QtGui, QtCore
from sharppy.viz import plotSkewT, plotHodo, plotText, plotAnalogues
from sharppy.viz import plotThetae, plotWinds, plotSpeed, plotKinematics
from sharppy.viz import plotSlinky, plotWatch, plotAdvection, plotSTP
from sharppy.sounding import prof, plot_title

# Setup Application
app = QtGui.QApplication(sys.argv)
mainWindow = QtGui.QMainWindow()
mainWindow.setGeometry(50, 50, 1180, 800)
title = 'SHARPpy: Sounding and Hodograph Analysis and Research Program '
title += 'in Python'
mainWindow.setWindowTitle(title)
mainWindow.setStyleSheet("QMainWindow {background-color: rgb(0, 0, 0);}")
centralWidget = QtGui.QFrame()
mainWindow.setCentralWidget(centralWidget)
grid = QtGui.QGridLayout()
grid.setHorizontalSpacing(0)
grid.setVerticalSpacing(2)
centralWidget.setLayout(grid)

# Handle the Upper Left
## plot the main sounding
#print prof.right_scp, prof.left_scp
brand = 'Oklahoma Weather Lab'
err = np.random.randint(3, high=5, size=(prof.tmpc.shape[0]))
sound = plotSkewT(prof, stdev=err, pcl=prof.mupcl, title=plot_title, brand=brand)
sound.setContentsMargins(0, 0, 0, 0)
grid.addWidget(sound, 0, 0, 3, 1)

# Handle the Upper Right
urparent = QtGui.QFrame()
urparent_grid = QtGui.QGridLayout()
urparent_grid.setContentsMargins(0, 0, 0, 0)
urparent.setLayout(urparent_grid)
ur = QtGui.QFrame()
ur.setStyleSheet("QFrame {"
                 "  background-color: rgb(0, 0, 0);"
                 "  border-width: 0px;"
                 "  border-style: solid;"
                 "  border-color: rgb(255, 255, 255);"
                 "  margin: 0px;}")
brand = QtGui.QLabel('HOOT - Oklahoma Weather Lab')
brand.setAlignment(QtCore.Qt.AlignRight)
brand.setStyleSheet("QFrame {"
                    "  background-color: rgb(0, 0, 0);"
                    "  text-align: right;"
                    "  font-size: 11px;"
                    "  color: #FFFFFF;}")
grid2 = QtGui.QGridLayout()
grid2.setHorizontalSpacing(0)
grid2.setVerticalSpacing(0)
grid2.setContentsMargins(0, 0, 0, 0)
ur.setLayout(grid2)

speed_vs_height = plotSpeed( prof )
speed_vs_height.setObjectName("svh")
inferred_temp_advection = plotAdvection(prof)
hodo = plotHodo(prof.hght, prof.u, prof.v, prof=prof)
storm_slinky = plotSlinky(prof)
thetae_vs_pressure = plotThetae(prof)
srwinds_vs_height = plotWinds(prof)
watch_type = plotWatch(prof)

grid2.addWidget(speed_vs_height, 0, 0, 11, 3)
grid2.addWidget(inferred_temp_advection, 0, 3, 11, 2)
grid2.addWidget(hodo, 0, 5, 8, 24)
grid2.addWidget(storm_slinky, 8, 5, 3, 6)
grid2.addWidget(thetae_vs_pressure, 8, 11, 3, 6)
grid2.addWidget(srwinds_vs_height, 8, 17, 3, 6)
grid2.addWidget(watch_type, 8, 23, 3, 6)
urparent_grid.addWidget(brand, 0, 0, 1, 0)
urparent_grid.addWidget(ur, 1, 0, 50, 0)
grid.addWidget(urparent, 0, 1, 3, 1)

# Handle the Text Areas
text = QtGui.QFrame()
text.setStyleSheet("QWidget {"
                   "  background-color: rgb(0, 0, 0);"
                   "  border-width: 2px;"
                   "  border-style: solid;"
                   "  border-color: #3399CC;}")
grid3 = QtGui.QGridLayout()
grid3.setHorizontalSpacing(0)
grid3.setContentsMargins(0, 0, 0, 0)
text.setLayout(grid3)
convective = plotText(prof)
#convective = QtGui.QFrame()
#kinematic = QtGui.QFrame()
kinematic = plotKinematics(prof)
SARS = plotAnalogues(prof)
stp = plotSTP(prof)
grid3.addWidget(convective, 0, 0)
grid3.addWidget(kinematic, 0, 1)
grid3.addWidget(SARS, 0, 2)
grid3.addWidget(stp, 0, 3)
grid.addWidget(text, 3, 0, 1, 2)
#pixmap = QtGui.QPixmap.grabWidget(mainWindow)
#pixmap.save('skewt.png', 'PNG', 100)
mainWindow.show()

app.exec_()
<commit_msg>updated gui<commit_after>import sys
import numpy as np
from PySide import QtGui, QtCore
from sharppy.viz import plotSkewT, plotHodo, plotText, plotAnalogues
from sharppy.viz import plotThetae, plotWinds, plotSpeed, plotKinematics
from sharppy.viz import plotSlinky, plotWatch, plotAdvection, plotSTP
from sharppy.sounding import prof, plot_title

# Setup Application
app = QtGui.QApplication(sys.argv)
mainWindow = QtGui.QMainWindow()
mainWindow.setGeometry(50, 50, 1180, 800)
title = 'SHARPpy: Sounding and Hodograph Analysis and Research Program '
title += 'in Python'
mainWindow.setWindowTitle(title)
mainWindow.setStyleSheet("QMainWindow {background-color: rgb(0, 0, 0);}")
centralWidget = QtGui.QFrame()
mainWindow.setCentralWidget(centralWidget)
grid = QtGui.QGridLayout()
grid.setHorizontalSpacing(0)
grid.setVerticalSpacing(2)
centralWidget.setLayout(grid)

# Handle the Upper Left
## plot the main sounding
#print prof.right_scp, prof.left_scp
brand = 'Oklahoma Weather Lab'
sound = plotSkewT(prof, pcl=prof.mupcl, title=plot_title, brand=brand)
sound.setContentsMargins(0, 0, 0, 0)
grid.addWidget(sound, 0, 0, 3, 1)

# Handle the Upper Right
urparent = QtGui.QFrame()
urparent_grid = QtGui.QGridLayout()
urparent_grid.setContentsMargins(0, 0, 0, 0)
urparent.setLayout(urparent_grid)
ur = QtGui.QFrame()
ur.setStyleSheet("QFrame {"
                 "  background-color: rgb(0, 0, 0);"
                 "  border-width: 0px;"
                 "  border-style: solid;"
                 "  border-color: rgb(255, 255, 255);"
                 "  margin: 0px;}")
brand = QtGui.QLabel('HOOT - Oklahoma Weather Lab')
brand.setAlignment(QtCore.Qt.AlignRight)
brand.setStyleSheet("QFrame {"
                    "  background-color: rgb(0, 0, 0);"
                    "  text-align: right;"
                    "  font-size: 11px;"
                    "  color: #FFFFFF;}")
grid2 = QtGui.QGridLayout()
grid2.setHorizontalSpacing(0)
grid2.setVerticalSpacing(0)
grid2.setContentsMargins(0, 0, 0, 0)
ur.setLayout(grid2)

speed_vs_height = plotSpeed( prof )
speed_vs_height.setObjectName("svh")
inferred_temp_advection = plotAdvection(prof)
hodo = plotHodo(prof.hght, prof.u, prof.v, prof=prof)
storm_slinky = plotSlinky(prof)
thetae_vs_pressure = plotThetae(prof)
srwinds_vs_height = plotWinds(prof)
watch_type = plotWatch(prof)

grid2.addWidget(speed_vs_height, 0, 0, 11, 3)
grid2.addWidget(inferred_temp_advection, 0, 3, 11, 2)
grid2.addWidget(hodo, 0, 5, 8, 24)
grid2.addWidget(storm_slinky, 8, 5, 3, 6)
grid2.addWidget(thetae_vs_pressure, 8, 11, 3, 6)
grid2.addWidget(srwinds_vs_height, 8, 17, 3, 6)
grid2.addWidget(watch_type, 8, 23, 3, 6)
urparent_grid.addWidget(brand, 0, 0, 1, 0)
urparent_grid.addWidget(ur, 1, 0, 50, 0)
grid.addWidget(urparent, 0, 1, 3, 1)

# Handle the Text Areas
text = QtGui.QFrame()
text.setStyleSheet("QWidget {"
                   "  background-color: rgb(0, 0, 0);"
                   "  border-width: 2px;"
                   "  border-style: solid;"
                   "  border-color: #3399CC;}")
grid3 = QtGui.QGridLayout()
grid3.setHorizontalSpacing(0)
grid3.setContentsMargins(0, 0, 0, 0)
text.setLayout(grid3)
convective = plotText(prof)
#convective = QtGui.QFrame()
#kinematic = QtGui.QFrame()
kinematic = plotKinematics(prof)
SARS = plotAnalogues(prof)
stp = plotSTP(prof)
grid3.addWidget(convective, 0, 0)
grid3.addWidget(kinematic, 0, 1)
grid3.addWidget(SARS, 0, 2)
grid3.addWidget(stp, 0, 3)
grid.addWidget(text, 3, 0, 1, 2)
#pixmap = QtGui.QPixmap.grabWidget(mainWindow)
#pixmap.save('skewt.png', 'PNG', 100)
mainWindow.show()

app.exec_()
<|endoftext|>


<commit_before># This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/
from __future__ import division, absolute_import, with_statement, print_function, unicode_literals
import requests
import json
import time
from common import basename

__author__ = "Drew Bonasera"
__license__ = "MPL 2.0"

TYPE = "Detonation"
NAME = "Cuckoo Sandbox"
DEFAULTCONF = {
    "ENABLED": False,
    "API URL": 'http://cuckoo:8090/',
    "timeout": 360,
    "running timeout": 120,
    "delete tasks": False,
    "maec": False,
}

def fetch_report_json(report_url):
    report = requests.get(report_url)
    if report.status_code == 200:
        return report.json()
    return {}

def check(conf=DEFAULTCONF):
    return conf["ENABLED"]

def scan(filelist, conf=DEFAULTCONF):
    resultlist = []
    tasks = []
    if conf['API URL'].endswith('/'):
        url = conf['API URL']
    else:
        url = conf['API URL'] + '/'
    new_file_url = url + 'tasks/create/file'
    report_url = url + 'tasks/report/'
    view_url = url + 'tasks/view/'
    delete_url = url + 'tasks/delete/'
    maec_report_url = url + 'tasks/report/{task_id}/maec'

    for fname in filelist:
        with open(fname, "rb") as sample:
            multipart_file = {"file": (basename(fname), sample)}
            payload = {"timeout": conf['timeout']}
            request = requests.post(new_file_url, files=multipart_file, json=json.dumps(payload))

        task_id = request.json()["task_id"]
        if task_id is not None:
            tasks.append((fname, str(task_id)))
        else:
            #TODO Do something here?
            pass

    # Wait for tasks to finish
    task_status = {}
    while tasks:
        for fname, task_id in tasks[:]:
            status = requests.get(view_url+task_id).json()['task']['status']

            # TODO - if we don't find a report, should we add (fname, {}) or
            # just skip fname?
            # If we have a report
            if status == 'reported':
                report = fetch_report_json(report_url+task_id)
                if conf['maec']:
                    maec_report = fetch_report_json(
                        maec_report_url.format(task_id=task_id))
                    report['maec'] = maec_report
                resultlist.append((fname, report))
                tasks.remove((fname, task_id))
                if conf['delete tasks']:
                    requests.get(delete_url+task_id)

            # Check for dead tasks
            elif status == 'running':
                if task_id not in task_status:
                    task_status[task_id] = time.time() + conf['timeout'] + conf['running timeout']
                else:
                    if time.time() > task_status[task_id]:
                        #TODO Log timeout
                        tasks.remove((fname, task_id))

            # If there is an unknown status
            elif status not in ['pending', 'processing', 'finished', 'completed', 'running']:
                #TODO Log errors better
                tasks.remove((fname, task_id))
        time.sleep(15)

    metadata = {}
    metadata["Name"] = NAME
    metadata["Type"] = TYPE
    metadata["Include"] = False
    return (resultlist, metadata)
<commit_msg>Add link to Cuckoo web UI for analysts to view results within Cuckoo<commit_after># This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/
from __future__ import division, absolute_import, with_statement, print_function, unicode_literals
import requests
import json
import time
from common import basename

__author__ = "Drew Bonasera"
__license__ = "MPL 2.0"

TYPE = "Detonation"
NAME = "Cuckoo Sandbox"
DEFAULTCONF = {
    "ENABLED": False,
    "API URL": 'http://cuckoo:8090/',
    "WEB URL": 'http://cuckoo:80/',
    "timeout": 360,
    "running timeout": 120,
    "delete tasks": False,
    "maec": False,
}

def fetch_report_json(report_url):
    report = requests.get(report_url)
    if report.status_code == 200:
        return report.json()
    return {}

def normalize_url(url):
    if url.endswith('/'):
        return url
    return url + '/'

def check(conf=DEFAULTCONF):
    return conf["ENABLED"]

def scan(filelist, conf=DEFAULTCONF):
    resultlist = []
    tasks = []

    api_url = normalize_url(conf['API URL'])
    web_url = normalize_url(conf['WEB URL'])

    new_file_url = api_url + 'tasks/create/file'
    report_url = api_url + 'tasks/report/'
    view_url = api_url + 'tasks/view/'
    delete_url = api_url + 'tasks/delete/'
    maec_report_url = api_url + 'tasks/report/{task_id}/maec'
    web_report_url = web_url + 'analysis/{task_id}/summary/'

    for fname in filelist:
        with open(fname, "rb") as sample:
            multipart_file = {"file": (basename(fname), sample)}
            payload = {"timeout": conf['timeout']}
            request = requests.post(new_file_url, files=multipart_file, json=json.dumps(payload))

        task_id = request.json()["task_id"]
        if task_id is not None:
            tasks.append((fname, str(task_id)))
        else:
            #TODO Do something here?
            pass

    # Wait for tasks to finish
    task_status = {}
    while tasks:
        for fname, task_id in tasks[:]:
            status = requests.get(view_url+task_id).json()['task']['status']

            # TODO - if we don't find a report, should we add (fname, {}) or
            # just skip fname?
            # If we have a report
            if status == 'reported':
                report = fetch_report_json(report_url+task_id)
                if conf['maec']:
                    maec_report = fetch_report_json(
                        maec_report_url.format(task_id=task_id))
                    report['maec'] = maec_report
                # TODO - should we just modify Cuckoo to add this itself?
                if report.get('info'):
                    report['info']['web_report'] = web_report_url.format(
                        task_id=task_id)
                resultlist.append((fname, report))
                tasks.remove((fname, task_id))
                if conf['delete tasks']:
                    requests.get(delete_url+task_id)

            # Check for dead tasks
            elif status == 'running':
                if task_id not in task_status:
                    task_status[task_id] = time.time() + conf['timeout'] + conf['running timeout']
                else:
                    if time.time() > task_status[task_id]:
                        #TODO Log timeout
                        tasks.remove((fname, task_id))

            # If there is an unknown status
            elif status not in ['pending', 'processing', 'finished', 'completed', 'running']:
                #TODO Log errors better
                tasks.remove((fname, task_id))
        time.sleep(15)

    metadata = {}
    metadata["Name"] = NAME
    metadata["Type"] = TYPE
    metadata["Include"] = False
    return (resultlist, metadata)
<|endoftext|>


<commit_before>import os.path
import cStringIO
from django.db.models import ImageField
from django.db.models.fields.files import ImageFieldFile
from django.core.files.base import ContentFile
from django.core.files.images import ImageFile
from image_variations.transforms import Transforms
from image_variations.settings import IMAGE_VARIATIONS

class ImageVariationsFieldFile(ImageFieldFile):

    def save(self, name, content, save=True):
        super(ImageFieldFile, self).save(name, content, save)
        self._create_variations(content)

    def delete(self, save=True):
        for variation in IMAGE_VARIATIONS.iterkeys():
            self.storage.delete(self._variation_name(variation))
        super(ImageFieldFile, self).delete(save)

    def __getattr__(self, variation):
        if variation in IMAGE_VARIATIONS:
            name = self._variation_name(variation)
            try:
                image = ImageFile(self.storage.open(name), name)
                image.path = self.storage.path(image.name)
                image.url = self.storage.url(image.name)
                return image
            except IOError:
                pass
        raise AttributeError

    def _variation_name(self, variation):
        root, ext = os.path.splitext(self.name)
        return root + '_' + variation + ext

    def _create_variations(self, content):
        for variation, transforms in IMAGE_VARIATIONS.iteritems():
            content.seek(0)
            io = cStringIO.StringIO()
            image = Transforms()
            image.open(content)
            for transform in transforms:
                method_name, kwargs = transform
                if method_name == 'save':
                    kwargs['path'] = io
                method = getattr(image, method_name)
                method(**kwargs)
            if not 'save' in transforms:
                image.save(io)
            image_file = ContentFile(io.getvalue())
            self.storage.save(self._variation_name(variation), image_file)

class ImageVariationsField(ImageField):
    attr_class = ImageVariationsFieldFile
<commit_msg>Allow images with no absolute paths (s3)<commit_after>import os.path
import cStringIO
from django.db.models import ImageField
from django.db.models.fields.files import ImageFieldFile
from django.core.files.base import ContentFile
from django.core.files.images import ImageFile
from image_variations.transforms import Transforms
from image_variations.settings import IMAGE_VARIATIONS

class ImageVariationsFieldFile(ImageFieldFile):

    def save(self, name, content, save=True):
        super(ImageFieldFile, self).save(name, content, save)
        self._create_variations(content)

    def delete(self, save=True):
        for variation in IMAGE_VARIATIONS.iterkeys():
            self.storage.delete(self._variation_name(variation))
        super(ImageFieldFile, self).delete(save)

    def __getattr__(self, variation):
        if variation in IMAGE_VARIATIONS:
            name = self._variation_name(variation)
            try:
                image = ImageFile(self.storage.open(name), name)
                try:
                    image.path = self.storage.path(image.name)
                except:
                    image.path = None
                image.url = self.storage.url(image.name)
                return image
            except IOError:
                pass
        raise AttributeError

    def _variation_name(self, variation):
        root, ext = os.path.splitext(self.name)
        return root + '_' + variation + ext

    def _create_variations(self, content):
        for variation, transforms in IMAGE_VARIATIONS.iteritems():
            content.seek(0)
            io = cStringIO.StringIO()
            image = Transforms()
            image.open(content)
            for transform in transforms:
                method_name, kwargs = transform
                if method_name == 'save':
                    kwargs['path'] = io
                method = getattr(image, method_name)
                method(**kwargs)
            if not 'save' in transforms:
                image.save(io)
            image_file = ContentFile(io.getvalue())
            self.storage.save(self._variation_name(variation), image_file)

class ImageVariationsField(ImageField):
    attr_class = ImageVariationsFieldFile
<|endoftext|>


<commit_before>import collections

from .base import Base
from .meta import Attr

#-------------------------------------------------------------------------------
# Constants

_LIST = '_list'

#-------------------------------------------------------------------------------
# ListWrapper

class ListWrapper(Base):
    _attrs = dict(_list = Attr(list, doc='The wrapped list'))
    _opts = dict(max_len = None,
                 min_len = None)

    def __init__(self, *args, **kwargs):
        max_len = self._opts.max_len
        ltype = self._attrs.types[_LIST]
        _args = self._opts.args

        if max_len is None or not _args:
            _list = ltype.coerce(args)
            args = ()
        else:
            _list = ltype.coerce(args[:max_len])
            args = args[max_len:]

        _list.extend(kwargs.get(_LIST, ltype.coerce([])))

        kwargs[_LIST] = _list
        super(ListWrapper, self).__init__(*args, **kwargs)

    def __iter__(self):
        return iter(self._list)

    def __len__(self):
        return len(self._list)

    def __getitem__(self, item):
        return self._list[item]

    def __setitem__(self, item, value):
        self._list[item] = value

    def __delitem__(self, item):
        del self._list[item]

    def append(self, item):
        self._list.append(item)

    def count(self, item):
        return self._list.count(item)

    def extend(self, items):
        self._list.extend(items)

    def index(self, item):
        return self._list.index(item)

    def insert(self, index, item):
        self._list.insert(index, item)

    def pop(self, index=-1):
        self._list.pop(index)

    def remove(self, item):
        self._list.remove(item)

    def reverse(self):
        self._list.reverse()

    def sort(self, *args, **kwargs):
        self._list.sort(*args, **kwargs)

    def validate(self):
        super(ListWrapper, self).validate()

        max_len = self._opts.max_len
        min_len = self._opts.min_len

        if max_len is not None:
            if len(self._list) > max_len:
                raise ValueError("can have at most %d elemsents; (got %d)"
                                 % (max_len, len(self._list)))

        if min_len is not None:
            if len(self._list) < min_len:
                raise ValueError("must have at least %d elemsents; (got %d)"
                                 % (min_len, len(self._list)))

collections.MutableSequence.register(ListWrapper)

#-------------------------------------------------------------------------------
# __all__

__all__ = ('ListWrapper',)

#-------------------------------------------------------------------------------
<commit_msg>Making ListWrapper._list internal<commit_after>import collections

from .base import Base
from .meta import Attr

#-------------------------------------------------------------------------------
# Constants

_LIST = '_list'

#-------------------------------------------------------------------------------
# ListWrapper

class ListWrapper(Base):
    _attrs = dict(_list = Attr(list, internal=True, doc='The wrapped list'))
    _opts = dict(max_len = None,
                 min_len = None)

    def __init__(self, *args, **kwargs):
        max_len = self._opts.max_len
        ltype = self._attrs.types[_LIST]
        _args = self._opts.args

        if max_len is None or not _args:
            _list = ltype.coerce(args)
            args = ()
        else:
            _list = ltype.coerce(args[:max_len])
            args = args[max_len:]

        _list.extend(kwargs.get(_LIST, ltype.coerce([])))

        kwargs[_LIST] = _list
        super(ListWrapper, self).__init__(*args, **kwargs)

    def __iter__(self):
        return iter(self._list)

    def __len__(self):
        return len(self._list)

    def __getitem__(self, item):
        return self._list[item]

    def __setitem__(self, item, value):
        self._list[item] = value

    def __delitem__(self, item):
        del self._list[item]

    def append(self, item):
        self._list.append(item)

    def count(self, item):
        return self._list.count(item)

    def extend(self, items):
        self._list.extend(items)

    def index(self, item):
        return self._list.index(item)

    def insert(self, index, item):
        self._list.insert(index, item)

    def pop(self, index=-1):
        self._list.pop(index)

    def remove(self, item):
        self._list.remove(item)

    def reverse(self):
        self._list.reverse()

    def sort(self, *args, **kwargs):
        self._list.sort(*args, **kwargs)

    def validate(self):
        super(ListWrapper, self).validate()

        max_len = self._opts.max_len
        min_len = self._opts.min_len

        if max_len is not None:
            if len(self._list) > max_len:
                raise ValueError("can have at most %d elemsents; (got %d)"
                                 % (max_len, len(self._list)))

        if min_len is not None:
            if len(self._list) < min_len:
                raise ValueError("must have at least %d elemsents; (got %d)"
                                 % (min_len, len(self._list)))

collections.MutableSequence.register(ListWrapper)

#-------------------------------------------------------------------------------
# __all__

__all__ = ('ListWrapper',)

#-------------------------------------------------------------------------------
<|endoftext|>


<commit_before>#!/usr/bin/python

import json

def storeJSON(dict, file_string):
    with open(file_string, 'w') as fp:
        json.dump(dict, fp, indent=4)

def storeTEX(dict, file_string):
    with open(file_string, 'w') as fp:
        fp.write("\\begin{tabular}\n")
        fp.write("  \\hline\n")
        fp.write("  ")
        # First we need to write out the headers
        for row in dict:
            for column in dict[row]:
                fp.write("& %s " % (column))
            fp.write("\\\\\n")
            break
        fp.write("  \\hline\n")

        # Now read all rows and output them as well
        for row in dict:
            fp.write("  %s " % (row))
            for column in dict[row]:
                fp.write("& %s " % dict[row][column])
            fp.write("\\\\\n")
        fp.write("  \\hline\n")
        fp.write("\\end{tabular}\n")
<commit_msg>Fix latex output for splitted up/down values<commit_after>#!/usr/bin/python

import json

def storeJSON(dict, file_string):
    with open(file_string, 'w') as fp:
        json.dump(dict, fp, indent=4)

def storeTEX(dict, file_string):
    with open(file_string, 'w') as fp:
        fp.write("\\begin{tabular}\n")
        fp.write("  \\hline\n")
        fp.write("  ")
        # First we need to write out the headers
        for row in dict:
            for column in dict[row]:
                fp.write("& %s " % (column))
            fp.write("\\\\\n")
            break
        fp.write("  \\hline\n")

        # Now read all rows and output them as well
        for row in dict:
            fp.write("  %s " % (row))
            for column in dict[row]:
                fp.write("& %s / %s " % (dict[row][column][0], dict[row][column][1]))
            fp.write("\\\\\n")
        fp.write("  \\hline\n")
        fp.write("\\end{tabular}\n")
<|endoftext|>


<commit_before>from dbaas_credentials.models import CredentialType
from dbaas_dnsapi.models import HOST, INSTANCE, DatabaseInfraDNSList
from dbaas_dnsapi.provider import DNSAPIProvider
from dbaas_dnsapi.utils import add_dns_record
from util import get_credentials_for, check_nslookup
from base import BaseInstanceStep

class DNSStep(BaseInstanceStep):

    def __init__(self, instance):
        super(DNSStep, self).__init__(instance)
        self.provider = DNSAPIProvider

    @property
    def credentials(self):
        return get_credentials_for(self.environment, CredentialType.DNSAPI)

    def do(self):
        raise NotImplementedError

    def undo(self):
        pass

class ChangeTTL(DNSStep):

    def __unicode__(self):
        return "Changing DNS TLL to {} minutes...".format(self.minutes)

    @property
    def minutes(self):
        raise NotImplementedError

    @property
    def seconds(self):
        return self.minutes * 60

    def do(self):
        self.provider.update_database_dns_ttl(
            self.infra, self.seconds
        )

class ChangeTTLTo5Minutes(ChangeTTL):

    minutes = 5

class ChangeTTLTo3Hours(ChangeTTL):

    minutes = 180

class ChangeEndpoint(DNSStep):

    def __unicode__(self):
        return "Changing DNS endpoint..."

    def do(self):
        for instance in self.host.instances.all():
            old_instance = instance.future_instance
            DNSAPIProvider.update_database_dns_content(
                self.infra, old_instance.dns,
                old_instance.address, instance.address
            )

            instance.dns = old_instance.dns
            old_instance.dns = old_instance.address
            old_instance.save()
            instance.save()

            if self.instance.id == instance.id:
                self.instance.dns = instance.dns

        old_host = self.host.future_host
        DNSAPIProvider.update_database_dns_content(
            self.infra, old_host.hostname,
            old_host.address, self.host.address
        )

        self.host.hostname = old_host.hostname
        old_host.hostname = old_host.address

        old_host.save()
        self.host.save()

        if self.infra.endpoint and old_host.address in self.infra.endpoint:
            self.infra.endpoint = self.infra.endpoint.replace(
                old_host.address, self.host.address
            )
            self.infra.save()

class CreateDNS(DNSStep):

    def __unicode__(self):
        return "Creating DNS..."

    @property
    def database_sufix(self):
        return {}

    @property
    def can_run(self):
        from util import get_credentials_for
        from dbaas_credentials.models import CredentialType
        credential = get_credentials_for(self.environment, CredentialType.DNSAPI)
        return bool(credential)

    def do(self):
        if self.host.hostname == self.host.address:
            self.host.hostname = add_dns_record(
                databaseinfra=self.infra,
                name=self.instance.vm_name,
                ip=self.host.address,
                type=HOST
            )
            self.host.save()

        self.instance.dns = add_dns_record(
            databaseinfra=self.infra,
            name=self.instance.vm_name,
            ip=self.instance.address,
            type=INSTANCE,
            **self.database_sufix
        )

        self.provider.create_database_dns_for_ip(
            databaseinfra=self.infra,
            ip=self.instance.address
        )

        self.instance.save()

    def undo(self):
        self.provider.remove_databases_dns_for_ip(
            databaseinfra=self.infra,
            ip=self.instance.address
        )

class RegisterDNSVip(DNSStep):

    def __unicode__(self):
        return "Registry dns for VIP..."

    @property
    def is_valid(self):
        return self.instance == self.infra.instances.first()

    def do(self):
        if not self.is_valid:
            return

        self.provider.create_database_dns(self.infra)

class CreateDNSSentinel(CreateDNS):

    @property
    def database_sufix(self):
        base = super(CreateDNSSentinel, self).database_sufix
        if not self.instance.is_database:
            base['database_sufix'] = 'sentinel'
        return base

class CheckIsReady(DNSStep):

    def __unicode__(self):
        return "Waiting for DNS..."

    def do(self):
        if self.instance.id != self.infra.instances.first().id:
            return

        check_dns = self.credentials.get_parameter_by_name('check_dns')
        if str(check_dns).lower() != 'true':
            return

        for dns in DatabaseInfraDNSList.objects.filter(databaseinfra=self.infra.id):
            if not check_nslookup(dns.dns, self.credentials.project):
                raise EnvironmentError("DNS {} is not ready".format(dns.dns))
<commit_msg>Remove can_run logic from dns step<commit_after>from dbaas_credentials.models import CredentialType
from dbaas_dnsapi.models import HOST, INSTANCE, DatabaseInfraDNSList
from dbaas_dnsapi.provider import DNSAPIProvider
from dbaas_dnsapi.utils import add_dns_record
from util import get_credentials_for, check_nslookup
from base import BaseInstanceStep

class DNSStep(BaseInstanceStep):

    def __init__(self, instance):
        super(DNSStep, self).__init__(instance)
        self.provider = DNSAPIProvider

    @property
    def credentials(self):
        return get_credentials_for(self.environment, CredentialType.DNSAPI)

    def do(self):
        raise NotImplementedError

    def undo(self):
        pass

class ChangeTTL(DNSStep):

    def __unicode__(self):
        return "Changing DNS TLL to {} minutes...".format(self.minutes)

    @property
    def minutes(self):
        raise NotImplementedError

    @property
    def seconds(self):
        return self.minutes * 60

    def do(self):
        self.provider.update_database_dns_ttl(
            self.infra, self.seconds
        )

class ChangeTTLTo5Minutes(ChangeTTL):

    minutes = 5

class ChangeTTLTo3Hours(ChangeTTL):

    minutes = 180

class ChangeEndpoint(DNSStep):

    def __unicode__(self):
        return "Changing DNS endpoint..."

    def do(self):
        for instance in self.host.instances.all():
            old_instance = instance.future_instance
            DNSAPIProvider.update_database_dns_content(
                self.infra, old_instance.dns,
                old_instance.address, instance.address
            )

            instance.dns = old_instance.dns
            old_instance.dns = old_instance.address
            old_instance.save()
            instance.save()

            if self.instance.id == instance.id:
                self.instance.dns = instance.dns

        old_host = self.host.future_host
        DNSAPIProvider.update_database_dns_content(
            self.infra, old_host.hostname,
            old_host.address, self.host.address
        )

        self.host.hostname = old_host.hostname
        old_host.hostname = old_host.address

        old_host.save()
        self.host.save()

        if self.infra.endpoint and old_host.address in self.infra.endpoint:
            self.infra.endpoint = self.infra.endpoint.replace(
                old_host.address, self.host.address
            )
            self.infra.save()

class CreateDNS(DNSStep):

    def __unicode__(self):
        return "Creating DNS..."

    @property
    def database_sufix(self):
        return {}

    def do(self):
        if self.host.hostname == self.host.address:
            self.host.hostname = add_dns_record(
                databaseinfra=self.infra,
                name=self.instance.vm_name,
                ip=self.host.address,
                type=HOST
            )
            self.host.save()

        self.instance.dns = add_dns_record(
            databaseinfra=self.infra,
            name=self.instance.vm_name,
            ip=self.instance.address,
            type=INSTANCE,
            **self.database_sufix
        )

        self.provider.create_database_dns_for_ip(
            databaseinfra=self.infra,
            ip=self.instance.address
        )

        self.instance.save()

    def undo(self):
        self.provider.remove_databases_dns_for_ip(
            databaseinfra=self.infra,
            ip=self.instance.address
        )

class RegisterDNSVip(DNSStep):

    def __unicode__(self):
        return "Registry dns for VIP..."

    @property
    def is_valid(self):
        return self.instance == self.infra.instances.first()

    def do(self):
        if not self.is_valid:
            return

        self.provider.create_database_dns(self.infra)

class CreateDNSSentinel(CreateDNS):

    @property
    def database_sufix(self):
        base = super(CreateDNSSentinel, self).database_sufix
        if not self.instance.is_database:
            base['database_sufix'] = 'sentinel'
        return base

class CheckIsReady(DNSStep):

    def __unicode__(self):
        return "Waiting for DNS..."

    def do(self):
        if self.instance.id != self.infra.instances.first().id:
            return

        check_dns = self.credentials.get_parameter_by_name('check_dns')
        if str(check_dns).lower() != 'true':
            return

        for dns in DatabaseInfraDNSList.objects.filter(databaseinfra=self.infra.id):
            if not check_nslookup(dns.dns, self.credentials.project):
                raise EnvironmentError("DNS {} is not ready".format(dns.dns))
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
"""
	app
	~~~~~~~~~~~~~~

	Provides the flask application
"""
import config

from os import environ
from json import JSONEncoder, dumps
from api import Amazon
from amazon.api import SearchException
from flask import Flask, redirect, url_for, request, make_response
from flask.ext.cache import Cache

cache = Cache()
search_cache_timeout = 1 * 60 * 60  # hours (in seconds)

def jsonify(result, status=200):
	response = make_response(dumps(result, cls=CustomEncoder))
	response.headers['Content-Type'] = 'application/json; charset=utf-8'
	response.headers['mimetype'] = 'application/json'
	response.status_code = status
	return response

def corsify(response, methods):
	allow = 'HEAD, OPTIONS'

	for m in methods:
		allow += ', %s' % m

	response.headers['Access-Control-Allow-Origin'] = '*'
	response.headers['Access-Control-Allow-Methods'] = allow
	response.headers['Access-Control-Allow-Headers'] = (
		'Origin, X-Requested-With, Content-Type, Accept')

	response.headers['Access-Control-Allow-Credentials'] = 'true'
	return response

def create_app(config_mode=None, config_file=None):
	# Create webapp instance
	app = Flask(__name__)
	cache_config = {}

	if config_mode:
		app.config.from_object(getattr(config, config_mode))
	elif config_file:
		app.config.from_pyfile(config_file)
	else:
		app.config.from_envvar('APP_SETTINGS', silent=True)

	if app.config['HEROKU']:
		cache_config['CACHE_TYPE'] = 'spreadsaslmemcachedcache'
		cache_config.setdefault('CACHE_MEMCACHED_SERVERS',
			[environ.get('MEMCACHIER_SERVERS')])
		cache_config.setdefault('CACHE_MEMCACHED_USERNAME',
			environ.get('MEMCACHIER_USERNAME'))
		cache_config.setdefault('CACHE_MEMCACHED_PASSWORD',
			environ.get('MEMCACHIER_PASSWORD'))
	elif app.config['DEBUG_MEMCACHE']:
		cache_config = {
			'CACHE_TYPE': 'memcached',
			'CACHE_MEMCACHED_SERVERS': [environ.get('MEMCACHE_SERVERS')]}
	else:
		cache_config['CACHE_TYPE'] = 'simple'

	cache.init_app(app, config=cache_config)

	@app.route('/')
	def home():
		return redirect(url_for('api'))

	@app.route('/api/')
	@app.route('%s/' % app.config['API_URL_PREFIX'])
	def api():
		return 'Welcome to the Amazon Search API!'

	@app.route('/api/search/')
	@app.route('%s/search/' % app.config['API_URL_PREFIX'])
	def search():
		args = request.args.to_dict()
		limit = int(args.get('limit', 1))
		region = args.get('region', 'US')
		amazon = Amazon(region=region)

		kwargs = {
			'Condition': 'New',
			'SearchIndex': 'All',
			'ResponseGroup': 'Medium',
		}

		kwargs.update(args)

		try:
			response = amazon.search_n(limit, **kwargs)
			result = amazon.parse(response)
			status = 200
		except SearchException as err:
			result = err.message
			status = 500

		return jsonify({'objects': result}, status)

	@app.route('/reset/', methods=['GET'])
	def reset():
		cache.clear()
		return jsonify({'objects': "Caches reset"})

	@app.after_request
	def add_cors_header(response):
		return corsify(response, app.config['API_METHODS'])

	return app

class CustomEncoder(JSONEncoder):
	def default(self, obj):
		if set(['quantize', 'year']).intersection(dir(obj)):
			return str(obj)
		elif set(['next', 'union']).intersection(dir(obj)):
			return list(obj)
		return JSONEncoder.default(self, obj)
<commit_msg>Edit route<commit_after># -*- coding: utf-8 -*-
"""
	app
	~~~~~~~~~~~~~~

	Provides the flask application
"""
import config

from os import environ
from json import JSONEncoder, dumps
from api import Amazon
from amazon.api import SearchException
from flask import Flask, redirect, url_for, request, make_response
from flask.ext.cache import Cache

cache = Cache()
search_cache_timeout = 1 * 60 * 60  # hours (in seconds)

def jsonify(result, status=200):
	response = make_response(dumps(result, cls=CustomEncoder))
	response.headers['Content-Type'] = 'application/json; charset=utf-8'
	response.headers['mimetype'] = 'application/json'
	response.status_code = status
	return response

def corsify(response, methods):
	allow = 'HEAD, OPTIONS'

	for m in methods:
		allow += ', %s' % m

	response.headers['Access-Control-Allow-Origin'] = '*'
	response.headers['Access-Control-Allow-Methods'] = allow
	response.headers['Access-Control-Allow-Headers'] = (
		'Origin, X-Requested-With, Content-Type, Accept')

	response.headers['Access-Control-Allow-Credentials'] = 'true'
	return response

def create_app(config_mode=None, config_file=None):
	# Create webapp instance
	app = Flask(__name__)
	cache_config = {}

	if config_mode:
		app.config.from_object(getattr(config, config_mode))
	elif config_file:
		app.config.from_pyfile(config_file)
	else:
		app.config.from_envvar('APP_SETTINGS', silent=True)

	if app.config['HEROKU']:
		cache_config['CACHE_TYPE'] = 'spreadsaslmemcachedcache'
		cache_config.setdefault('CACHE_MEMCACHED_SERVERS',
			[environ.get('MEMCACHIER_SERVERS')])
		cache_config.setdefault('CACHE_MEMCACHED_USERNAME',
			environ.get('MEMCACHIER_USERNAME'))
		cache_config.setdefault('CACHE_MEMCACHED_PASSWORD',
			environ.get('MEMCACHIER_PASSWORD'))
	elif app.config['DEBUG_MEMCACHE']:
		cache_config = {
			'CACHE_TYPE': 'memcached',
			'CACHE_MEMCACHED_SERVERS': [environ.get('MEMCACHE_SERVERS')]}
	else:
		cache_config['CACHE_TYPE'] = 'simple'

	cache.init_app(app, config=cache_config)

	@app.route('/')
	def home():
		return redirect(url_for('api'))

	@app.route('/api/')
	@app.route('%s/' % app.config['API_URL_PREFIX'])
	def api():
		return 'Welcome to the Amazon Search API!'

	@app.route('/api/search/')
	@app.route('%s/search/' % app.config['API_URL_PREFIX'])
	def search():
		args = request.args.to_dict()
		limit = int(args.get('limit', 1))
		region = args.get('region', 'US')
		amazon = Amazon(region=region)

		kwargs = {
			'Condition': 'New',
			'SearchIndex': 'All',
			'ResponseGroup': 'Medium',
		}

		kwargs.update(args)

		try:
			response = amazon.search_n(limit, **kwargs)
			result = amazon.parse(response)
			status = 200
		except SearchException as err:
			result = err.message
			status = 500

		return jsonify({'objects': result}, status)

	@app.route('/api/reset/')
	@app.route('%s/reset/' % app.config['API_URL_PREFIX'])
	def reset():
		cache.clear()
		return jsonify({'objects': "Caches reset"})

	@app.after_request
	def add_cors_header(response):
		return corsify(response, app.config['API_METHODS'])

	return app

class CustomEncoder(JSONEncoder):
	def default(self, obj):
		if set(['quantize', 'year']).intersection(dir(obj)):
			return str(obj)
		elif set(['next', 'union']).intersection(dir(obj)):
			return list(obj)
		return JSONEncoder.default(self, obj)
<|endoftext|>


<commit_before>from datetime import datetime
import re

import discord
import aiohttp

from typing import Dict, List
from urllib.parse import quote_plus
from bs4 import BeautifulSoup as bs
from discord.ext import commands
from utils import aiohttp_wrap as aw
from utils.paginate import paginate
from utils.dict_manip import dig

class Books(commands.Cog):
    URL = "https://www.googleapis.com/books/v1"

    def __init__(self, bot, *args, **kwargs):
        self.color = 0xB38E86
        self.bot = bot
        self.session: aiohttp.ClientSession = bot.aio_session

    def make_embeds(self, books: List[dict]) -> discord.Embed:
        ret = []
        for idx, book in enumerate(books):
            volume_info = book.get("volumeInfo")
            if volume_info is None:
                return None

            categories = ", ".join(volume_info.get("categories"))
            description = volume_info.get("description")
            authors = volume_info.get("authors")
            date = volume_info.get("publishedDate")
            year = datetime.fromisoformat(date).year if date else "Unknown"
            thumbnail = dig(
                volume_info,
                "imageLinks",
                "thumbnail",
                default="https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/240/twitter/282/blue-book_1f4d8.png",
            )
            page_count = volume_info.get("pageCount")
            idents = [
                {"name": x["type"], "value": f"`{x['identifier']}`"}
                for x in book["volumeInfo"].get("industryIdentifiers", [])[:3]
            ]
            rating = volume_info.get("averageRating", "???")
            rating_count = volume_info.get("ratingsCount", 0)

            em = discord.Embed(
                description=description,
                title=volume_info["title"][:256],
                color=self.color,
                url=volume_info["canonicalVolumeLink"],
            )
            em.add_field(name="Author" if len(authors) == 1 else "Authors", value=", ".join(authors))
            em.add_field(name="Published year", value=year, inline=True)
            em.add_field(name="Categories", value=categories, inline=True)
            em.add_field(name="Page count", value=page_count)
            for id in idents:
                em.add_field(name=id["name"], value=id["value"], inline=True)
            em.add_field(name="Ratings", value=f"{rating} / 5 ({rating_count} ratings)")
            em.set_thumbnail(thumbnail)
            em.set_footer(f"{idx + 1} / {len(books)}")

            ret.append(em)

        return ret

    @commands.command(name="book", aliases=["books"])
    async def _book(self, ctx: commands.Context, *, search: str):
        resp = await aw.aio_get_json(self.session, f"{self.URL}/volumes", params={"q": search, "maxResults": 5})
        if resp is None:
            return await ctx.error("Couldn't find a matching book")

        embeds = self.make_embeds(resp["items"])
        await paginate(ctx, embeds)

def setup(bot):
    bot.add_cog(Books(bot))
<commit_msg>Fix erroneous use of args over kwargs<commit_after>from datetime import datetime
import re

import discord
import aiohttp

from typing import Dict, List
from urllib.parse import quote_plus
from bs4 import BeautifulSoup as bs
from discord.ext import commands
from utils import aiohttp_wrap as aw
from utils.paginate import paginate
from utils.dict_manip import dig

class Books(commands.Cog):
    URL = "https://www.googleapis.com/books/v1"

    def __init__(self, bot, *args, **kwargs):
        self.color = 0xB38E86
        self.bot = bot
        self.session: aiohttp.ClientSession = bot.aio_session

    def make_embeds(self, books: List[dict]) -> discord.Embed:
        ret = []
        for idx, book in enumerate(books):
            volume_info = book.get("volumeInfo")
            if volume_info is None:
                return None

            categories = ", ".join(volume_info.get("categories"))
            description = volume_info.get("description")
            authors = volume_info.get("authors")
            date = volume_info.get("publishedDate")
            year = datetime.fromisoformat(date).year if date else "Unknown"
            thumbnail = dig(
                volume_info,
                "imageLinks",
                "thumbnail",
                default="https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/240/twitter/282/blue-book_1f4d8.png",
            )
            page_count = volume_info.get("pageCount")
            idents = [
                {"name": x["type"], "value": f"`{x['identifier']}`"}
                for x in book["volumeInfo"].get("industryIdentifiers", [])[:3]
            ]
            rating = volume_info.get("averageRating", "???")
            rating_count = volume_info.get("ratingsCount", 0)

            em = discord.Embed(
                description=description,
                title=volume_info["title"][:256],
                color=self.color,
                url=volume_info["canonicalVolumeLink"],
            )
            em.add_field(name="Author" if len(authors) == 1 else "Authors", value=", ".join(authors))
            em.add_field(name="Published year", value=year, inline=True)
            em.add_field(name="Categories", value=categories, inline=True)
            em.add_field(name="Page count", value=page_count)
            for id in idents:
                em.add_field(name=id["name"], value=id["value"], inline=True)
            em.add_field(name="Ratings", value=f"{rating} / 5 ({rating_count} ratings)")
            em.set_thumbnail(url=thumbnail)
            em.set_footer(text=f"{idx + 1} / {len(books)}")

            ret.append(em)

        return ret

    @commands.command(name="book", aliases=["books"])
    async def _book(self, ctx: commands.Context, *, search: str):
        resp = await aw.aio_get_json(self.session, f"{self.URL}/volumes", params={"q": search, "maxResults": 5})
        if resp is None:
            return await ctx.error("Couldn't find a matching book")

        embeds = self.make_embeds(resp["items"])
        await paginate(ctx, embeds)

def setup(bot):
    bot.add_cog(Books(bot))
<|endoftext|>


<commit_before>import unittest
from main import (
    fonts_on_google,
    _font_exists,
    _convert_camelcase
    )

class GoogleFontsApi(unittest.TestCase):
    def setUp(self):
        self.fallback_url = 'https://fonts.googleapis.com/css?family=Inconsolata:400'

    def test_font_exists(self):
        self.assertEqual(_font_exists(self.fallback_url), True)

    def test_convert_camelcase(self):
        sngl_name = 'Anaheim'
        dbl_name = 'BadScript'
        self.assertEqual(_convert_camelcase(sngl_name, '+'), 'Anaheim')
        self.assertEqual(_convert_camelcase(dbl_name, '+'), 'Bad+Script')
        self.assertEqual(_convert_camelcase(dbl_name), 'Bad Script')

    def test_get_google_fontface_url_from_local_font(self):
        """Test we can convert a local font's path into a compatible url for
        the Google fonts api"""
        inconsolata_local = [('path/Inconsolata-Regular.ttf', 'Inconsolata-Regular', 'Inconsolata-Regular-new')]
        fonts_on_google(inconsolata_local)
        self.assertEqual(fonts_on_google(inconsolata_local),
                         [('Inconsolata',
                           'Inconsolata',
                           'Inconsolata-400-normal-online',
                           '400',
                           'normal',
                           self.fallback_url)])

if __name__ == '__main__':
    unittest.main()
<commit_msg>test.py: refactored tests for v0.200 release<commit_after>import unittest
from main import (
    _convert_camelcase,
    URL_PREFIX,
    url_200_response,
    )

class GoogleFontsApi(unittest.TestCase):

    def test_api_url_still_works(self):
        """Check the download api url is still operational"""
        real_family_dl_url = url_200_response('%sInconsolata' % URL_PREFIX)
        fake_family_dl_url = url_200_response('%sThisIsAFauxFont' % URL_PREFIX)
        self.assertEqual(real_family_dl_url, True)
        self.assertEqual(fake_family_dl_url, False)

    def test_convert_camelcase(self):
        sngl_name = 'Anaheim'
        dbl_name = 'BadScript'
        self.assertEqual(_convert_camelcase(sngl_name, '+'), 'Anaheim')
        self.assertEqual(_convert_camelcase(dbl_name, '+'), 'Bad+Script')
        self.assertEqual(_convert_camelcase(dbl_name), 'Bad Script')

class GlyphTablComparisons(unittest.TestCase):

    def test_new_font_glyphs(self):
        pass

if __name__ == '__main__':
    unittest.main()
<|endoftext|>


<commit_before>import sys
import os
import argparse
import logging
import asyncio
from functools import partial
from fnmatch import fnmatch

import yaml
from shelver.provider import Provider
from shelver.build import Coordinator
from shelver.errors import ShelverError

logger = logging.getLogger('shelver.cli')

def _filter_img(patterns, image):
    return any(fnmatch(image.name, pat) for pat in patterns)

def _build_done(image, fut):
    try:
        artifacts = fut.result()
        for artifact in artifacts:
            print('Built succeeded for image {}: {}'.format(
                image.name, artifact))
    except ShelverError as e:
        print('Build failed for image {}: {}'.format(
            image.name, e))
    except Exception:
        logger.exception('Build failed with unexpected exception')

@asyncio.coroutine
def do_build(opts, provider, config):
    if not opts.images:
        opts.images = ['*']

    loop = provider._loop
    registry = provider.make_registry(config)
    yield from registry.load_existing_artifacts()
    builder = yield from provider.make_builder(
        registry,
        base_dir=opts.base_dir,
        tmp_dir=opts.tmp_dir,
        cache_dir=opts.cache_dir,
        keep_tmp=opts.keep_tmp,
        packer_cmd=opts.packer_cmd)

    try:
        coordinator = builder.make_coordinator(max_builds=opts.max_builds)
        for name, image in registry.images.items():
            if not _filter_img(opts.images, image):
                continue

            artifact = registry.get_image_artifact(image, default=None)
            if artifact:
                continue

            logger.info('Scheduling build for %s', name)
            build = coordinator.get_or_run_build(image)
            build.add_done_callback(partial(_build_done, image))

        results = yield from coordinator.run_all()
        for result in results:
            if result.cancelled() or result.exception():
                return 1

        return 0
    finally:
        builder.close()

@asyncio.coroutine
def do_list(opts, provider, config):
    registry = provider.make_registry(config)
    yield from registry.load_existing_artifacts()

    images = sorted(registry.images.items())
    artifacts = set(registry.artifacts.values())

    for name, image in images:
        print('==', name)
        for version, artifact in registry.get_image_versions(image):
            artifacts.remove(artifact)
            print('{}: {}'.format(version, artifact))

        print()

    print('==', 'Unmanaged artifacts')
    for artifact in sorted(artifacts, key=lambda a: a.name):
        print(artifact)

    return 0

def main():
    logging.basicConfig(level=logging.INFO)
    logging.getLogger('shelver').setLevel(logging.DEBUG)
    logging.getLogger('botocore').setLevel(logging.WARN)
    logging.getLogger('boto3').setLevel(logging.WARN)

    args = argparse.ArgumentParser(
        description='Cloud compute image continuous delivery assistant for '
                    'Packer')
    args.add_argument(
        '-p', '--provider', metavar='PROVIDER',
        choices=Provider.available_names())
    args.add_argument(
        '-d', '--base-dir', metavar='DIR',
        help='Base directory to make paths in the config relative to. '
             'If not specified, the directory containing the config will be '
             'used')
    args.add_argument(
        '-c', '--config', metavar='FILE', default='shelver.yml',
        help='Path to configuration file in YAML/Jinja format. '
             'YAML values are templated using Jinja instead of the whole file')
    args.add_argument(
        '-r', '--region', metavar='region',
        help='Use non-default region for providers that support it')
    args.add_argument(
        '-j', '--max-builds', metavar='JOBS', type=int,
        help='Maximum number of concurrent builds to run')
    args.add_argument(
        '--tmp-dir', metavar='DIR',
        help='Override path to store temporary files into')
    args.add_argument(
        '--cache-dir', metavar='DIR',
        help='Override path to store cached files into (between builds)')
    args.add_argument(
        '--keep-tmp', action='store_true', default=False,
        help='Do not delete temporary files after finishing (for debugging)')
    args.add_argument(
        '--packer-cmd', default='packer',
        help='Path to packer executable')

    cmds = args.add_subparsers(dest='command')

    build_cmd = cmds.add_parser('build')
    build_cmd.add_argument(
        'images', nargs='*',
        help='Names of images to build from the config. Can use wildcard '
             'patterns. Images that serve as bases for other images will be '
             'automatically included if any image that requires them is '
             'included, wether they match the patterns or not')

    cmds.add_parser('list')

    ##

    opts = args.parse_args()
    if not opts.base_dir:
        opts.base_dir = os.path.dirname(os.path.abspath(opts.config))

    with open(opts.config, 'rb') as f:
        config = yaml.safe_load(f)

    provider_config = config.pop('provider', {})
    config_provider_name = provider_config.pop('name', None)

    opts.provider = opts.provider or config_provider_name
    if not opts.provider:
        print('Error: no provider specified in command line or config file',
              file=sys.stderr)
        return 1

    ##

    loop = asyncio.get_event_loop()
    provider = Provider.new(opts.provider, config=provider_config, loop=loop)

    if opts.command == 'list':
        run = do_list(opts, provider, config)
    elif opts.command == 'build':
        run = do_build(opts, provider, config)
    else:
        print("Error: invalid command '{}'".format(opts.command),
              file=sys.stderr)
        return 1

    run_fut = asyncio.ensure_future(run, loop=loop)
    try:
        status = loop.run_until_complete(run_fut)
        return status
    except KeyboardInterrupt:
        print('Received interrupt, stopping tasks', file=sys.stderr)
        run_fut.cancel()
        loop.run_forever()
        run_fut.exception()
        return 1
    except Exception:
        logger.exception('Unexpected exception')
        run_fut.cancel()
        loop.run_forever()
        run_fut.exception()
        return 1
<commit_msg>cli: refactor to use LoopManager<commit_after>import sys
import os
import argparse
import logging
import asyncio
from functools import partial
from fnmatch import fnmatch
from signal import SIGHUP, SIGINT, SIGTERM

import yaml
from shelver.image import Image
from shelver.provider import Provider
from shelver.build import Coordinator
from shelver.errors import ShelverError
from shelver.util import LoopManager

logger = logging.getLogger('shelver.cli')

def _filter_img(patterns, image):
    return any(fnmatch(image.name, pat) for pat in patterns)

def _build_done(image, fut):
    try:
        artifacts = fut.result()
        for artifact in artifacts:
            print('Build succeeded for image {}: {}'.format(
                image.name, artifact))
    except ShelverError as e:
        print('Build failed for image {}: {}'.format(
            image.name, e))
    except Exception:
        logger.exception('Build failed with unexpected exception')

@asyncio.coroutine
def do_build(opts, provider, config):
    if not opts.images:
        opts.images = ['*']

    registry = provider.make_registry(Image.parse_config(config))
    yield from registry.load_existing_artifacts()

    with provider.make_builder(registry,
                               base_dir=opts.base_dir,
                               tmp_dir=opts.tmp_dir,
                               cache_dir=opts.cache_dir,
                               keep_tmp=opts.keep_tmp,
                               packer_cmd=opts.packer_cmd) as builder:
        coordinator = builder.make_coordinator(max_builds=opts.max_builds)
        for name, image in registry.images.items():
            if not _filter_img(opts.images, image):
                continue

            artifact = registry.get_image_artifact(image, default=None)
            if artifact:
                continue

            logger.info('Scheduling build for %s', name)
            build = coordinator.get_or_run_build(image)
            build.add_done_callback(partial(_build_done, image))

        yield from coordinator.run_all()

@asyncio.coroutine
def do_list(opts, provider, config):
    registry = provider.make_registry(Image.parse_config(config))
    yield from registry.load_existing_artifacts()

    images = sorted(registry.images.items())
    artifacts = set(registry.artifacts.values())

    for name, image in images:
        print('==', name)
        for version, artifact in registry.get_image_versions(image):
            artifacts.remove(artifact)
            print('{}: {}'.format(version, artifact))

        print()

    print('==', 'Unmanaged artifacts')
    for artifact in sorted(artifacts, key=lambda a: a.name):
        print(artifact)

def parse_args():
    args = argparse.ArgumentParser(
        description='Cloud compute image continuous delivery assistant for '
                    'Packer')
    args.add_argument(
        '-p', '--provider', metavar='PROVIDER',
        choices=Provider.available_names())
    args.add_argument(
        '-d', '--base-dir', metavar='DIR',
        help='Base directory to make paths in the config relative to. '
             'If not specified, the directory containing the config will be '
             'used')
    args.add_argument(
        '-c', '--config', metavar='FILE', default='shelver.yml',
        help='Path to configuration file in YAML/Jinja format. '
             'YAML values are templated using Jinja instead of the whole file')
    args.add_argument(
        '-r', '--region', metavar='region',
        help='Use non-default region for providers that support it')
    args.add_argument(
        '-j', '--max-builds', metavar='JOBS', type=int,
        help='Maximum number of concurrent builds to run')
    args.add_argument(
        '--tmp-dir', metavar='DIR',
        help='Override path to store temporary files into')
    args.add_argument(
        '--cache-dir', metavar='DIR',
        help='Override path to store cached files into (between builds)')
    args.add_argument(
        '--keep-tmp', action='store_true', default=False,
        help='Do not delete temporary files after finishing (for debugging)')
    args.add_argument(
        '--packer-cmd', default='packer',
        help='Path to packer executable')

    cmds = args.add_subparsers(dest='command')

    build_cmd = cmds.add_parser('build')
    build_cmd.add_argument(
        'images', nargs='*',
        help='Names of images to build from the config. Can use wildcard '
             'patterns. Images that serve as bases for other images will be '
             'automatically included if any image that requires them is '
             'included, wether they match the patterns or not')
    cmds.add_parser('list')

    return args.parse_args()

def main():
    logging.basicConfig(level=logging.INFO)
    logging.getLogger('shelver').setLevel(logging.DEBUG)
    logging.getLogger('botocore').setLevel(logging.WARN)
    logging.getLogger('boto3').setLevel(logging.WARN)

    opts = parse_args()
    if not opts.base_dir:
        opts.base_dir = os.path.dirname(os.path.abspath(opts.config))

    with open(opts.config, 'rb') as f:
        config = yaml.safe_load(f)

    provider_config = config.pop('provider', {})
    config_provider_name = provider_config.pop('name', None)

    opts.provider = opts.provider or config_provider_name
    if not opts.provider:
        print('Error: no provider specified in command line or config file',
              file=sys.stderr)
        return 1

    ##

    loop = asyncio.get_event_loop()
    with LoopManager(loop):
        provider = Provider.new(opts.provider, config=provider_config, loop=loop)
        if opts.command == 'list':
            run = do_list(opts, provider, config)
        elif opts.command == 'build':
            run = do_build(opts, provider, config)
        else:
            print("Error: invalid command '{}'".format(opts.command),
                  file=sys.stderr)
            return 1

        run_fut = asyncio.ensure_future(run, loop=loop)
        try:
            loop.run_until_complete(run_fut)
            return 0
        except ShelverError as e:
            # Already logged in the _build_done callback
            return 1
        except Exception as e:
            logger.exception('Unexpected exception')
            return 1
<|endoftext|>


<commit_before>from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from __future__ import print_function

from copy import deepcopy
import functools
import io
import os
import zipfile

from .package import Package
from .. import tech
from . import layouts
from . import meta

# technology modules
timestamp = tech.timestamp
securehash = tech.securehash
persistence = tech.persistence
path = tech.fs

class Archive(Package):

    def __init__(self, filename):
        self.archive_filename = filename
        self.zipfile = None
        self._meta = self._load_meta()

    def __zipfile_user(method):
        # method is called with the zipfile opened
        @functools.wraps(method)
        def f(self, *args, **kwargs):
            if self.zipfile:
                return method(self, *args, **kwargs)
            try:
                with zipfile.ZipFile(self.archive_filename) as self.zipfile:
                    return method(self, *args, **kwargs)
            finally:
                self.zipfile = None
        return f

    def export(self, exported_archive_path):
        '''
        I pack my content (everything!) as a zip-Archive to requested location.
        '''
        # FIXME: Implement Archive.export
        pass

    # -
    @property
    @__zipfile_user
    def is_valid(self):
        # FIXME verify checksums!
        '''
        verify, that
        - all files under code, data, meta are present in the checksums
          file and they match their checksums
          (can extra files be allowed in the archive?)
        - the pkgmeta file is valid
            - has package uuid
            - has timestamp
            - has unofficial package name
            - has inputs (even if empty)
        '''
        return all(self._checks())

    def _checks(self):
        yield meta.PACKAGE in self.meta
        yield meta.PACKAGE_TIMESTAMP in self.meta
        yield meta.INPUTS in self.meta
        yield meta.DEFAULT_NAME in self.meta
        # verify package creation time
        read_time = timestamp.time_from_timestamp
        now = read_time(timestamp.timestamp())
        pkgtime = read_time(self.meta[meta.PACKAGE_TIMESTAMP])
        yield pkgtime < now
        yield self._check_extra_files() is None
        yield self._check_checksums() is None

    @__zipfile_user
    def _check_extra_files(self):
        data_dir_prefix = layouts.Archive.DATA + '/'
        code_dir_prefix = layouts.Archive.CODE + '/'
        checksums = self.checksums
        # check that there are no extra files
        for name in self.zipfile.namelist():
            is_data = name.startswith(data_dir_prefix)
            is_code = name.startswith(code_dir_prefix)
            if is_data or is_code:
                if name not in checksums:
                    # unexpected extra file!
                    return name

    @__zipfile_user
    def _check_checksums(self):
        for name, hash in self.checksums.items():
            try:
                info = self.zipfile.getinfo(name)
            except KeyError:
                return name
            archived_hash = securehash.file(
                self.zipfile.open(info), info.file_size)
            if hash != archived_hash:
                return name

    @property
    @__zipfile_user
    def checksums(self):
        with self.zipfile.open(layouts.Archive.CHECKSUMS) as f:
            return persistence.load(io.TextIOWrapper(f, encoding='utf-8'))

    @property
    @__zipfile_user
    def version(self):
        zipinfo = self.zipfile.getinfo(layouts.Archive.CHECKSUMS)
        with self.zipfile.open(zipinfo) as f:
            return securehash.file(f, zipinfo.file_size)

    @property
    def uuid(self):
        return self._meta[meta.PACKAGE]

    @property
    def timestamp_str(self):
        return self._meta[meta.PACKAGE_TIMESTAMP]

    @property
    def meta(self):
        # create a copy, so that returned meta can be modified without causing
        # harm to this Archive instance
        return deepcopy(self._meta)

    # -
    @__zipfile_user
    def _load_meta(self):
        with self.zipfile.open(layouts.Archive.PKGMETA) as f:
            return persistence.load(io.TextIOWrapper(f, encoding='utf-8'))

    @__zipfile_user
    def extract_file(self, zip_path, destination):
        '''Extract zip_path from zipfile to destination'''

        assert not os.path.exists(destination)

        with path.temp_dir(destination.parent) as unzip_dir:
            self.zipfile.extract(zip_path, unzip_dir)
            os.rename(unzip_dir / zip_path, destination)

    @__zipfile_user
    def extract_dir(self, zip_dir, destination):
        '''
        Extract all files from zipfile under zip_dir to destination.
        '''

        assert not os.path.exists(destination)

        zip_dir_prefix = zip_dir + '/'
        filelist = [
            name
            for name in self.zipfile.namelist()
            if name.startswith(zip_dir_prefix)
        ]

        if filelist:
            with path.temp_dir(destination.parent) as unzip_dir:
                self.zipfile.extractall(unzip_dir, filelist)
                os.rename(unzip_dir / zip_dir, destination)
        else:
            path.ensure_directory(destination)

    def unpack_code_to(self, destination):
        self.extract_dir(layouts.Archive.CODE, destination)

    def unpack_data_to(self, destination):
        self.extract_dir(layouts.Archive.DATA, destination)

    def unpack_meta_to(self, workspace):
        workspace.meta = self.meta
<commit_msg>clean up Archive.is_valid<commit_after>from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from __future__ import print_function

from copy import deepcopy
import functools
import io
import os
import zipfile

from .package import Package
from .. import tech
from . import layouts
from . import meta

# technology modules
timestamp = tech.timestamp
securehash = tech.securehash
persistence = tech.persistence
path = tech.fs

class Archive(Package):

    def __init__(self, filename):
        self.archive_filename = filename
        self.zipfile = None
        self._meta = self._load_meta()

    def __zipfile_user(method):
        # method is called with the zipfile opened
        @functools.wraps(method)
        def f(self, *args, **kwargs):
            if self.zipfile:
                return method(self, *args, **kwargs)
            try:
                with zipfile.ZipFile(self.archive_filename) as self.zipfile:
                    return method(self, *args, **kwargs)
            finally:
                self.zipfile = None
        return f

    def export(self, exported_archive_path):
        '''
        I pack my content (everything!) as a zip-Archive to requested location.
        '''
        # FIXME: Implement Archive.export
        pass

    # -
    @property
    @__zipfile_user
    def is_valid(self):
        '''
        verify, that
        - all files under code, data, meta are present in the checksums
          file and they match their checksums (extra files are allowed
          in the archive, but not as data or code files)
        - the pkgmeta file is valid
            - has package uuid
            - has timestamp
            - has unofficial package name
            - has inputs (even if empty)
        '''
        return all(self._checks())

    def _checks(self):
        yield self._has_well_formed_meta()
        yield self._package_creation_time_is_in_the_past()
        yield self._extra_file() is None
        yield self._file_failing_checksum() is None

    def _has_well_formed_meta(self):
        m = self.meta
        keys = (
            meta.PACKAGE,
            meta.PACKAGE_TIMESTAMP,
            meta.INPUTS,
            meta.DEFAULT_NAME)
        return all(key in m for key in keys)

    def _package_creation_time_is_in_the_past(self):
        read_time = timestamp.time_from_timestamp
        now = read_time(timestamp.timestamp())
        pkgtime = read_time(self.meta[meta.PACKAGE_TIMESTAMP])
        return pkgtime < now

    @__zipfile_user
    def _extra_file(self):
        data_dir_prefix = layouts.Archive.DATA + '/'
        code_dir_prefix = layouts.Archive.CODE + '/'
        checksums = self.checksums
        # check that there are no extra files
        for name in self.zipfile.namelist():
            is_data = name.startswith(data_dir_prefix)
            is_code = name.startswith(code_dir_prefix)
            if is_data or is_code:
                if name not in checksums:
                    # unexpected extra file!
                    return name

    @__zipfile_user
    def _file_failing_checksum(self):
        for name, hash in self.checksums.items():
            try:
                info = self.zipfile.getinfo(name)
            except KeyError:
                return name
            archived_hash = securehash.file(
                self.zipfile.open(info), info.file_size)
            if hash != archived_hash:
                return name

    @property
    @__zipfile_user
    def checksums(self):
        with self.zipfile.open(layouts.Archive.CHECKSUMS) as f:
            return persistence.load(io.TextIOWrapper(f, encoding='utf-8'))

    @property
    @__zipfile_user
    def version(self):
        zipinfo = self.zipfile.getinfo(layouts.Archive.CHECKSUMS)
        with self.zipfile.open(zipinfo) as f:
            return securehash.file(f, zipinfo.file_size)

    @property
    def uuid(self):
        return self._meta[meta.PACKAGE]

    @property
    def timestamp_str(self):
        return self._meta[meta.PACKAGE_TIMESTAMP]

    @property
    def meta(self):
        # create a copy, so that returned meta can be modified without causing
        # harm to this Archive instance
        return deepcopy(self._meta)

    # -
    @__zipfile_user
    def _load_meta(self):
        with self.zipfile.open(layouts.Archive.PKGMETA) as f:
            return persistence.load(io.TextIOWrapper(f, encoding='utf-8'))

    @__zipfile_user
    def extract_file(self, zip_path, destination):
        '''Extract zip_path from zipfile to destination'''

        assert not os.path.exists(destination)

        with path.temp_dir(destination.parent) as unzip_dir:
            self.zipfile.extract(zip_path, unzip_dir)
            os.rename(unzip_dir / zip_path, destination)

    @__zipfile_user
    def extract_dir(self, zip_dir, destination):
        '''
        Extract all files from zipfile under zip_dir to destination.
        '''

        assert not os.path.exists(destination)

        zip_dir_prefix = zip_dir + '/'
        filelist = [
            name
            for name in self.zipfile.namelist()
            if name.startswith(zip_dir_prefix)
        ]

        if filelist:
            with path.temp_dir(destination.parent) as unzip_dir:
                self.zipfile.extractall(unzip_dir, filelist)
                os.rename(unzip_dir / zip_dir, destination)
        else:
            path.ensure_directory(destination)

    def unpack_code_to(self, destination):
        self.extract_dir(layouts.Archive.CODE, destination)

    def unpack_data_to(self, destination):
        self.extract_dir(layouts.Archive.DATA, destination)

    def unpack_meta_to(self, workspace):
        workspace.meta = self.meta
<|endoftext|>


<commit_before># You might want to change these
DEFAULT_PORT = 8999
OSD_DURATION = 3.0
OSD_WARNING_MESSAGE_DURATION = 5.0
NO_SECONDARY_OSD_WARNING_DURATION = 13.0
MPC_OSD_POSITION = 1  #Right corner, 1 for left
MPLAYER_OSD_LEVEL = 1
UI_TIME_FORMAT = "[%X] "
CONFIG_NAMES = [".syncplay", "syncplay.ini"]  #Syncplay searches first to last
DEFAULT_CONFIG_NAME_WINDOWS = "syncplay.ini"
DEFAULT_CONFIG_NAME_LINUX = ".syncplay"
RECENT_CLIENT_THRESHOLD = "1.3.0"  #This and higher considered 'recent' clients (no warnings)
WARN_OLD_CLIENTS = True  #Use MOTD to inform old clients to upgrade
LIST_RELATIVE_CONFIGS = True  # Print list of relative configs loaded
SHOW_CONTACT_INFO = True  # Displays dev contact details below list in GUI
SHOW_TOOLTIPS = True
WARN_ABOUT_MISSING_STRINGS = False # (If debug mode is enabled)
FALLBACK_INITIAL_LANGUAGE = "en"
FALLBACK_PUBLIC_SYNCPLAY_SERVERS = [[u'syncplay.pl:8995 (France)', u'syncplay.pl:8995'],[u'syncplay.pl:8996 (France)', u'syncplay.pl:8996'],[u'syncplay.pl:8997 (France)', u'syncplay.pl:8997'],[u'syncplay.pl:8998 (France)', u'syncplay.pl:8998'],[u'syncplay.pl:8999 (France)', u'syncplay.pl:8999']]
PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH = 10 # Seconds
PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD = 5 # Seconds (only triggered if file is paused, e.g. due to EOF)

#Overriden by config
SHOW_OSD = True  # Sends Syncplay messages to media player OSD
SHOW_OSD_WARNINGS = True  # Show warnings if playing different file, alone in room
SHOW_SLOWDOWN_OSD = True  # Show notifications of slowing down / reverting on time difference
SHOW_SAME_ROOM_OSD = True  # Show OSD notifications for events relating to room user is in
SHOW_NONCONTROLLER_OSD = False # Show OSD notifications for non-controllers in controlled rooms
SHOW_DIFFERENT_ROOM_OSD = False  # Show OSD notifications for events relating to room user is not in
SHOW_DURATION_NOTIFICATION = True
DEBUG_MODE = False

#Changing these might be ok
AUTOMATIC_UPDATE_CHECK_FREQUENCY = 7 * 86400 # Days converted into seconds
DEFAULT_REWIND_THRESHOLD = 4
MINIMUM_REWIND_THRESHOLD = 3
DEFAULT_FASTFORWARD_THRESHOLD = 5
MINIMUM_FASTFORWARD_THRESHOLD = 4
FASTFORWARD_EXTRA_TIME = 0.25
FASTFORWARD_RESET_THRESHOLD = 3.0
FASTFORWARD_BEHIND_THRESHOLD = 1.75
SEEK_THRESHOLD = 1
SLOWDOWN_RATE = 0.95
DEFAULT_SLOWDOWN_KICKIN_THRESHOLD = 1.5
MINIMUM_SLOWDOWN_THRESHOLD = 1.3
SLOWDOWN_RESET_THRESHOLD = 0.1
DIFFERENT_DURATION_THRESHOLD = 2.5
PROTOCOL_TIMEOUT = 12.5
RECONNECT_RETRIES = 10
SERVER_STATE_INTERVAL = 1
WARNING_OSD_MESSAGES_LOOP_INTERVAL = 1
AUTOPLAY_DELAY = 3.0
SYNC_ON_PAUSE = True  # Client seek to global position - subtitles may disappear on some media players

# Options for the File Switch feature:
FOLDER_SEARCH_TIMEOUT = 60.0 # Secs - How long to wait until searches in folder to update cache are aborted (may be longer than this if hard drive needs to spin up)
FOLDER_SEARCH_DOUBLE_CHECK_INTERVAL = 120.0 # Secs - Frequency of updating cache when someone is playing a file not in current cache

#Usually there's no need to adjust these
LAST_PAUSED_DIFF_THRESHOLD = 2
FILENAME_STRIP_REGEX = u"[-~_\.\[\](): ]"
CONTROL_PASSWORD_STRIP_REGEX = u"[^a-zA-Z0-9\-]"
ROOM_NAME_STRIP_REGEX = u"^(\+)(?P<roomnamebase>.*)(:)(\w{12})$"
COMMANDS_UNDO = ["u", "undo", "revert"]
COMMANDS_LIST = ["l", "list", "users"]
COMMANDS_PAUSE = ["p", "play", "pause"]
COMMANDS_ROOM = ["r", "room"]
COMMANDS_HELP = ['help', 'h', '?', '/?', r'\?']
COMMANDS_CREATE = ['c','create']
COMMANDS_AUTH = ['a','auth']
COMMANDS_TOGGLE = ['t','toggle']
MPC_MIN_VER = "1.6.4"
VLC_MIN_VERSION = "2.2.1"
VLC_INTERFACE_MIN_VERSION = "0.2.9"
VLC_LATENCY_ERROR_THRESHOLD = 2.0
MPV_UNRESPONSIVE_THRESHOLD = 60.0
CONTROLLED_ROOMS_MIN_VERSION = "1.3.0"
USER_READY_MIN_VERSION = "1.3.0"
MPC_PATHS = [
    r"c:\program files (x86)\mpc-hc\mpc-hc.exe",
    r"c:\program files\mpc-hc\mpc-hc.exe",
    r"c:\program files\mpc-hc\mpc-hc64.exe",
    r"c:\program files\media player classic - home cinema\mpc-hc.exe",
    r"c:\program files\media player classic - home cinema\mpc-hc64.exe",
    r"c:\program files (x86)\media player classic - home cinema\mpc-hc.exe",
    r"c:\program files (x86)\k-lite codec pack\media player classic\mpc-hc.exe",
    r"c:\program files\k-lite codec pack\media Player classic\mpc-hc.exe",
    r"c:\program files (x86)\combined community codec pack\mpc\mpc-hc.exe",
    r"c:\program files\combined community codec pack\mpc\mpc-hc.exe",
    r"c:\program files\mpc homecinema (x64)\mpc-hc64.exe",
]
MPLAYER_PATHS = ["mplayer2", "mplayer"]
MPV_PATHS = ["mpv", "/opt/mpv/mpv", r"c:\program files\mpv\mpv.exe", r"c:\program files\mpv-player\mpv.exe",
             r"c:\program Files (x86)\mpv\mpv.exe", r"c:\program Files (x86)\mpv-player\mpv.exe",
             "/Applications/mpv.app/Contents/MacOS/mpv"]
VLC_PATHS = [
    r"c:\program files (x86)\videolan\vlc\vlc.exe",
    r"c:\program files\videolan\vlc\vlc.exe",
    "/usr/bin/vlc",
    "/usr/bin/vlc-wrapper",
    "/Applications/VLC.app/Contents/MacOS/VLC",
    "/usr/local/bin/vlc",
    "/usr/local/bin/vlc-wrapper"
]

VLC_ICONPATH = "vlc.png"
MPLAYER_ICONPATH = "mplayer.png"
MPV_ICONPATH = "mpv.png"
MPC_ICONPATH = "mpc-hc.png"
MPC64_ICONPATH = "mpc-hc64.png"

MPV_ERROR_MESSAGES_TO_REPEAT = ['[ytdl_hook] Your version of youtube-dl is too old', '[ytdl_hook] youtube-dl failed', 'Failed to recognize file format.']

#Changing these is usually not something you're looking for
PLAYER_ASK_DELAY = 0.1
PING_MOVING_AVERAGE_WEIGHT = 0.85
MPC_OPEN_MAX_WAIT_TIME = 10
MPC_LOCK_WAIT_TIME = 0.2
MPC_RETRY_WAIT_TIME = 0.01
MPC_MAX_RETRIES = 30
MPC_PAUSE_TOGGLE_DELAY = 0.05
MPV_NEWFILE_IGNORE_TIME = 1
STREAM_ADDITIONAL_IGNORE_TIME = 10
MPV_LOCK_WAIT_TIME = 0.05
VLC_OPEN_MAX_WAIT_TIME = 15
VLC_MIN_PORT = 10000
VLC_MAX_PORT = 55000

#These are not changes you're looking for
STYLE_TABLIST = "QListWidget::item { border-style: solid; border-width: 1px; border-radius: 2px; } QListWidget::item:selected { color: black; background: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgba(242, 248, 255, 255), stop:1 rgba(208, 229, 255, 255)); border-color: #84ACDD; } QListWidget::item:!selected { border-color: transparent; } QListWidget::item:!selected:hover { color: black; background: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgba(248, 248, 248, 255), stop:1 rgba(229, 229, 229, 255)); border-color: silver; }"
STYLE_SUBCHECKBOX  = "QCheckBox, QLabel, QRadioButton {{ margin-left: 6px; padding-left: 21px; background:url('{}') left no-repeat }}" #Graphic path
STYLE_SUBLABEL  = "QCheckBox, QLabel {{ margin-left: 6px; padding-left: 16px; background:url('{}') left no-repeat }}" #Graphic path
STYLE_ERRORLABEL = "QLabel { color : black; border-style: outset; border-width: 2px; border-radius: 7px; border-color: red; padding: 2px; background: #FFAAAA; }"
STYLE_SUCCESSLABEL = "QLabel { color : black; border-style: outset; border-width: 2px; border-radius: 7px; border-color: green; padding: 2px; background: #AAFFAA; }"
STYLE_READY_PUSHBUTTON = "QPushButton { text-align: left; padding: 10px 5px 10px 5px;}"
STYLE_AUTO_PLAY_PUSHBUTTON = "QPushButton { text-align: left; padding: 5px 5px 5px 5px; }"
STYLE_NOTIFICATIONBOX = "Username { color: #367AA9; font-weight:bold; }"
STYLE_CONTACT_INFO = u"{}

" # Contact info message
STYLE_USERNAME = "color: #367AA9; font-weight:bold;"
STYLE_ERRORNOTIFICATION = "color: red;"
STYLE_DIFFERENTITEM_COLOR = 'red'
STYLE_NOFILEITEM_COLOR = 'blue'
STYLE_NOTCONTROLLER_COLOR = 'grey'

USERLIST_GUI_USERNAME_OFFSET = 21 # Pixels
USERLIST_GUI_USERNAME_COLUMN = 0
USERLIST_GUI_FILENAME_COLUMN = 3

MPLAYER_SLAVE_ARGS = ['-slave', '--hr-seek=always', '-nomsgcolor', '-msglevel', 'all=1:global=4:cplayer=4', '-af-add', 'scaletempo']
MPV_ARGS = ['--force-window', '--idle', '--hr-seek=always', '--keep-open', '--af-add=scaletempo']
MPV_SLAVE_ARGS = ['--quiet', '--input-terminal=no', '--input-file=/dev/stdin']
MPV_SLAVE_ARGS_NEW = ['--term-playing-msg=<SyncplayUpdateFile>\nANS_filename=${filename}\nANS_length=${=length}\nANS_path=${path}\n</SyncplayUpdateFile>', '--terminal=yes']
MPV_NEW_VERSION = False
VLC_SLAVE_ARGS = ['--extraintf=luaintf', '--lua-intf=syncplay', '--no-quiet', '--no-input-fast-seek',
                  '--play-and-pause', '--start-time=0']
VLC_SLAVE_NONOSX_ARGS = ['--no-one-instance', '--no-one-instance-when-started-from-file']
MPLAYER_ANSWER_REGEX = "^ANS_([a-zA-Z_-]+)=(.+)$|^(Exiting)\.\.\. \((.+)\)$"
VLC_ANSWER_REGEX = r"(?:^(?P<command>[a-zA-Z_]+)(?:\: )?(?P<argument>.*))"
UI_COMMAND_REGEX = r"^(?P<command>[^\ ]+)(?:\ (?P<parameter>.+))?"
UI_OFFSET_REGEX = r"^(?:o|offset)\ ?(?P<sign>[/+-])?(?P<time>\d{1,9}(?:[^\d\.](?:\d{1,9})){0,2}(?:\.(?:\d{1,3}))?)$"
UI_SEEK_REGEX = r"^(?:s|seek)?\ ?(?P<sign>[+-])?(?P<time>\d{1,4}(?:[^\d\.](?:\d{1,6})){0,2}(?:\.(?:\d{1,3}))?)$"
PARSE_TIME_REGEX = r'(:?(?:(?P<hours>\d+?)[^\d\.])?(?:(?P<minutes>\d+?))?[^\d\.])?(?P<seconds>\d+?)(?:\.(?P<miliseconds>\d+?))?$'
SERVER_MAX_TEMPLATE_LENGTH = 10000
PRIVACY_SENDRAW_MODE = "SendRaw"
PRIVACY_SENDHASHED_MODE = "SendHashed"
PRIVACY_DONTSEND_MODE = "DoNotSend"
UNPAUSE_IFALREADYREADY_MODE = "IfAlreadyReady"
UNPAUSE_IFOTHERSREADY_MODE = "IfOthersReady"
UNPAUSE_IFMINUSERSREADY_MODE = "IfMinUsersReady"
UNPAUSE_ALWAYS_MODE = "Always"

PRIVACY_HIDDENFILENAME = "**Hidden filename**"
INVERTED_STATE_MARKER = "*"
ERROR_MESSAGE_MARKER = "*"
LOAD_SAVE_MANUALLY_MARKER = "!"
CONFIG_NAME_MARKER = ":"
CONFIG_VALUE_MARKER = "="
USERITEM_CONTROLLER_ROLE = 0
USERITEM_READY_ROLE = 1
FILEITEM_SWITCH_ROLE = 1
FILEITEM_SWITCH_NO_SWITCH = 0
FILEITEM_SWITCH_FILE_SWITCH = 1
FILEITEM_SWITCH_STREAM_SWITCH = 2
PLAYLISTITEM_CURRENTLYPLAYING_ROLE = 3

SYNCPLAY_UPDATE_URL = u"http://syncplay.pl/checkforupdate?{}" # Params
SYNCPLAY_DOWNLOAD_URL = "http://syncplay.pl/download/"
SYNCPLAY_PUBLIC_SERVER_LIST_URL = u"http://syncplay.pl/listpublicservers?{}" # Params

PRIVATE_FILE_FIELDS = ["path"]<commit_msg>Reduce folder search double-check threshold to 65 seconds<commit_after># You might want to change these
DEFAULT_PORT = 8999
OSD_DURATION = 3.0
OSD_WARNING_MESSAGE_DURATION = 5.0
NO_SECONDARY_OSD_WARNING_DURATION = 13.0
MPC_OSD_POSITION = 1  #Right corner, 1 for left
MPLAYER_OSD_LEVEL = 1
UI_TIME_FORMAT = "[%X] "
CONFIG_NAMES = [".syncplay", "syncplay.ini"]  #Syncplay searches first to last
DEFAULT_CONFIG_NAME_WINDOWS = "syncplay.ini"
DEFAULT_CONFIG_NAME_LINUX = ".syncplay"
RECENT_CLIENT_THRESHOLD = "1.3.0"  #This and higher considered 'recent' clients (no warnings)
WARN_OLD_CLIENTS = True  #Use MOTD to inform old clients to upgrade
LIST_RELATIVE_CONFIGS = True  # Print list of relative configs loaded
SHOW_CONTACT_INFO = True  # Displays dev contact details below list in GUI
SHOW_TOOLTIPS = True
WARN_ABOUT_MISSING_STRINGS = False # (If debug mode is enabled)
FALLBACK_INITIAL_LANGUAGE = "en"
FALLBACK_PUBLIC_SYNCPLAY_SERVERS = [[u'syncplay.pl:8995 (France)', u'syncplay.pl:8995'],[u'syncplay.pl:8996 (France)', u'syncplay.pl:8996'],[u'syncplay.pl:8997 (France)', u'syncplay.pl:8997'],[u'syncplay.pl:8998 (France)', u'syncplay.pl:8998'],[u'syncplay.pl:8999 (France)', u'syncplay.pl:8999']]
PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH = 10 # Seconds
PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD = 5 # Seconds (only triggered if file is paused, e.g. due to EOF)

#Overriden by config
SHOW_OSD = True  # Sends Syncplay messages to media player OSD
SHOW_OSD_WARNINGS = True  # Show warnings if playing different file, alone in room
SHOW_SLOWDOWN_OSD = True  # Show notifications of slowing down / reverting on time difference
SHOW_SAME_ROOM_OSD = True  # Show OSD notifications for events relating to room user is in
SHOW_NONCONTROLLER_OSD = False # Show OSD notifications for non-controllers in controlled rooms
SHOW_DIFFERENT_ROOM_OSD = False  # Show OSD notifications for events relating to room user is not in
SHOW_DURATION_NOTIFICATION = True
DEBUG_MODE = False

#Changing these might be ok
AUTOMATIC_UPDATE_CHECK_FREQUENCY = 7 * 86400 # Days converted into seconds
DEFAULT_REWIND_THRESHOLD = 4
MINIMUM_REWIND_THRESHOLD = 3
DEFAULT_FASTFORWARD_THRESHOLD = 5
MINIMUM_FASTFORWARD_THRESHOLD = 4
FASTFORWARD_EXTRA_TIME = 0.25
FASTFORWARD_RESET_THRESHOLD = 3.0
FASTFORWARD_BEHIND_THRESHOLD = 1.75
SEEK_THRESHOLD = 1
SLOWDOWN_RATE = 0.95
DEFAULT_SLOWDOWN_KICKIN_THRESHOLD = 1.5
MINIMUM_SLOWDOWN_THRESHOLD = 1.3
SLOWDOWN_RESET_THRESHOLD = 0.1
DIFFERENT_DURATION_THRESHOLD = 2.5
PROTOCOL_TIMEOUT = 12.5
RECONNECT_RETRIES = 10
SERVER_STATE_INTERVAL = 1
WARNING_OSD_MESSAGES_LOOP_INTERVAL = 1
AUTOPLAY_DELAY = 3.0
SYNC_ON_PAUSE = True  # Client seek to global position - subtitles may disappear on some media players

# Options for the File Switch feature:
FOLDER_SEARCH_TIMEOUT = 60.0 # Secs - How long to wait until searches in folder to update cache are aborted (may be longer than this if hard drive needs to spin up)
FOLDER_SEARCH_DOUBLE_CHECK_INTERVAL = 65.0 # Secs - Frequency of updating cache when someone is playing a file not in current cache

#Usually there's no need to adjust these
LAST_PAUSED_DIFF_THRESHOLD = 2
FILENAME_STRIP_REGEX = u"[-~_\.\[\](): ]"
CONTROL_PASSWORD_STRIP_REGEX = u"[^a-zA-Z0-9\-]"
ROOM_NAME_STRIP_REGEX = u"^(\+)(?P<roomnamebase>.*)(:)(\w{12})$"
COMMANDS_UNDO = ["u", "undo", "revert"]
COMMANDS_LIST = ["l", "list", "users"]
COMMANDS_PAUSE = ["p", "play", "pause"]
COMMANDS_ROOM = ["r", "room"]
COMMANDS_HELP = ['help', 'h', '?', '/?', r'\?']
COMMANDS_CREATE = ['c','create']
COMMANDS_AUTH = ['a','auth']
COMMANDS_TOGGLE = ['t','toggle']
MPC_MIN_VER = "1.6.4"
VLC_MIN_VERSION = "2.2.1"
VLC_INTERFACE_MIN_VERSION = "0.2.9"
VLC_LATENCY_ERROR_THRESHOLD = 2.0
MPV_UNRESPONSIVE_THRESHOLD = 60.0
CONTROLLED_ROOMS_MIN_VERSION = "1.3.0"
USER_READY_MIN_VERSION = "1.3.0"
MPC_PATHS = [
    r"c:\program files (x86)\mpc-hc\mpc-hc.exe",
    r"c:\program files\mpc-hc\mpc-hc.exe",
    r"c:\program files\mpc-hc\mpc-hc64.exe",
    r"c:\program files\media player classic - home cinema\mpc-hc.exe",
    r"c:\program files\media player classic - home cinema\mpc-hc64.exe",
    r"c:\program files (x86)\media player classic - home cinema\mpc-hc.exe",
    r"c:\program files (x86)\k-lite codec pack\media player classic\mpc-hc.exe",
    r"c:\program files\k-lite codec pack\media Player classic\mpc-hc.exe",
    r"c:\program files (x86)\combined community codec pack\mpc\mpc-hc.exe",
    r"c:\program files\combined community codec pack\mpc\mpc-hc.exe",
    r"c:\program files\mpc homecinema (x64)\mpc-hc64.exe",
]
MPLAYER_PATHS = ["mplayer2", "mplayer"]
MPV_PATHS = ["mpv", "/opt/mpv/mpv", r"c:\program files\mpv\mpv.exe", r"c:\program files\mpv-player\mpv.exe",
             r"c:\program Files (x86)\mpv\mpv.exe", r"c:\program Files (x86)\mpv-player\mpv.exe",
             "/Applications/mpv.app/Contents/MacOS/mpv"]
VLC_PATHS = [
    r"c:\program files (x86)\videolan\vlc\vlc.exe",
    r"c:\program files\videolan\vlc\vlc.exe",
    "/usr/bin/vlc",
    "/usr/bin/vlc-wrapper",
    "/Applications/VLC.app/Contents/MacOS/VLC",
    "/usr/local/bin/vlc",
    "/usr/local/bin/vlc-wrapper"
]

VLC_ICONPATH = "vlc.png"
MPLAYER_ICONPATH = "mplayer.png"
MPV_ICONPATH = "mpv.png"
MPC_ICONPATH = "mpc-hc.png"
MPC64_ICONPATH = "mpc-hc64.png"

MPV_ERROR_MESSAGES_TO_REPEAT = ['[ytdl_hook] Your version of youtube-dl is too old', '[ytdl_hook] youtube-dl failed', 'Failed to recognize file format.']

#Changing these is usually not something you're looking for
PLAYER_ASK_DELAY = 0.1
PING_MOVING_AVERAGE_WEIGHT = 0.85
MPC_OPEN_MAX_WAIT_TIME = 10
MPC_LOCK_WAIT_TIME = 0.2
MPC_RETRY_WAIT_TIME = 0.01
MPC_MAX_RETRIES = 30
MPC_PAUSE_TOGGLE_DELAY = 0.05
MPV_NEWFILE_IGNORE_TIME = 1
STREAM_ADDITIONAL_IGNORE_TIME = 10
MPV_LOCK_WAIT_TIME = 0.05
VLC_OPEN_MAX_WAIT_TIME = 15
VLC_MIN_PORT = 10000
VLC_MAX_PORT = 55000

#These are not changes you're looking for
STYLE_TABLIST = "QListWidget::item { border-style: solid; border-width: 1px; border-radius: 2px; } QListWidget::item:selected { color: black; background: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgba(242, 248, 255, 255), stop:1 rgba(208, 229, 255, 255)); border-color: #84ACDD; } QListWidget::item:!selected { border-color: transparent; } QListWidget::item:!selected:hover { color: black; background: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgba(248, 248, 248, 255), stop:1 rgba(229, 229, 229, 255)); border-color: silver; }"
STYLE_SUBCHECKBOX  = "QCheckBox, QLabel, QRadioButton {{ margin-left: 6px; padding-left: 21px; background:url('{}') left no-repeat }}" #Graphic path
STYLE_SUBLABEL  = "QCheckBox, QLabel {{ margin-left: 6px; padding-left: 16px; background:url('{}') left no-repeat }}" #Graphic path
STYLE_ERRORLABEL = "QLabel { color : black; border-style: outset; border-width: 2px; border-radius: 7px; border-color: red; padding: 2px; background: #FFAAAA; }"
STYLE_SUCCESSLABEL = "QLabel { color : black; border-style: outset; border-width: 2px; border-radius: 7px; border-color: green; padding: 2px; background: #AAFFAA; }"
STYLE_READY_PUSHBUTTON = "QPushButton { text-align: left; padding: 10px 5px 10px 5px;}"
STYLE_AUTO_PLAY_PUSHBUTTON = "QPushButton { text-align: left; padding: 5px 5px 5px 5px; }"
STYLE_NOTIFICATIONBOX = "Username { color: #367AA9; font-weight:bold; }"
STYLE_CONTACT_INFO = u"{}

" # Contact info message
STYLE_USERNAME = "color: #367AA9; font-weight:bold;"
STYLE_ERRORNOTIFICATION = "color: red;"
STYLE_DIFFERENTITEM_COLOR = 'red'
STYLE_NOFILEITEM_COLOR = 'blue'
STYLE_NOTCONTROLLER_COLOR = 'grey'

USERLIST_GUI_USERNAME_OFFSET = 21 # Pixels
USERLIST_GUI_USERNAME_COLUMN = 0
USERLIST_GUI_FILENAME_COLUMN = 3

MPLAYER_SLAVE_ARGS = ['-slave', '--hr-seek=always', '-nomsgcolor', '-msglevel', 'all=1:global=4:cplayer=4', '-af-add', 'scaletempo']
MPV_ARGS = ['--force-window', '--idle', '--hr-seek=always', '--keep-open', '--af-add=scaletempo']
MPV_SLAVE_ARGS = ['--quiet', '--input-terminal=no', '--input-file=/dev/stdin']
MPV_SLAVE_ARGS_NEW = ['--term-playing-msg=<SyncplayUpdateFile>\nANS_filename=${filename}\nANS_length=${=length}\nANS_path=${path}\n</SyncplayUpdateFile>', '--terminal=yes']
MPV_NEW_VERSION = False
VLC_SLAVE_ARGS = ['--extraintf=luaintf', '--lua-intf=syncplay', '--no-quiet', '--no-input-fast-seek',
                  '--play-and-pause', '--start-time=0']
VLC_SLAVE_NONOSX_ARGS = ['--no-one-instance', '--no-one-instance-when-started-from-file']
MPLAYER_ANSWER_REGEX = "^ANS_([a-zA-Z_-]+)=(.+)$|^(Exiting)\.\.\. \((.+)\)$"
VLC_ANSWER_REGEX = r"(?:^(?P<command>[a-zA-Z_]+)(?:\: )?(?P<argument>.*))"
UI_COMMAND_REGEX = r"^(?P<command>[^\ ]+)(?:\ (?P<parameter>.+))?"
UI_OFFSET_REGEX = r"^(?:o|offset)\ ?(?P<sign>[/+-])?(?P<time>\d{1,9}(?:[^\d\.](?:\d{1,9})){0,2}(?:\.(?:\d{1,3}))?)$"
UI_SEEK_REGEX = r"^(?:s|seek)?\ ?(?P<sign>[+-])?(?P<time>\d{1,4}(?:[^\d\.](?:\d{1,6})){0,2}(?:\.(?:\d{1,3}))?)$"
PARSE_TIME_REGEX = r'(:?(?:(?P<hours>\d+?)[^\d\.])?(?:(?P<minutes>\d+?))?[^\d\.])?(?P<seconds>\d+?)(?:\.(?P<miliseconds>\d+?))?$'
SERVER_MAX_TEMPLATE_LENGTH = 10000
PRIVACY_SENDRAW_MODE = "SendRaw"
PRIVACY_SENDHASHED_MODE = "SendHashed"
PRIVACY_DONTSEND_MODE = "DoNotSend"
UNPAUSE_IFALREADYREADY_MODE = "IfAlreadyReady"
UNPAUSE_IFOTHERSREADY_MODE = "IfOthersReady"
UNPAUSE_IFMINUSERSREADY_MODE = "IfMinUsersReady"
UNPAUSE_ALWAYS_MODE = "Always"

PRIVACY_HIDDENFILENAME = "**Hidden filename**"
INVERTED_STATE_MARKER = "*"
ERROR_MESSAGE_MARKER = "*"
LOAD_SAVE_MANUALLY_MARKER = "!"
CONFIG_NAME_MARKER = ":"
CONFIG_VALUE_MARKER = "="
USERITEM_CONTROLLER_ROLE = 0
USERITEM_READY_ROLE = 1
FILEITEM_SWITCH_ROLE = 1
FILEITEM_SWITCH_NO_SWITCH = 0
FILEITEM_SWITCH_FILE_SWITCH = 1
FILEITEM_SWITCH_STREAM_SWITCH = 2
PLAYLISTITEM_CURRENTLYPLAYING_ROLE = 3

SYNCPLAY_UPDATE_URL = u"http://syncplay.pl/checkforupdate?{}" # Params
SYNCPLAY_DOWNLOAD_URL = "http://syncplay.pl/download/"
SYNCPLAY_PUBLIC_SERVER_LIST_URL = u"http://syncplay.pl/listpublicservers?{}" # Params

PRIVATE_FILE_FIELDS = ["path"]<|endoftext|>