<commit_before>#!/usr/bin/env python3

import sys
import tkinter as tk
import tkplot
import threading
from queue import Queue, Empty
import serial
import struct
from collections import namedtuple
import time
import csv
import math

def execute_delayed(root, generator):
    """For each yielded value wait the given amount of time (in seconds)
    without pausing the Tkinter main loop.

    See 'slowmotion' in http://effbot.org/zone/tkinter-generator-polyline.htm
    """
    try:
        root.after(int(next(generator) * 1000), execute_delayed, root, generator)
    except StopIteration:
        pass

class Status(namedtuple('Status', ['local_time', 'weight_reading'])):
    @property
    def temp(self):
        return self.weight_reading

START_TIME = time.time()

def local_time():
    return time.time() - START_TIME

class Arduino:
    def __init__(self, port, filename):
        self.filename = filename
        self.port = port
        self.status = Queue()
        self.command = Queue()
        self.thread = threading.Thread(target=self.interact, daemon=True)
        self.started = threading.Event()
        self.last_status = None
        self._power = None

    def line_status(self, line):
        weight_reading = float(line.strip())
        return Status(local_time(), weight_reading)

    def interact(self):
        with open(self.filename, 'wb') as f:
            self.serial = serial.Serial(self.port, 9600)
            try:
                self.started.set()
                while True:
                    try:
                        while True:
                            None
                            self.serial.write(self.command.get_nowait())
                    except Empty:
                        pass

                    line = self.serial.readline()
                    try:
                        status = self.line_status(line)
                    except ValueError:
                        continue
                    f.write(line)
                    f.flush()
                    self.status.put_nowait(status)
                    self.last_status = status
            finally:
                self.started.clear()

    def iter_status(self):
        assert(self.started.is_set())
        try:
            while True:
                status = self.status.get_nowait()
                yield status
        except Empty:
            pass

    def __str__(self):
        return "<{} {}>".format(self.__class__.__name__, self.last_status if self.started.is_set() else '(stopped)')

    @property
    def calibrationWeight(self):
        assert(self.started.is_set())
        return self._power

    @calibrationWeight.setter
    def calibrationWeight(self, calibrationWeight):
        assert(self.started.is_set())
        assert(0 <= calibrationWeight <= 2**24)
        command = struct.pack('4sc', str.encode(str(int(calibrationWeight))), b'\n')
        self.command.put(command)

    def start(self):
        self.thread.start()
        self.started.wait()

class HeatPlot(tkplot.TkPlot):
    def __init__(self, root):
        tkplot.TkPlot.__init__(self, root, (9, 6))

        self.plot = self.figure.add_subplot(111)
        self.plot.set_xlabel("Time (s)")
        self.plot.set_ylabel("Weight (g)")
        self.plot.set_xlim(0, 1)
        self.plot.set_ylim(0, 110)
        self.weight_reading_line, = self.plot.plot([], [], label="Weight, g")
        self.plot.legend(handles=[self.weight_reading_line], bbox_to_anchor=(0.3, 1))
        self.figure.tight_layout()

    def update(self, status):
        time = [s.local_time for s in status]
        weight_reading = [s.weight_reading for s in status]

        if time:
            self.plot.set_xlim(min(time), max(time))
            self.plot.set_ylim(0, max(110, round(max(weight_reading) / 50.0 + 0.5) * 50 + 10))
            self.weight_reading_line.set_xdata(time)
            self.weight_reading_line.set_ydata(weight_reading)
            self.figure.canvas.draw()

class Krosnis:
    def __init__(self, root, port, experiment):
        self.root = root
        self.root.title("Scales - {}".format(experiment))
        self.experiment = experiment

        self.update_period = 1.0

        self.plot = HeatPlot(self.root)
        self.plot.pack(fill=tk.BOTH, expand=1)

        self.toolbar = tk.Frame(self.root)
        self.toolbar.pack(fill=tk.X)
        self.label = tk.Label(self.toolbar)
        self.label.pack(side=tk.RIGHT, fill=tk.BOTH, expand=1)

        self.power_val = tk.StringVar()
        self.power_val.set('0')
        self.power = tk.Entry(self.toolbar, textvariable=self.power_val)
        self.power.bind('<Return>', self.set_calibrationWeight)
        self.power.pack(side=tk.LEFT)
        self.power.focus_set()
        self.set_power = tk.Button(self.toolbar, text='Set weight', command=self.set_calibrationWeight)
        self.set_power.pack(side=tk.LEFT)

#        self.setpoint_val = tk.StringVar()
#        self.setpoint_val.set('0.0')
#        self.setpoint = tk.Entry(self.toolbar, textvariable=self.setpoint_val)
#        self.setpoint.bind('<Return>', self.set_setpoint)
#        self.setpoint.pack(side=tk.LEFT)
#        self.setpoint.focus_set()
#        self.set_setpoint = tk.Button(self.toolbar, text='Set temperature', command=self.set_setpoint)
#        self.set_setpoint.pack(side=tk.LEFT)

        self.arduino = Arduino(port, "experiments/{}_raw.csv".format(experiment))
        self.every_status = []
        self.th0 = 0

    def set_status(self, status):
        self.label.config(text=status)

    def set_calibrationWeight(self, event=None):
        self.arduino.calibrationWeight = float(self.power_val.get())

    # def set_setpoint(self, event=None):
    #     self.arduino.setpoint = float(self.setpoint_val.get())

    def start(self):
        _self = self
        def shell():
            self = _self
        threading.Thread(target=shell, daemon=True).start()
        execute_delayed(self.root, self.sample())

    def time_deviation(self):
        if self.every_status:
            t0 = self.every_status[0].time
            t0_local = self.every_status[0].local_time
            t_sum = 0
            for s in self.every_status:
                t_sum += (s.time - t0) - (s.local_time - t0_local)
            return t_sum / len(self.every_status)
        else:
            return 0

    def control(self):
        pass

    def sample(self):
        self.arduino.start()
        with open("experiments/{}.csv".format(self.experiment), 'w') as f:
            csvf = csv.writer(f)
            csvf.writerow(Status._fields)
            while True:
                try:
                    for s in self.arduino.iter_status():
                        if self.th0 is None:
                            self.th0 = s.temp
                        csvf.writerow(s)
                        self.set_status(str(s))
                        self.every_status.append(s)
                    f.flush()
                    self.plot.update(self.every_status)
                    self.control()
                    yield self.update_period
                except Exception as e:
                    print(e)

def run(port, experiment):
    root = tk.Tk()
    root.geometry("1000x700")
    win = Krosnis(root, port, experiment)
    win.start()
    tk.mainloop()

if __name__ == "__main__":
    run(sys.argv[1], sys.argv[2])
<commit_msg>Make line thickness thicker<commit_after>#!/usr/bin/env python3

import sys
import tkinter as tk
import tkplot
import threading
from queue import Queue, Empty
import serial
import struct
from collections import namedtuple
import time
import csv
import math

def execute_delayed(root, generator):
    """For each yielded value wait the given amount of time (in seconds)
    without pausing the Tkinter main loop.

    See 'slowmotion' in http://effbot.org/zone/tkinter-generator-polyline.htm
    """
    try:
        root.after(int(next(generator) * 1000), execute_delayed, root, generator)
    except StopIteration:
        pass

class Status(namedtuple('Status', ['local_time', 'weight_reading'])):
    @property
    def temp(self):
        return self.weight_reading

START_TIME = time.time()

def local_time():
    return time.time() - START_TIME

class Arduino:
    def __init__(self, port, filename):
        self.filename = filename
        self.port = port
        self.status = Queue()
        self.command = Queue()
        self.thread = threading.Thread(target=self.interact, daemon=True)
        self.started = threading.Event()
        self.last_status = None
        self._power = None

    def line_status(self, line):
        weight_reading = float(line.strip())
        return Status(local_time(), weight_reading)

    def interact(self):
        with open(self.filename, 'wb') as f:
            self.serial = serial.Serial(self.port, 9600)
            try:
                self.started.set()
                while True:
                    try:
                        while True:
                            None
                            self.serial.write(self.command.get_nowait())
                    except Empty:
                        pass

                    line = self.serial.readline()
                    try:
                        status = self.line_status(line)
                    except ValueError:
                        continue
                    f.write(line)
                    f.flush()
                    self.status.put_nowait(status)
                    self.last_status = status
            finally:
                self.started.clear()

    def iter_status(self):
        assert(self.started.is_set())
        try:
            while True:
                status = self.status.get_nowait()
                yield status
        except Empty:
            pass

    def __str__(self):
        return "<{} {}>".format(self.__class__.__name__, self.last_status if self.started.is_set() else '(stopped)')

    @property
    def calibrationWeight(self):
        assert(self.started.is_set())
        return self._power

    @calibrationWeight.setter
    def calibrationWeight(self, calibrationWeight):
        assert(self.started.is_set())
        assert(0 <= calibrationWeight <= 2**24)
        command = struct.pack('4sc', str.encode(str(int(calibrationWeight))), b'\n')
        self.command.put(command)

    def start(self):
        self.thread.start()
        self.started.wait()

class HeatPlot(tkplot.TkPlot):
    def __init__(self, root):
        tkplot.TkPlot.__init__(self, root, (9, 6))

        self.plot = self.figure.add_subplot(111)
        self.plot.set_xlabel("Time (s)")
        self.plot.set_ylabel("Weight (g)")
        self.plot.set_xlim(0, 1)
        self.plot.set_ylim(0, 110)
        self.weight_reading_line, = self.plot.plot([], [], label="Weight, g", linewidth=4.0)
        self.plot.legend(handles=[self.weight_reading_line], bbox_to_anchor=(0.3, 1))
        self.figure.tight_layout()

    def update(self, status):
        time = [s.local_time for s in status]
        weight_reading = [s.weight_reading for s in status]

        if time:
            self.plot.set_xlim(min(time), max(time))
            self.plot.set_ylim(0, max(110, round(max(weight_reading) / 50.0 + 0.5) * 50 + 10))
            self.weight_reading_line.set_xdata(time)
            self.weight_reading_line.set_ydata(weight_reading)
            self.figure.canvas.draw()

class Krosnis:
    def __init__(self, root, port, experiment):
        self.root = root
        self.root.title("Scales - {}".format(experiment))
        self.experiment = experiment

        self.update_period = 1.0

        self.plot = HeatPlot(self.root)
        self.plot.pack(fill=tk.BOTH, expand=1)

        self.toolbar = tk.Frame(self.root)
        self.toolbar.pack(fill=tk.X)
        self.label = tk.Label(self.toolbar)
        self.label.pack(side=tk.RIGHT, fill=tk.BOTH, expand=1)

        self.power_val = tk.StringVar()
        self.power_val.set('0')
        self.power = tk.Entry(self.toolbar, textvariable=self.power_val)
        self.power.bind('<Return>', self.set_calibrationWeight)
        self.power.pack(side=tk.LEFT)
        self.power.focus_set()
        self.set_power = tk.Button(self.toolbar, text='Set weight', command=self.set_calibrationWeight)
        self.set_power.pack(side=tk.LEFT)

#        self.setpoint_val = tk.StringVar()
#        self.setpoint_val.set('0.0')
#        self.setpoint = tk.Entry(self.toolbar, textvariable=self.setpoint_val)
#        self.setpoint.bind('<Return>', self.set_setpoint)
#        self.setpoint.pack(side=tk.LEFT)
#        self.setpoint.focus_set()
#        self.set_setpoint = tk.Button(self.toolbar, text='Set temperature', command=self.set_setpoint)
#        self.set_setpoint.pack(side=tk.LEFT)

        self.arduino = Arduino(port, "experiments/{}_raw.csv".format(experiment))
        self.every_status = []
        self.th0 = 0

    def set_status(self, status):
        self.label.config(text=status)

    def set_calibrationWeight(self, event=None):
        self.arduino.calibrationWeight = float(self.power_val.get())

    # def set_setpoint(self, event=None):
    #     self.arduino.setpoint = float(self.setpoint_val.get())

    def start(self):
        _self = self
        def shell():
            self = _self
        threading.Thread(target=shell, daemon=True).start()
        execute_delayed(self.root, self.sample())

    def time_deviation(self):
        if self.every_status:
            t0 = self.every_status[0].time
            t0_local = self.every_status[0].local_time
            t_sum = 0
            for s in self.every_status:
                t_sum += (s.time - t0) - (s.local_time - t0_local)
            return t_sum / len(self.every_status)
        else:
            return 0

    def control(self):
        pass

    def sample(self):
        self.arduino.start()
        with open("experiments/{}.csv".format(self.experiment), 'w') as f:
            csvf = csv.writer(f)
            csvf.writerow(Status._fields)
            while True:
                try:
                    for s in self.arduino.iter_status():
                        if self.th0 is None:
                            self.th0 = s.temp
                        csvf.writerow(s)
                        self.set_status(str(s))
                        self.every_status.append(s)
                    f.flush()
                    self.plot.update(self.every_status)
                    self.control()
                    yield self.update_period
                except Exception as e:
                    print(e)

def run(port, experiment):
    root = tk.Tk()
    root.geometry("1000x700")
    win = Krosnis(root, port, experiment)
    win.start()
    tk.mainloop()

if __name__ == "__main__":
    run(sys.argv[1], sys.argv[2])
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright (c) 2010-2014, GEM Foundation.
#
# OpenQuake is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# OpenQuake is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with OpenQuake.  If not, see <http://www.gnu.org/licenses/>.

"""Utility functions related to splitting work into tasks."""

from celery.result import ResultSet
from celery.app import current_app
from celery.task import task

from openquake.hazardlib.gsim.base import GroundShakingIntensityModel
from openquake.commonlib.parallel import \
    TaskManager, safely_call, check_mem_usage
from openquake.engine import logs
from openquake.engine.db import models
from openquake.engine.utils import config

SOFT_MEM_LIMIT = int(config.get('memory', 'soft_mem_limit'))
HARD_MEM_LIMIT = int(config.get('memory', 'hard_mem_limit'))
check_mem_usage.__defaults__ = (SOFT_MEM_LIMIT, HARD_MEM_LIMIT)

class JobNotRunning(Exception):
    pass

class OqTaskManager(TaskManager):
    """
    A celery-based task manager. The usage is::

      oqm = OqTaskManager(do_something, logs.LOG.progress)
      oqm.send(arg1, arg2)
      oqm.send(arg3, arg4)
      print oqm.aggregate_results(agg, acc)

    Progress report is built-in.
    """
    progress = staticmethod(logs.LOG.progress)
    task_ids = []

    def _submit(self, pickled_args):
        # submit tasks by using celery
        res = self.oqtask.delay(*pickled_args)
        self.task_ids.append(res.task_id)
        return res

    def aggregate_result_set(self, agg, acc):
        """
        Loop on a set of celery AsyncResults and update the accumulator
        by using the aggregation function.

        :param agg: the aggregation function, (acc, val) -> new acc
        :param acc: the initial value of the accumulator
        :returns: the final value of the accumulator
        """
        if not self.results:
            return acc
        backend = current_app().backend
        amqp_backend = backend.__class__.__name__.startswith('AMQP')
        rset = ResultSet(self.results)
        for task_id, result_dict in rset.iter_native():
            idx = self.task_ids.index(task_id)
            self.task_ids.pop(idx)
            check_mem_usage()  # warn if too much memory is used
            result = result_dict['result']
            if isinstance(result, BaseException):
                raise result
            self.received.append(len(result))
            acc = agg(acc, result.unpickle())
            if amqp_backend:
                # work around a celery bug
                del backend._cache[task_id]
        return acc

# convenient aliases
starmap = OqTaskManager.starmap
apply_reduce = OqTaskManager.apply_reduce

def oqtask(task_func):
    """
    Task function decorator which sets up logging and catches (and logs) any
    errors which occur inside the task. Also checks to make sure the job is
    actually still running. If it is not running, the task doesn't get
    executed, so we don't do useless computation.

    :param task_func: the function to decorate
    """
    def wrapped(*args):
        """
        Initialize logs, make sure the job is still running, and run the task
        code surrounded by a try-except. If any error occurs, log it as a
        critical failure.
        """
        # the last argument is assumed to be a monitor
        monitor = args[-1]
        job = models.OqJob.objects.get(id=monitor.job_id)
        if job.is_running is False:
            # the job was killed, it is useless to run the task
            raise JobNotRunning(monitor.job_id)
        check_mem_usage()  # warn if too much memory is used
        total = 'total ' + task_func.__name__
        with monitor(total, task=tsk):
            with GroundShakingIntensityModel.forbid_instantiation():
                return task_func(*args)

    celery_queue = config.get('amqp', 'celery_queue')

    def f(*args):
        return safely_call(wrapped, args, pickle=True)
    f.__name__ = task_func.__name__
    f.__module__ = task_func.__module__
    tsk = task(f, queue=celery_queue)
    tsk.__func__ = tsk
    tsk.task_func = task_func
    return tsk

# ######### oq-lite support: this is hackish for the time being ############# #

from openquake.commonlib import parallel

# monkey patch the parallel module
parallel.starmap = starmap
parallel.apply_reduce = apply_reduce
parallel.litetask = oqtask
<commit_msg>Removed the oqtask decorator<commit_after># -*- coding: utf-8 -*-
# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright (c) 2010-2014, GEM Foundation.
#
# OpenQuake is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# OpenQuake is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with OpenQuake.  If not, see <http://www.gnu.org/licenses/>.

"""Utility functions related to splitting work into tasks."""
import types

from celery.result import ResultSet
from celery.app import current_app
from celery.task import task

from openquake.commonlib.parallel import TaskManager, check_mem_usage
from openquake.engine import logs
from openquake.engine.utils import config

SOFT_MEM_LIMIT = int(config.get('memory', 'soft_mem_limit'))
HARD_MEM_LIMIT = int(config.get('memory', 'hard_mem_limit'))
check_mem_usage.__defaults__ = (SOFT_MEM_LIMIT, HARD_MEM_LIMIT)

class OqTaskManager(TaskManager):
    """
    A celery-based task manager. The usage is::

      oqm = OqTaskManager(do_something, logs.LOG.progress)
      oqm.send(arg1, arg2)
      oqm.send(arg3, arg4)
      print oqm.aggregate_results(agg, acc)

    Progress report is built-in.
    """
    progress = staticmethod(logs.LOG.progress)
    task_ids = []

    def _submit(self, pickled_args):
        # submit tasks by using celery
        if isinstance(self.oqtask, types.FunctionType):
            celery_queue = config.get('amqp', 'celery_queue')
            self.oqtask = task(self.oqtask, queue=celery_queue)
        res = self.oqtask.delay(*pickled_args)
        self.task_ids.append(res.task_id)
        return res

    def aggregate_result_set(self, agg, acc):
        """
        Loop on a set of celery AsyncResults and update the accumulator
        by using the aggregation function.

        :param agg: the aggregation function, (acc, val) -> new acc
        :param acc: the initial value of the accumulator
        :returns: the final value of the accumulator
        """
        if not self.results:
            return acc
        backend = current_app().backend
        amqp_backend = backend.__class__.__name__.startswith('AMQP')
        rset = ResultSet(self.results)
        for task_id, result_dict in rset.iter_native():
            idx = self.task_ids.index(task_id)
            self.task_ids.pop(idx)
            check_mem_usage()  # warn if too much memory is used
            result = result_dict['result']
            if isinstance(result, BaseException):
                raise result
            self.received.append(len(result))
            acc = agg(acc, result.unpickle())
            if amqp_backend:
                # work around a celery bug
                del backend._cache[task_id]
        return acc

# convenient aliases
starmap = OqTaskManager.starmap
apply_reduce = OqTaskManager.apply_reduce
<|endoftext|>


<commit_before>#!/usr/bin/env python3
import pygame
import subprocess
import os
import configparser
import sys
import time
from PIL import Image
import calendar

config = {
    "photostorage": "Pictures",
    "chdkptp": "chdkptp",
    "fullscreen": "-w" not in sys.argv,  # -w for windowed
    "bgcolor": (0, 0, 0),
    "textcolor": (255, 255, 255),
    "displayPhotoFor": 5,
    "photosPerSet": 4,
    "countdown": 4,
    "countdownSpeed": 1
}

def configure():
    cf = configparser.SafeConfigParser()
    cf.read(['photobooth.cfg', os.path.expanduser('~/.photobooth.cfg')])
    if cf.has_section('photobooth'):
        for key in config:
            if cf.has_option('photobooth', key):
                if type(config[key]) == int:
                    config[key] = cf.getint('photobooth', key)
                else:
                    config[key] = cf.get('photobooth', key)
                    if key in ["bgcolor", "textcolor"]:
                        config[key] = config[key].split(",")
                        for color in range(len(config[key])):
                            config[key][color] = int(config[key][color].strip())
                        config[key] = tuple(config[key])
    print(config)

def waitForInput(stream, waitFor=">"):
    char = stream.read(1).decode('utf-8')
    while char != waitFor:
        print(char, end="")
        sys.stdout.flush()
        char = stream.read(1).decode('utf-8')
    print(char)

## The following code was stolen from http://www.pygame.org/wiki/TextWrapping
def truncline(text, font, maxwidth):
    real = len(text)
    stext = text
    l = font.size(text)[0]
    cut = 0
    a = 0
    done = 1
    while l > maxwidth:
        a = a+1
        n = text.rsplit(None, a)[0]
        if stext == n:
            cut += 1
            stext = n[:-cut]
        else:
            stext = n
        l = font.size(stext)[0]
        real = len(stext)
        done = 0
    return real, done, stext

def wrapline(text, font, maxwidth):
    done = 0
    wrapped = []

    while not done:
        nl, done, stext = truncline(text, font, maxwidth)
        wrapped.append(stext.strip())
        text = text[nl:]
    return wrapped

def renderText(textstr, game, fontSize=1000, top=None):
    font = pygame.font.Font(None, fontSize)
    lines = wrapline(textstr, font, game['size'][0]-100)
    height = font.size(lines[0])[1]
    totalTextHeight = height * len(lines)
    if top is None:
        top = game['background'].get_rect().centery - (totalTextHeight/2)
    for line in range(0, len(lines)):
        text = font.render(lines[line], 1, config['textcolor'])
        textpos = text.get_rect()
        textpos.centerx = game['background'].get_rect().centerx
        textpos.top = top + height*line
        game['background'].blit(text, textpos)
        game['screen'].blit(game['background'], (0, 0))
    pygame.display.flip()
    game['clock'].tick()

def makeSet(prefix, mode="RGB", color="white"):
    images = []
    for i in range(0, config['photosPerSet']):
        images.append(Image.open("%s-%s.jpg" % (prefix, i)))
    width = images[0].size[0] * 2
    height = images[0].size[1] * 2
    image = Image.new(mode, (width, height), color)

    left, upper = 0, 0
    for img in images:
        image.paste(img, (left, upper))
        if left == 0:
            left += img.size[1]
        else:
            left = 0
            upper += img.size[1]

    outputName = "%s-strip.jpg" % prefix
    image.save(outputName, "JPEG", quality=80, optimize=True)
    return outputName

def takePhotoSet(chdkptp, game):
    stripnumber = calendar.timegm(time.gmtime())
    filename = "%s/%s" % (config['photostorage'], stripnumber)
    # Enter rsint mode
    chdkptp.stdin.write(("rsint %s-last\n" % filename).encode())
    waitForInput(chdkptp.stdout)
    photonumber = 0
    while photonumber < config['photosPerSet']:
        doCountdown(game)
        takePhoto(chdkptp)
        os.rename("%s-last.jpg" % filename, "%s-%s.jpg" % (filename, photonumber))
        if photonumber != config['photosPerSet']-1:
            # Don't display the last picture because we're going to show it in the strip
            displayPhoto("%s-%s.jpg" % (filename, photonumber), game, sleep=20)
        photonumber += 1
    chdkptp.stdin.write(b"q\n")
    photoset = makeSet(filename)
    displayPhoto(photoset, game)
    waitForInput(chdkptp.stdout)

def doCountdown(game):
    countdown = config['countdown']
    while countdown >= 0:
        game['background'].fill(config['bgcolor'])
        if countdown > 0:
            renderText(str(countdown), game, fontSize=1500, top=-75)
        elif countdown == 0:
            renderText("smile!", game, fontSize=500)
        countdown -= 1
        time.sleep(config['countdownSpeed'])

def takePhoto(chdkptp):
    chdkptp.stdin.write(b"s\n")
    waitForInput(chdkptp.stdout)

def displayPhoto(filename, game, sleep=None, size=None):
    if sleep is None:
        sleep = config['displayPhotoFor']

    # Blank the screen
    game['background'].fill(config['bgcolor'])

    img = pygame.image.load(filename)

    if size is None:
        imgsize = img.get_rect().size
        bgsize = game['background'].get_rect()
        print(bgsize)
        print(imgsize)
        scale = imgsize[0]/bgsize[2]
        if imgsize[0] < imgsize[1]:
            scale = imgsize[1]/bgsize[3]
        size = (int(imgsize[0]/scale), int(imgsize[1]/scale))
        print(size)

    img = pygame.transform.scale(img, size)
    imgposition = img.get_rect()
    imgposition.centerx = game['background'].get_rect().centerx
    imgposition.centery = game['background'].get_rect().centery
    game['background'].blit(img, imgposition)
    game['screen'].blit(game['background'], (0, 0))
    pygame.display.flip()
    game['clock'].tick()
    time.sleep(config['displayPhotoFor'])

def waitForTrigger(game):
    triggered = False
    renderText("Press the #BigRedButton to begin", game, fontSize=200)
    while not triggered:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
            if event.type == pygame.KEYDOWN:
                if event.key == 32:  # spacebar
                    triggered = True
                elif event.key in [113, 27]:  # q, esc
                    sys.exit()
        time.sleep(0.05)
    print("triggered!!")

def main():
    configure()

    pygame.init()
    pygame.mouse.set_visible(False)
    displayinfo = pygame.display.Info()  # Find some info out about the display
    if config['fullscreen']:
        size = displayinfo.current_w, displayinfo.current_h
    else:
        size = 1800, 800

    screen = pygame.display.set_mode(size)

    if config['fullscreen']:
        pygame.display.toggle_fullscreen()
    c = pygame.time.Clock()

    background = pygame.Surface(screen.get_size())

    chdkptp = subprocess.Popen(config['chdkptp'],
                               stdin=subprocess.PIPE,
                               stdout=subprocess.PIPE,
                               stderr=subprocess.PIPE,
                               bufsize=0)
    waitForInput(chdkptp.stdout)
    chdkptp.stdin.write(b"c\n")
    print("Connecting to camera....")
    waitForInput(chdkptp.stdout)
    chdkptp.stdin.write(b"rec\n")
    print("Entering shooting mode...")
    waitForInput(chdkptp.stdout)
    print("Annnd into the loop we go!")

    game = {
        "screen": screen,
        "background": background,
        "clock": c,
        "size": size
    }

    game['background'].fill(config['bgcolor'])
    while True:
        waitForTrigger(game)
        takePhotoSet(chdkptp, game)

if __name__ == "__main__":
    main()
<commit_msg>Compute width properly<commit_after>#!/usr/bin/env python3
import pygame
import subprocess
import os
import configparser
import sys
import time
from PIL import Image
import calendar

config = {
    "photostorage": "Pictures",
    "chdkptp": "chdkptp",
    "fullscreen": "-w" not in sys.argv,  # -w for windowed
    "bgcolor": (0, 0, 0),
    "textcolor": (255, 255, 255),
    "displayPhotoFor": 5,
    "photosPerSet": 4,
    "countdown": 4,
    "countdownSpeed": 1
}

def configure():
    cf = configparser.SafeConfigParser()
    cf.read(['photobooth.cfg', os.path.expanduser('~/.photobooth.cfg')])
    if cf.has_section('photobooth'):
        for key in config:
            if cf.has_option('photobooth', key):
                if type(config[key]) == int:
                    config[key] = cf.getint('photobooth', key)
                else:
                    config[key] = cf.get('photobooth', key)
                    if key in ["bgcolor", "textcolor"]:
                        config[key] = config[key].split(",")
                        for color in range(len(config[key])):
                            config[key][color] = int(config[key][color].strip())
                        config[key] = tuple(config[key])
    print(config)

def waitForInput(stream, waitFor=">"):
    char = stream.read(1).decode('utf-8')
    while char != waitFor:
        print(char, end="")
        sys.stdout.flush()
        char = stream.read(1).decode('utf-8')
    print(char)

## The following code was stolen from http://www.pygame.org/wiki/TextWrapping
def truncline(text, font, maxwidth):
    real = len(text)
    stext = text
    l = font.size(text)[0]
    cut = 0
    a = 0
    done = 1
    while l > maxwidth:
        a = a+1
        n = text.rsplit(None, a)[0]
        if stext == n:
            cut += 1
            stext = n[:-cut]
        else:
            stext = n
        l = font.size(stext)[0]
        real = len(stext)
        done = 0
    return real, done, stext

def wrapline(text, font, maxwidth):
    done = 0
    wrapped = []

    while not done:
        nl, done, stext = truncline(text, font, maxwidth)
        wrapped.append(stext.strip())
        text = text[nl:]
    return wrapped

def renderText(textstr, game, fontSize=1000, top=None):
    font = pygame.font.Font(None, fontSize)
    lines = wrapline(textstr, font, game['size'][0]-100)
    height = font.size(lines[0])[1]
    totalTextHeight = height * len(lines)
    if top is None:
        top = game['background'].get_rect().centery - (totalTextHeight/2)
    for line in range(0, len(lines)):
        text = font.render(lines[line], 1, config['textcolor'])
        textpos = text.get_rect()
        textpos.centerx = game['background'].get_rect().centerx
        textpos.top = top + height*line
        game['background'].blit(text, textpos)
        game['screen'].blit(game['background'], (0, 0))
    pygame.display.flip()
    game['clock'].tick()

def makeSet(prefix, mode="RGB", color="white"):
    images = []
    for i in range(0, config['photosPerSet']):
        images.append(Image.open("%s-%s.jpg" % (prefix, i)))
    width = images[0].size[0] * 2
    height = images[0].size[1] * 2
    image = Image.new(mode, (width, height), color)

    left, upper = 0, 0
    for img in images:
        image.paste(img, (left, upper))
        if left == 0:
            left += img.size[0]
        else:
            left = 0
            upper += img.size[1]

    outputName = "%s-strip.jpg" % prefix
    image.save(outputName, "JPEG", quality=80, optimize=True)
    return outputName

def takePhotoSet(chdkptp, game):
    stripnumber = calendar.timegm(time.gmtime())
    filename = "%s/%s" % (config['photostorage'], stripnumber)
    # Enter rsint mode
    chdkptp.stdin.write(("rsint %s-last\n" % filename).encode())
    waitForInput(chdkptp.stdout)
    photonumber = 0
    while photonumber < config['photosPerSet']:
        doCountdown(game)
        takePhoto(chdkptp)
        os.rename("%s-last.jpg" % filename, "%s-%s.jpg" % (filename, photonumber))
        if photonumber != config['photosPerSet']-1:
            # Don't display the last picture because we're going to show it in the strip
            displayPhoto("%s-%s.jpg" % (filename, photonumber), game, sleep=20)
        photonumber += 1
    chdkptp.stdin.write(b"q\n")
    photoset = makeSet(filename)
    displayPhoto(photoset, game)
    waitForInput(chdkptp.stdout)

def doCountdown(game):
    countdown = config['countdown']
    while countdown >= 0:
        game['background'].fill(config['bgcolor'])
        if countdown > 0:
            renderText(str(countdown), game, fontSize=1500, top=-75)
        elif countdown == 0:
            renderText("smile!", game, fontSize=500)
        countdown -= 1
        time.sleep(config['countdownSpeed'])

def takePhoto(chdkptp):
    chdkptp.stdin.write(b"s\n")
    waitForInput(chdkptp.stdout)

def displayPhoto(filename, game, sleep=None, size=None):
    if sleep is None:
        sleep = config['displayPhotoFor']

    # Blank the screen
    game['background'].fill(config['bgcolor'])

    img = pygame.image.load(filename)

    if size is None:
        imgsize = img.get_rect().size
        bgsize = game['background'].get_rect()
        print(bgsize)
        print(imgsize)
        scale = imgsize[0]/bgsize[2]
        if imgsize[0] < imgsize[1]:
            scale = imgsize[1]/bgsize[3]
        size = (int(imgsize[0]/scale), int(imgsize[1]/scale))
        print(size)

    img = pygame.transform.scale(img, size)
    imgposition = img.get_rect()
    imgposition.centerx = game['background'].get_rect().centerx
    imgposition.centery = game['background'].get_rect().centery
    game['background'].blit(img, imgposition)
    game['screen'].blit(game['background'], (0, 0))
    pygame.display.flip()
    game['clock'].tick()
    time.sleep(config['displayPhotoFor'])

def waitForTrigger(game):
    triggered = False
    renderText("Press the #BigRedButton to begin", game, fontSize=200)
    while not triggered:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
            if event.type == pygame.KEYDOWN:
                if event.key == 32:  # spacebar
                    triggered = True
                elif event.key in [113, 27]:  # q, esc
                    sys.exit()
        time.sleep(0.05)
    print("triggered!!")

def main():
    configure()

    pygame.init()
    pygame.mouse.set_visible(False)
    displayinfo = pygame.display.Info()  # Find some info out about the display
    if config['fullscreen']:
        size = displayinfo.current_w, displayinfo.current_h
    else:
        size = 1800, 800

    screen = pygame.display.set_mode(size)

    if config['fullscreen']:
        pygame.display.toggle_fullscreen()
    c = pygame.time.Clock()

    background = pygame.Surface(screen.get_size())

    chdkptp = subprocess.Popen(config['chdkptp'],
                               stdin=subprocess.PIPE,
                               stdout=subprocess.PIPE,
                               stderr=subprocess.PIPE,
                               bufsize=0)
    waitForInput(chdkptp.stdout)
    chdkptp.stdin.write(b"c\n")
    print("Connecting to camera....")
    waitForInput(chdkptp.stdout)
    chdkptp.stdin.write(b"rec\n")
    print("Entering shooting mode...")
    waitForInput(chdkptp.stdout)
    print("Annnd into the loop we go!")

    game = {
        "screen": screen,
        "background": background,
        "clock": c,
        "size": size
    }

    game['background'].fill(config['bgcolor'])
    while True:
        waitForTrigger(game)
        takePhotoSet(chdkptp, game)

if __name__ == "__main__":
    main()
<|endoftext|>


<commit_before>from sqlalchemy import Column, ForeignKey, Integer, Table
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import relationship

from great.models.core import Base, Model, Media

class Track(Media):
    __tablename__ = "tracks"

    number = Column(Integer)

    disc_id = Column(Integer, ForeignKey("discs.id"), nullable=True)

    @hybrid_property
    def artist(self):
        if self.artists:
            return self.artists[0].name

    @artist.setter
    def artist(self, name):
        if not self.artists:
            self.artists = [Artist(name=name)]
        else:
            raise ValueError("{0!r} already has artists set.".format(self))

class Artist(Model):
    __tablename__ = "artists"

    tracks = relationship(
        "Track", backref="artists", secondary="tracks_artists", lazy="dynamic",
    )

class Album(Media):
    __tablename__ = "albums"

    discs = relationship("Disc", backref="album", lazy="dynamic")

class Disc(Media):
    __tablename__ = "discs"

    number = Column(Integer)

    tracks = relationship("Track", backref="disc")

    album_id = Column(Integer, ForeignKey("albums.id"))

class Composer(Model):
    __tablename__ = "composers"

    tracks = relationship(
        "Track",
        backref="composers",
        secondary="tracks_composers",
        lazy="dynamic",
    )

Table(
    "tracks_artists",
    Base.metadata,
    Column("track_id", Integer, ForeignKey("tracks.id")),
    Column("artist_id", Integer, ForeignKey("artists.id")),
)

Table(
    "tracks_composers",
    Base.metadata,
    Column("track_id", Integer, ForeignKey("tracks.id")),
    Column("composer_id", Integer, ForeignKey("composers.id")),
)
<commit_msg>Add album.<commit_after>from sqlalchemy import Column, ForeignKey, Integer, Table
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import relationship

from great.models.core import Base, Model, Media

class Track(Media):
    __tablename__ = "tracks"

    number = Column(Integer)

    disc_id = Column(Integer, ForeignKey("discs.id"), nullable=True)

    @hybrid_property
    def artist(self):
        if self.artists:
            return self.artists[0].name

    @artist.setter
    def artist(self, artist):
        if self.artists:
            raise ValueError("{0!r} already has artists set.".format(self))
        self.artists = [artist]

    @hybrid_property
    def album(self):
        if self.disc:
            return self.disc.album

    @album.setter
    def album(self, album):
        if self.disc is not None:
            raise ValueError("{0!r} already has its disc set.".format(self))
        if album.discs.count():
            raise ValueError("{0!r} already has discs set.".format(album))
        self.disc = Disc(number=1, album=album)

class Artist(Model):
    __tablename__ = "artists"

    tracks = relationship(
        "Track", backref="artists", secondary="tracks_artists", lazy="dynamic",
    )

class Album(Media):
    __tablename__ = "albums"

    discs = relationship("Disc", backref="album", lazy="dynamic")

class Disc(Media):
    __tablename__ = "discs"

    number = Column(Integer)

    tracks = relationship("Track", backref="disc")

    album_id = Column(Integer, ForeignKey("albums.id"))

class Composer(Model):
    __tablename__ = "composers"

    tracks = relationship(
        "Track",
        backref="composers",
        secondary="tracks_composers",
        lazy="dynamic",
    )

Table(
    "tracks_artists",
    Base.metadata,
    Column("track_id", Integer, ForeignKey("tracks.id")),
    Column("artist_id", Integer, ForeignKey("artists.id")),
)

Table(
    "tracks_composers",
    Base.metadata,
    Column("track_id", Integer, ForeignKey("tracks.id")),
    Column("composer_id", Integer, ForeignKey("composers.id")),
)
<|endoftext|>


<commit_before>#!/usr/bin/env python

import piggyphoto, pygame
import os, sys
import time, datetime
import StringIO
from subprocess import call
import argparse

COUNT_DOWN_TIME = 1
MONTAGE_DISPLAY_TIME = 2
IDLE_TIME = 10

class Camera(object):
    def __init__(self):
        self.camera = piggyphoto.camera()
        #qq self.camera.leave_locked()

    def capture_preview(self):
        return StringIO.StringIO(self.camera.capture_preview().get_data())

    def capture_image(self, image_path):
        self.camera.capture_image(image_path)

class DebugCamera():
    def capture_preview(self):
        with open('preview.jpg', 'rb') as img:
            return StringIO.StringIO(img.read())

    def capture_image(self, image_path):
        print "Captured an image!", image_path

class PhotoBooth(object):
    def __init__(self, image_dest, fullscreen, debug, printing):
        self.debug = debug
        if debug:
            self.camera = DebugCamera()
        else:
            self.camera = Camera()

        self.printing = printing

        self.output_dir = image_dest
        self.size = None
        self.fullscreen = fullscreen
        self.events = []
        self.current_session = None

    def capture_preview(self):
        preview = self.camera.capture_preview()

        picture = pygame.image.load(preview)
        if self.size:
            picture = pygame.transform.scale(picture, self.size)
        return picture

    def start(self):
        preview = self.capture_preview()
        pygame.init()
        self.clock = pygame.time.Clock()

        if self.fullscreen:
            pygame.display.set_mode((0,0), pygame.FULLSCREEN)
        else:
            pygame.display.set_mode(preview.get_size())

        self.main_surface = pygame.display.get_surface()

        self.size = self.main_surface.get_size()

        while self.main_loop():
            pass

    def main_loop(self):
        pygame.event.clear()
        self.clock.tick(25)
        if len(self.events) > 10:
            self.events = self.events[:10]
        pygame.display.flip()

        button_press = self.space_pressed()

        if self.current_session:
            if self.current_session.do_frame(button_press):
                # Start a new session
                self.current_session = PhotoSession(self)
            if self.current_session.idle():
                self.current_session = None
        elif button_press:
            # Start a new session
            self.current_session = PhotoSession(self)
        else:
            self.wait()

        return self.check_for_quit_event()

    def idle_timeout(self):
        return False

    def wait(self):
        self.main_surface.fill((0,0,0))
        self.render_text_centred('Press the button to start!')

    def render_text_centred(self, text_string):
        location = self.main_surface.get_rect()
        font = pygame.font.SysFont(pygame.font.get_default_font(), 142)
        text = font.render(text_string, 1, (210, 210, 210))
        textpos = text.get_rect()

        textpos.centerx = location.centerx
        textpos.centery = location.centery
        self.main_surface.blit(text, textpos)

    def capture_image(self, file_name):
        self.camera.capture_image(os.path.join(self.output_dir, file_name))

    def load_image(self, file_name):
        if self.debug:
            image_path = "test.jpg"
        else:
            image_path = os.path.join(self.output_dir, file_name)
        return pygame.image.load(image_path)

    def save(self, out_name, images):
        out_path = os.path.join(self.output_dir, out_name)
        first = self.load_image(images[0])
        size = (first.get_size()[0]/2, first.get_size()[1]/2)
        print size
        combined = pygame.Surface(first.get_size())
        for count, image_name in enumerate(images):
            image = self.load_image(image_name)
            image = pygame.transform.scale(image, size)
            x_pos = size[0] * (count % 2)
            y_pos = size[1] * (1 if count > 1 else 0)
            combined.blit(image, (x_pos, y_pos))
        pygame.image.save(combined, out_path)

        if self.printing:
            call(["lpr", out_path])

    def check_key_event(self, *keys):
        self.events += pygame.event.get(pygame.KEYUP)
        for event in self.events:
            if event.dict['key'] in keys:
                self.events = []
                return True
        return False

    def space_pressed(self):
        return self.check_key_event(pygame.K_SPACE)

    def check_for_quit_event(self):
        return not self.check_key_event(pygame.K_q, pygame.K_ESCAPE) \
            and not pygame.event.peek(pygame.QUIT)

class PhotoSession(object):
    def __init__(self, booth):
        self.booth = booth

        self.timer = -1
        self.photo_count = 1
        self.capture_start = None
        self.montage_timer = -1
        self.montage_displayed = False
        self.finished = False
        self.saved_image = False
        self.session_start = time.time()

    def do_frame(self, button_pressed):
        if self.montage_timer > 0:
            self.display_montage()
        else:
            picture = self.booth.capture_preview()
            self.booth.main_surface.blit(picture, (0, 0))
            if not self.capture_start:
                self.booth.render_text_centred("Push when ready!")

        if self.timer > 0:
            self.do_countdown()

        if button_pressed and not self.capture_start:
            self.timer = time.time() + COUNT_DOWN_TIME + 1
            self.capture_start = datetime.datetime.now()

        return self.finished

    def idle(self):
        return not self.capture_start and time.time() - self.session_start > IDLE_TIME

    def do_countdown(self):
        time_remaining = int(self.timer - time.time())

        if time_remaining <= 0:
            image_name = self.get_image_name(self.photo_count)
            self.booth.capture_image(image_name)
            if self.photo_count == 4:
                self.timer = -1
                self.photo_count = 1
                self.montage_timer = time.time()
            else:
                self.timer = time.time() + COUNT_DOWN_TIME + 1
                self.photo_count += 1
        else:
            self.booth.render_text_centred(str(time_remaining))

    def display_montage(self):
        if not self.montage_displayed:
            for im in range(1, 5):
                image = self.booth.load_image(self.get_image_name(im))
                size = (self.booth.size[0]/2, self.booth.size[1]/2)
                image = pygame.transform.scale(image, size)
                x_pos = size[0] * ((im - 1) % 2)
                y_pos = size[1] * (1 if im > 2 else 0)
                self.booth.main_surface.blit(image, (x_pos, y_pos))
            self.montage_displayed = True
        elif not self.saved_image:
            self.booth.save(self.get_image_name('combined'), [self.get_image_name(im) for im in range(1,5)])
            self.saved_image = True

        if time.time() - self.montage_timer > MONTAGE_DISPLAY_TIME:
            self.montage_timer = -1
            self.montage_displayed = False
            self.finished = True

    def get_image_name(self, count):
        return self.capture_start.strftime('%Y-%m-%d-%H%M%S') + '-' + str(count) + '.jpg'

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument("save_to", help="Location to save images")
    parser.add_argument("-d", "--debug", help="Don't require a real camera to be attached", action="store_true")
    parser.add_argument("--nofullscreen", help="Don't use fullscreen mode", action="store_true")
    parser.add_argument("--printing", help="Enable printing", action="store_true")
    args = parser.parse_args()

    booth = PhotoBooth(args.save_to, fullscreen=(not args.nofullscreen), debug=args.debug, printing=args.printing)
    booth.start()

# TODO
# Investigate leave locked
# Reinit the camera, or exit it when on black screen
# Focusing?

<commit_msg>Fix focusing.<commit_after>#!/usr/bin/env python

import piggyphoto, pygame
import os, sys
import time, datetime
import StringIO
from subprocess import call
import argparse

COUNT_DOWN_TIME = 1
MONTAGE_DISPLAY_TIME = 2
IDLE_TIME = 10

class Camera(object):
    def __init__(self):
        self.camera = piggyphoto.camera()
        #qq self.camera.leave_locked()

    def capture_preview(self):
        if not self.camera:
            self.camera = piggyphoto.camera()
        return StringIO.StringIO(self.camera.capture_preview().get_data())

    def capture_image(self, image_path):
        del self.camera
        self.camera = piggyphoto.camera()
        self.camera.capture_image(image_path)

    def sleep(self):
        del self.camera
        self.camera = None

class DebugCamera():
    def capture_preview(self):
        with open('preview.jpg', 'rb') as img:
            return StringIO.StringIO(img.read())

    def capture_image(self, image_path):
        print "Captured an image!", image_path

    def sleep(self):
        print "Sleep!"

class PhotoBooth(object):
    def __init__(self, image_dest, fullscreen, debug, printing):
        self.debug = debug
        if debug:
            self.camera = DebugCamera()
        else:
            self.camera = Camera()

        self.printing = printing

        self.output_dir = image_dest
        self.size = None
        self.fullscreen = fullscreen
        self.events = []
        self.current_session = None

    def capture_preview(self):
        preview = self.camera.capture_preview()

        picture = pygame.image.load(preview)
        if self.size:
            picture = pygame.transform.scale(picture, self.size)
        return picture

    def start(self):
        preview = self.capture_preview()
        pygame.init()
        self.clock = pygame.time.Clock()

        if self.fullscreen:
            pygame.display.set_mode((0,0), pygame.FULLSCREEN)
        else:
            pygame.display.set_mode(preview.get_size())

        self.main_surface = pygame.display.get_surface()

        self.size = self.main_surface.get_size()

        while self.main_loop():
            pass

    def main_loop(self):
        pygame.event.clear()
        self.clock.tick(25)
        if len(self.events) > 10:
            self.events = self.events[:10]
        pygame.display.flip()

        button_press = self.space_pressed()

        if self.current_session:
            if self.current_session.do_frame(button_press):
                # Start a new session
                self.current_session = PhotoSession(self)
            if self.current_session.idle():
                self.current_session = None
                self.camera.sleep()
        elif button_press:
            # Start a new session
            self.current_session = PhotoSession(self)
        else:
            self.wait()

        return self.check_for_quit_event()

    def idle_timeout(self):
        return False

    def wait(self):
        self.main_surface.fill((0,0,0))
        self.render_text_centred('Press the button to start!')

    def render_text_centred(self, text_string):
        location = self.main_surface.get_rect()
        font = pygame.font.SysFont(pygame.font.get_default_font(), 142)
        text = font.render(text_string, 1, (210, 210, 210))
        textpos = text.get_rect()

        textpos.centerx = location.centerx
        textpos.centery = location.centery
        self.main_surface.blit(text, textpos)

    def capture_image(self, file_name):
        self.camera.capture_image(os.path.join(self.output_dir, file_name))

    def load_image(self, file_name):
        if self.debug:
            image_path = "test.jpg"
        else:
            image_path = os.path.join(self.output_dir, file_name)
        return pygame.image.load(image_path)

    def save(self, out_name, images):
        out_path = os.path.join(self.output_dir, out_name)
        first = self.load_image(images[0])
        size = (first.get_size()[0]/2, first.get_size()[1]/2)
        print size
        combined = pygame.Surface(first.get_size())
        for count, image_name in enumerate(images):
            image = self.load_image(image_name)
            image = pygame.transform.scale(image, size)
            x_pos = size[0] * (count % 2)
            y_pos = size[1] * (1 if count > 1 else 0)
            combined.blit(image, (x_pos, y_pos))
        pygame.image.save(combined, out_path)

        if self.printing:
            call(["lpr", out_path])

    def check_key_event(self, *keys):
        self.events += pygame.event.get(pygame.KEYUP)
        for event in self.events:
            if event.dict['key'] in keys:
                self.events = []
                return True
        return False

    def space_pressed(self):
        return self.check_key_event(pygame.K_SPACE)

    def check_for_quit_event(self):
        return not self.check_key_event(pygame.K_q, pygame.K_ESCAPE) \
            and not pygame.event.peek(pygame.QUIT)

class PhotoSession(object):
    def __init__(self, booth):
        self.booth = booth

        self.timer = -1
        self.photo_count = 1
        self.capture_start = None
        self.montage_timer = -1
        self.montage_displayed = False
        self.finished = False
        self.saved_image = False
        self.session_start = time.time()

    def do_frame(self, button_pressed):
        if self.montage_timer > 0:
            self.display_montage()
        else:
            picture = self.booth.capture_preview()
            self.booth.main_surface.blit(picture, (0, 0))
            if not self.capture_start:
                self.booth.render_text_centred("Push when ready!")

        if self.timer > 0:
            self.do_countdown()

        if button_pressed and not self.capture_start:
            self.timer = time.time() + COUNT_DOWN_TIME + 1
            self.capture_start = datetime.datetime.now()

        return self.finished

    def idle(self):
        return not self.capture_start and time.time() - self.session_start > IDLE_TIME

    def do_countdown(self):
        time_remaining = int(self.timer - time.time())

        if time_remaining <= 0:
            image_name = self.get_image_name(self.photo_count)
            self.booth.capture_image(image_name)
            if self.photo_count == 4:
                self.timer = -1
                self.photo_count = 1
                self.montage_timer = time.time()
            else:
                self.timer = time.time() + COUNT_DOWN_TIME + 1
                self.photo_count += 1
        else:
            self.booth.render_text_centred(str(time_remaining))

    def display_montage(self):
        if not self.montage_displayed:
            for im in range(1, 5):
                image = self.booth.load_image(self.get_image_name(im))
                size = (self.booth.size[0]/2, self.booth.size[1]/2)
                image = pygame.transform.scale(image, size)
                x_pos = size[0] * ((im - 1) % 2)
                y_pos = size[1] * (1 if im > 2 else 0)
                self.booth.main_surface.blit(image, (x_pos, y_pos))
            self.montage_displayed = True
        elif not self.saved_image:
            self.booth.save(self.get_image_name('combined'), [self.get_image_name(im) for im in range(1,5)])
            self.saved_image = True

        if time.time() - self.montage_timer > MONTAGE_DISPLAY_TIME:
            self.montage_timer = -1
            self.montage_displayed = False
            self.finished = True

    def get_image_name(self, count):
        return self.capture_start.strftime('%Y-%m-%d-%H%M%S') + '-' + str(count) + '.jpg'

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument("save_to", help="Location to save images")
    parser.add_argument("-d", "--debug", help="Don't require a real camera to be attached", action="store_true")
    parser.add_argument("--nofullscreen", help="Don't use fullscreen mode", action="store_true")
    parser.add_argument("--printing", help="Enable printing", action="store_true")
    args = parser.parse_args()

    booth = PhotoBooth(args.save_to, fullscreen=(not args.nofullscreen), debug=args.debug, printing=args.printing)
    booth.start()

# TODO
# Investigate leave locked
# Reinit the camera, or exit it when on black screen
# Focusing?

<|endoftext|>


<commit_before>"""The GrepTree data structure centers around the idea of a `context`. A context
is essentially the function/class/method/file that a line belongs to. A GrepTree
is a nested tree-style collection of these contexts, with the furthest point on
each branch containing a list of lines that come from that context.
"""
import json

def count_lines(subtree):
    """Convienience function to count the number of lines in a subtree."""
    count = 0
    for key, val in subtree.iteritems():
        if key == 'lines':
            count += len(val)
        else:
            count += count_lines(val)

    return count

class GrepTree(object):
    """Data structure for storing results as a tree of nested contexts."""
    LINES = 'lines'

    def __init__(self, data={}):
        self.data = data
        self._count = 0

    @classmethod
    def load(cls, inp_file):
        """Create GrepTree object from json file handler."""
        return cls(json.load(inp_file))

    @classmethod
    def load_path(cls, path):
        """Open json file as GrepTree object."""
        with open(path, 'r') as inp_file:
            return cls(json.load(inp_file))

    def dump_to_path(self, path):
        """JSON encode GrepTree object and write to file at path."""
        with open(path, 'w') as outp_file:
            self.dump(outp_file)

    def dump(self, outp_file):
        """JSON encode GrepTree object and write to file object."""
        flat = json.dumps(self.data, indent=4)
        outp_file.write(flat)

    def touch(self, key, subtree=None):
        """Add empty node to a subtree."""
        if subtree is None:
            subtree = self.data
        return subtree.setdefault(key, {})

    def touch_path(self, path, subtree=None):
        """Recursively adds a string of empty nodes to a subtree.
        Returns the new subtree when it's done."""
        for step in path:
            subtree = self.touch(step, subtree)

        return subtree

    def append(self, file_path, line_number, line_text, cntx_list):
        """
        Adds a line to the tree creating any intermediate nodes along the way.
        """
        # Step through context tree, creating nodes along the way
        node_path = [file_path] + list(cntx_list)
        head = self.touch_path(node_path)

        # Add line_number + line_text to head of path
        lines = head.setdefault(self.LINES, [])
        lines.append((line_number, line_text))

        # Increment counter
        self._count += 1

    def walk(self, tree=None, kpath=[]):
        """Traverses a GrepTree, yielding node details along the way."""
        if not tree:
            tree = self.data
        for key, val in tree.iteritems():
            if isinstance(val, dict):
                new_kpath = kpath + [key]
                yield new_kpath, val.get(GrepTree.LINES, [])
                for sub_kpath, lines in self.walk(val, new_kpath):
                    yield sub_kpath, lines
            elif isinstance(val, list):
                pass
            else:
                print "%s type found: %s" % (type(val), val)

    def prune(self, path):
        if count_lines(self.touch_path(path)) == 0:
            # access path's parent node
            subtree = self.touch_path(path[:-1])

            # delete node at path
            if not subtree:
                subtree = self.data
            del subtree[path[-1]]

    def set_lines(self, path, lines):
        subtree = self.touch_path(path)
        subtree[self.LINES] = lines
<commit_msg>@bugfix. GrepTree.walk() could fall into infinite recursion.<commit_after>"""The GrepTree data structure centers around the idea of a `context`. A context
is essentially the function/class/method/file that a line belongs to. A GrepTree
is a nested tree-style collection of these contexts, with the furthest point on
each branch containing a list of lines that come from that context.
"""
import json

def count_lines(subtree):
    """Convienience function to count the number of lines in a subtree."""
    count = 0
    for key, val in subtree.iteritems():
        if key == 'lines':
            count += len(val)
        else:
            count += count_lines(val)

    return count

class GrepTree(object):
    """Data structure for storing results as a tree of nested contexts."""
    LINES = 'lines'

    def __init__(self, data={}):
        self.data = data
        self._count = 0

    @classmethod
    def load(cls, inp_file):
        """Create GrepTree object from json file handler."""
        return cls(json.load(inp_file))

    @classmethod
    def load_path(cls, path):
        """Open json file as GrepTree object."""
        with open(path, 'r') as inp_file:
            return cls(json.load(inp_file))

    def dump_to_path(self, path):
        """JSON encode GrepTree object and write to file at path."""
        with open(path, 'w') as outp_file:
            self.dump(outp_file)

    def dump(self, outp_file):
        """JSON encode GrepTree object and write to file object."""
        flat = json.dumps(self.data, indent=4)
        outp_file.write(flat)

    def touch(self, key, subtree=None):
        """Add empty node to a subtree."""
        if subtree is None:
            subtree = self.data
        return subtree.setdefault(key, {})

    def touch_path(self, path, subtree=None):
        """Recursively adds a string of empty nodes to a subtree.
        Returns the new subtree when it's done."""
        for step in path:
            subtree = self.touch(step, subtree)

        return subtree

    def append(self, file_path, line_number, line_text, cntx_list):
        """
        Adds a line to the tree creating any intermediate nodes along the way.
        """
        # Step through context tree, creating nodes along the way
        node_path = [file_path] + list(cntx_list)
        head = self.touch_path(node_path)

        # Add line_number + line_text to head of path
        lines = head.setdefault(self.LINES, [])
        lines.append((line_number, line_text))

        # Increment counter
        self._count += 1

    def walk(self, tree=None, kpath=None):
        """Traverses a GrepTree, yielding node details along the way."""
        if tree is None:
            tree = self.data
        if kpath is None:
            kpath = []

        for key, val in tree.iteritems():
            if isinstance(val, dict):
                new_kpath = kpath + [key]
                yield new_kpath, val.get(GrepTree.LINES, [])
                for sub_kpath, lines in self.walk(val, new_kpath):
                    yield sub_kpath, lines
            elif isinstance(val, list):
                pass
            else:
                print "%s type found: %s" % (type(val), val)

    def prune(self, path):
        if count_lines(self.touch_path(path)) == 0:
            # access path's parent node
            subtree = self.touch_path(path[:-1])

            # delete node at path
            if not subtree:
                subtree = self.data
            del subtree[path[-1]]

    def set_lines(self, path, lines):
        subtree = self.touch_path(path)
        subtree[self.LINES] = lines
<|endoftext|>


<commit_before>#!/usr/bin/env python
# vim: ts=4 sts=4 et sw=4

# A script/bot for reddit to manage user awarded karma
#
# see karmaflair.ini to set options

from praw import Reddit
from praw import helpers
from reddit import reddit_auth
from reddit import reddit_reply_to_comment
import ConfigParser
from string import Template
import psycopg2
import sys
import re
import time
from datetime import datetime
import uuid

# globals
debug_level = ''
cfg_file = None
r = None
conn = None
cur = None

# helper class for tee logging
class FlushOutput(object):
    def __init__(self):
        self.terminal = sys.stdout

    def write(self, message):
        self.terminal.write(message)
        self.terminal.flush()

sys.stdout = FlushOutput()

def get_reply_text(reply_type, reply_vars):
    template = open('tmpl/karmaflair/' + reply_type + '.tpl')

    return Template(template.read()).substitute(reply_vars)

def check_for_reply(submission, name, granter, reply_type=None):
    replied = False

    try:
        if reply_type is not None:
            cur.execute("SELECT replied FROM karma WHERE id=%s AND name=%s AND granter=%s AND type=%s AND replied=TRUE", (submission.id, name, granter, reply_type,))
        else:
            cur.execute("SELECT replied FROM karma WHERE id=%s AND name=%s AND granter=%s AND replied=TRUE", (submission.id, name, granter,))

        result = cur.fetchone()

        if result is not None:
            replied = True

            if debug_level == 'DEBUG':
                print('[{}] [DEBUG] Reply exists for submission {}'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), submission.id))
    except Exception as e:
        conn.rollback()

        sys.stderr.write('[{}] [ERROR]: {}\n'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), e))
        sys.stderr.flush()
    else:
        conn.commit()

        return not replied

def set_replied(submission, name, granter, reply_type):
    try:
        cur.execute("INSERT INTO " + cfg_file.get('karmaflair', 'dbtablename') + " (id, name, granter, type, replied, session_id)" +
                " VALUES (%s, %s, %s, %s, TRUE, %s) ON CONFLICT (id, name, granter, type) DO UPDATE SET replied=TRUE, session_id=%s",
                (submission.id, name, granter, reply_type, session_id, session_id,))
    except Exception as e:
        conn.rollback()

        sys.stderr.write('[{}] [ERROR]: {}\n'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), e))
        sys.stderr.flush()
    else:
        conn.commit()

        if debug_level == 'DEBUG':
            print('[{}] [DEBUG] Message reply has been recorded to {} by {}, for submission {} of type {}'
                    .format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), name, granter, submission.id, reply_type))

def handle_reply(comment, submission, name, granter, reply_type, reply_vars):
    if check_for_reply(submission, name, granter, reply_type):
        try:
            reddit_reply_to_comment(comment, get_reply_text(reply_type, reply_vars))
        except Exception as e:
            sys.stderr.write('[{}] [ERROR]: {}\n'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), e))
            sys.stderr.flush()
        else:
            set_replied(submission, name, granter, reply_type)

def grant_karma(comment, submission, name, granter, reply_vars):
    try:
        cur.execute("INSERT INTO " + cfg_file.get('karmaflair', 'dbtablename') + " (id, name, granter, type, session_id)" +
                " VALUES (%s, %s, %s, 'successful_award', %s)",
                (submission.id, name, granter, session_id,))
    except psycopg2.IntegrityError as e:
        conn.rollback()

        if e.pgcode == '23505':
            # unique key violation, already granted
            if debug_level == 'NOTICE' or debug_level == 'DEBUG':
                print('[{}] [NOTICE] Karma has already been granted to {} by {}, for submission {}'
                        .format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), name, granter, submission.id))

            handle_reply(comment, submission, name, granter, 'already_awarded', reply_vars)
        else:
            sys.stderr.write('[{}] [ERROR]: {}\n'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), e))
            sys.stderr.flush()
    else:
        conn.commit()

        print('[{}] Karma successfully granted to {} by {}, for submission {}'
                .format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), name, granter, submission.id))

        # reply and update karma flair
        handle_reply(comment, submission, name, granter, 'successful_award', reply_vars)
        set_karma_flair(name)

def set_karma_flair(name):
    try:
        # get their total karma from the db
        cur.execute("SELECT name, count(*) AS karma FROM karma WHERE name=%s AND type='successful_award' GROUP BY name", (name,))
        result = cur.fetchone()

        if result[1]:
            # grab their existing flair info
            subreddit = cfg_file.get('karmaflair', 'subreddit')
            current_flair = r.get_flair(subreddit, name)
            karma_flair_text = str(result[1]) + " Karma"

            r.set_flair(subreddit, name, karma_flair_text, current_flair['flair_css_class'])
    except Exception as e:
        conn.rollback()

        sys.stderr.write('[{}] [ERROR]: {}\n'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), e))
        sys.stderr.flush()
    else:
        conn.commit()

        if debug_level == 'NOTICE' or debug_level == 'DEBUG':
            print('[{}] [NOTICE] Karma flair successfully updated for {} to {}'
                    .format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), name, karma_flair_text))

def process_comment_command(command, command_type, valid_commands, comment, submission, parent=None):
    if command == 'karma' and command_type == '+' and parent is not None:
        # dict of vars for template completion
        reply_vars = {
            'name': comment.author.name,
            'parent_name': parent.author.name,
        }

        # valid grant karma command, check for additional criteria
        while True:
            # request must have correct link flair
            if submission.link_flair_text != cfg_file.get('karmaflair', 'valid_link_flair'):
                handle_reply(comment, submission, parent.author.name, comment.author.name, 'invalid_link_flair', reply_vars)
                break

            # command must be a reply to a comment, unless excepted
            if comment.is_root and submission.link_flair_text != cfg_file.get('karmaflair', 'valid_root_flair'):
                handle_reply(comment, submission, parent.author.name, comment.author.name, 'top_level', reply_vars)
                break

            # user cannot grant karma to themselves
            if parent.author.name == comment.author.name:
                handle_reply(comment, submission, parent.author.name, comment.author.name, 'award_to_self', reply_vars)
                break

            # cannot grant karma to another command
            if re.match("^([\+|-])(" + valid_commands + ")$", parent.body.lower().strip()):
                handle_reply(comment, submission, parent.author.name, comment.author.name, 'award_to_command', reply_vars)
                break

            # user granting karma must be the same as the submitter, or the parent must be the submitter
            if comment.author.name != comment.link_author and parent.author.name != comment.link_author:
                handle_reply(comment, submission, parent.author.name, comment.author.name, 'invalid_author', reply_vars)
                break

            grant_karma(comment, submission, parent.author.name, comment.author.name, reply_vars)
            break

def main():
    global cfg_file
    global debug_level
    global r
    global conn
    global cur
    global session_id

    # read ini and set config
    cfg_file = ConfigParser.RawConfigParser()
    while True:
        try:
            cfg_file.read('karmaflair.ini')
            break
        except Exception as e:
            sys.stderr.write('[{}] [ERROR]: {}\n'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), e))
            sys.stderr.flush()
            sys.exit()

    debug_level = cfg_file.get('debug', 'level')
    mode = cfg_file.get('general', 'mode')
    loop_time = cfg_file.getint('general', 'loop_time')
    subreddit = cfg_file.get('karmaflair', 'subreddit')
    valid_commands = cfg_file.get('karmaflair', 'valid_commands')

    # main loop at set interval if mode is set to 'continuous'
    while True:
        print('[{}] Starting karma flair...'
              .format(datetime.now().strftime('%Y-%m-%d %H:%M:%S')))

        try:
            # connect to db
            conn = psycopg2.connect('dbname=' + cfg_file.get('karmaflair', 'dbname') + ' user=' + cfg_file.get('karmaflair', 'dbuser'))
            cur = conn.cursor()

            # login
            r = Reddit(user_agent=cfg_file.get('auth', 'user_agent'))
            reddit_auth(r, cfg_file, debug_level)

            # generate session id
            session_id = uuid.uuid1()

            # retrieve comments, stream will go back limit # of comments from start
            for comment in helpers.comment_stream(r, subreddit, limit=100, verbosity=0):
                if debug_level == 'DEBUG':
                    print('[{}] [DEBUG] Checking comment posted at {} by {}'
                        .format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), datetime.utcfromtimestamp(comment.created_utc), comment.author.name))

                # command format is to start with a + or - and be the only text in the comment, whitespace is excluded
                match = re.match("^([\+|-])(" + valid_commands + ")$", comment.body.lower().strip())

                if match is not None and match.group(2):
                    # comment contains a valid command
                    command = match.group(2)
                    command_type = match.group(1)

                    submission = r.get_info(thing_id=comment.link_id)
                    parent = r.get_info(thing_id=comment.parent_id)

                    if debug_level == 'DEBUG':
                        print('[{}] [DEBUG] Processing comment command: {}{}'
                            .format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), command_type, command))

                    process_comment_command(command, command_type, valid_commands, comment, submission, parent)

            if mode == 'continuous':
                print('[{}] Pausing karma flair...'
                    .format(datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
                time.sleep(loop_time)
            else:
                cur.close()
                conn.close()
                break
        except KeyboardInterrupt, SystemExit:
            cur.close()
            conn.close()

            print('[{}] Stopping summon karma...'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
            break
        except Exception as e:
            cur.close()
            conn.close()

            sys.stderr.write('[{}] [ERROR]: {}\n'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), e))
            sys.stderr.flush()

            if mode == 'continuous':
                time.sleep(loop_time)
            else:
                break

if __name__ == '__main__':
    main()
<commit_msg>fix to properly handle uuid type<commit_after>#!/usr/bin/env python
# vim: ts=4 sts=4 et sw=4

# A script/bot for reddit to manage user awarded karma
#
# see karmaflair.ini to set options

from praw import Reddit
from praw import helpers
from reddit import reddit_auth
from reddit import reddit_reply_to_comment
import ConfigParser
from string import Template
import psycopg2, psycopg2.extras
import sys
import re
import time
from datetime import datetime
import uuid

# globals
debug_level = ''
cfg_file = None
r = None
conn = None
cur = None

# helper class for tee logging
class FlushOutput(object):
    def __init__(self):
        self.terminal = sys.stdout

    def write(self, message):
        self.terminal.write(message)
        self.terminal.flush()

sys.stdout = FlushOutput()

def get_reply_text(reply_type, reply_vars):
    template = open('tmpl/karmaflair/' + reply_type + '.tpl')

    return Template(template.read()).substitute(reply_vars)

def check_for_reply(submission, name, granter, reply_type=None):
    replied = False

    try:
        if reply_type is not None:
            cur.execute("SELECT replied FROM karma WHERE id=%s AND name=%s AND granter=%s AND type=%s AND replied=TRUE", (submission.id, name, granter, reply_type,))
        else:
            cur.execute("SELECT replied FROM karma WHERE id=%s AND name=%s AND granter=%s AND replied=TRUE", (submission.id, name, granter,))

        result = cur.fetchone()

        if result is not None:
            replied = True

            if debug_level == 'DEBUG':
                print('[{}] [DEBUG] Reply exists for submission {}'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), submission.id))
    except Exception as e:
        conn.rollback()

        sys.stderr.write('[{}] [ERROR]: {}\n'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), e))
        sys.stderr.flush()
    else:
        conn.commit()

        return not replied

def set_replied(submission, name, granter, reply_type):
    try:
        cur.execute("INSERT INTO " + cfg_file.get('karmaflair', 'dbtablename') + " (id, name, granter, type, replied, session_id)" +
                " VALUES (%s, %s, %s, %s, TRUE, %s) ON CONFLICT (id, name, granter, type) DO UPDATE SET replied=TRUE, session_id=%s",
                (submission.id, name, granter, reply_type, session_id, session_id,))
    except Exception as e:
        conn.rollback()

        sys.stderr.write('[{}] [ERROR]: {}\n'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), e))
        sys.stderr.flush()
    else:
        conn.commit()

        if debug_level == 'DEBUG':
            print('[{}] [DEBUG] Message reply has been recorded to {} by {}, for submission {} of type {}'
                    .format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), name, granter, submission.id, reply_type))

def handle_reply(comment, submission, name, granter, reply_type, reply_vars):
    if check_for_reply(submission, name, granter, reply_type):
        try:
            reddit_reply_to_comment(comment, get_reply_text(reply_type, reply_vars))
        except Exception as e:
            sys.stderr.write('[{}] [ERROR]: {}\n'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), e))
            sys.stderr.flush()
        else:
            set_replied(submission, name, granter, reply_type)

def grant_karma(comment, submission, name, granter, reply_vars):
    try:
        cur.execute("INSERT INTO " + cfg_file.get('karmaflair', 'dbtablename') + " (id, name, granter, type, session_id)" +
                " VALUES (%s, %s, %s, 'successful_award', %s)",
                (submission.id, name, granter, session_id,))
    except psycopg2.IntegrityError as e:
        conn.rollback()

        if e.pgcode == '23505':
            # unique key violation, already granted
            if debug_level == 'NOTICE' or debug_level == 'DEBUG':
                print('[{}] [NOTICE] Karma has already been granted to {} by {}, for submission {}'
                        .format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), name, granter, submission.id))

            handle_reply(comment, submission, name, granter, 'already_awarded', reply_vars)
        else:
            sys.stderr.write('[{}] [ERROR]: {}\n'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), e))
            sys.stderr.flush()
    else:
        conn.commit()

        print('[{}] Karma successfully granted to {} by {}, for submission {}'
                .format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), name, granter, submission.id))

        # reply and update karma flair
        handle_reply(comment, submission, name, granter, 'successful_award', reply_vars)
        set_karma_flair(name)

def set_karma_flair(name):
    try:
        # get their total karma from the db
        cur.execute("SELECT name, count(*) AS karma FROM karma WHERE name=%s AND type='successful_award' GROUP BY name", (name,))
        result = cur.fetchone()

        if result[1]:
            # grab their existing flair info
            subreddit = cfg_file.get('karmaflair', 'subreddit')
            current_flair = r.get_flair(subreddit, name)
            karma_flair_text = str(result[1]) + " Karma"

            r.set_flair(subreddit, name, karma_flair_text, current_flair['flair_css_class'])
    except Exception as e:
        conn.rollback()

        sys.stderr.write('[{}] [ERROR]: {}\n'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), e))
        sys.stderr.flush()
    else:
        conn.commit()

        if debug_level == 'NOTICE' or debug_level == 'DEBUG':
            print('[{}] [NOTICE] Karma flair successfully updated for {} to {}'
                    .format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), name, karma_flair_text))

def process_comment_command(command, command_type, valid_commands, comment, submission, parent=None):
    if command == 'karma' and command_type == '+' and parent is not None:
        # dict of vars for template completion
        reply_vars = {
            'name': comment.author.name,
            'parent_name': parent.author.name,
        }

        # valid grant karma command, check for additional criteria
        while True:
            # request must have correct link flair
            if submission.link_flair_text != cfg_file.get('karmaflair', 'valid_link_flair'):
                handle_reply(comment, submission, parent.author.name, comment.author.name, 'invalid_link_flair', reply_vars)
                break

            # command must be a reply to a comment, unless excepted
            if comment.is_root and submission.link_flair_text != cfg_file.get('karmaflair', 'valid_root_flair'):
                handle_reply(comment, submission, parent.author.name, comment.author.name, 'top_level', reply_vars)
                break

            # user cannot grant karma to themselves
            if parent.author.name == comment.author.name:
                handle_reply(comment, submission, parent.author.name, comment.author.name, 'award_to_self', reply_vars)
                break

            # cannot grant karma to another command
            if re.match("^([\+|-])(" + valid_commands + ")$", parent.body.lower().strip()):
                handle_reply(comment, submission, parent.author.name, comment.author.name, 'award_to_command', reply_vars)
                break

            # user granting karma must be the same as the submitter, or the parent must be the submitter
            if comment.author.name != comment.link_author and parent.author.name != comment.link_author:
                handle_reply(comment, submission, parent.author.name, comment.author.name, 'invalid_author', reply_vars)
                break

            grant_karma(comment, submission, parent.author.name, comment.author.name, reply_vars)
            break

def main():
    global cfg_file
    global debug_level
    global r
    global conn
    global cur
    global session_id

    # read ini and set config
    cfg_file = ConfigParser.RawConfigParser()
    while True:
        try:
            cfg_file.read('karmaflair.ini')
            break
        except Exception as e:
            sys.stderr.write('[{}] [ERROR]: {}\n'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), e))
            sys.stderr.flush()
            sys.exit()

    debug_level = cfg_file.get('debug', 'level')
    mode = cfg_file.get('general', 'mode')
    loop_time = cfg_file.getint('general', 'loop_time')
    subreddit = cfg_file.get('karmaflair', 'subreddit')
    valid_commands = cfg_file.get('karmaflair', 'valid_commands')

    # main loop at set interval if mode is set to 'continuous'
    while True:
        print('[{}] Starting karma flair...'
              .format(datetime.now().strftime('%Y-%m-%d %H:%M:%S')))

        try:
            # connect to db
            psycopg2.extras.register_uuid()
            conn = psycopg2.connect('dbname=' + cfg_file.get('karmaflair', 'dbname') + ' user=' + cfg_file.get('karmaflair', 'dbuser'))
            cur = conn.cursor()

            # login
            r = Reddit(user_agent=cfg_file.get('auth', 'user_agent'))
            reddit_auth(r, cfg_file, debug_level)

            # generate session id
            session_id = uuid.uuid1()

            # retrieve comments, stream will go back limit # of comments from start
            for comment in helpers.comment_stream(r, subreddit, limit=100, verbosity=0):
                if debug_level == 'DEBUG':
                    print('[{}] [DEBUG] Checking comment posted at {} by {}'
                        .format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), datetime.utcfromtimestamp(comment.created_utc), comment.author.name))

                # command format is to start with a + or - and be the only text in the comment, whitespace is excluded
                match = re.match("^([\+|-])(" + valid_commands + ")$", comment.body.lower().strip())

                if match is not None and match.group(2):
                    # comment contains a valid command
                    command = match.group(2)
                    command_type = match.group(1)

                    submission = r.get_info(thing_id=comment.link_id)
                    parent = r.get_info(thing_id=comment.parent_id)

                    if debug_level == 'DEBUG':
                        print('[{}] [DEBUG] Processing comment command: {}{}'
                            .format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), command_type, command))

                    process_comment_command(command, command_type, valid_commands, comment, submission, parent)

            if mode == 'continuous':
                print('[{}] Pausing karma flair...'
                    .format(datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
                time.sleep(loop_time)
            else:
                cur.close()
                conn.close()
                break
        except KeyboardInterrupt, SystemExit:
            cur.close()
            conn.close()

            print('[{}] Stopping summon karma...'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
            break
        except Exception as e:
            cur.close()
            conn.close()

            sys.stderr.write('[{}] [ERROR]: {}\n'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), e))
            sys.stderr.flush()

            if mode == 'continuous':
                time.sleep(loop_time)
            else:
                break

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