<commit_before># Copyright (c) 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Skia Telemetry pages."""

import datetime
import json

from google.appengine.ext import db

from base_page import BasePage
import utils

FETCH_LIMIT = 50

TELEMETRY_ADMINS = (
    'rmistry@google.com',)

class TelemetryInfo(db.Model):
  """Contains a single row of Skia Telemetry data."""
  chrome_last_built = db.DateTimeProperty(required=True)
  gce_slaves = db.IntegerProperty(required=True)
  num_webpages = db.IntegerProperty(required=True)
  num_skp_files = db.IntegerProperty(required=True)
  last_updated = db.DateTimeProperty(required=True)
  skia_rev = db.IntegerProperty(required=True)
  chromium_rev = db.IntegerProperty(required=True)

  @classmethod
  def get_telemetry_info(cls):
    return cls.all().fetch(limit=1)[0]

class AdminTasks(db.Model):
  """Data model for Admin tasks."""
  username = db.StringProperty(required=True)
  task_name = db.StringProperty(required=True)
  requested_time = db.DateTimeProperty(required=True)
  completed_time = db.DateTimeProperty()

  @classmethod
  def get_all_admin_tasks(cls):
    return (cls.all()
               .order('-requested_time')
               .fetch(limit=FETCH_LIMIT))

  @classmethod
  def get_admin_task(cls, key):
    return db.GqlQuery(
        'SELECT * FROM AdminTasks WHERE __key__ = Key(\'AdminTasks\', %s);' % (
            key))

  @classmethod
  def get_all_pending_admin_tasks(cls):
    return (cls.all()
               .filter('completed_time =', None)
               .order('requested_time')
               .fetch(limit=FETCH_LIMIT))

class LuaTasks(db.Model):
  """Data model for Lua tasks."""
  username = db.StringProperty(required=True)
  lua_script = db.TextProperty(required=True)
  lua_script_link = db.LinkProperty()
  requested_time = db.DateTimeProperty(required=True)
  completed_time = db.DateTimeProperty()
  lua_output_link = db.LinkProperty()
  description = db.StringProperty()

  @classmethod
  def get_all_pending_lua_tasks(cls):
    return (cls.all()
               .filter('completed_time =', None)
               .order('requested_time')
               .fetch(limit=FETCH_LIMIT))

  @classmethod
  def get_all_lua_tasks(cls):
    return (cls.all()
               .order('-requested_time')
               .fetch(limit=FETCH_LIMIT))

  @classmethod
  def get_all_lua_tasks_of_user(cls, user):
    return (cls.all()
               .filter('username =', user)
               .order('-requested_time')
               .fetch(limit=FETCH_LIMIT))

  @classmethod
  def get_lua_task(cls, key):
    return db.GqlQuery(
        'SELECT * FROM LuaTasks WHERE __key__ = Key(\'LuaTasks\', %s);' %  key)

class TelemetryTasks(db.Model):
  """Data model for Telemetry tasks."""
  username = db.StringProperty(required=True)
  benchmark_name = db.StringProperty(required=True)
  benchmark_arguments = db.StringProperty()
  requested_time = db.DateTimeProperty(required=True)
  completed_time = db.DateTimeProperty()

  @classmethod
  def get_all_pending_telemetry_tasks(cls):
    return (cls.all()
               .filter('completed_time =', None)
               .order('requested_time')
               .fetch(limit=FETCH_LIMIT))

  @classmethod
  def get_all_telemetry_tasks(cls):
    return (cls.all()
               .order('-requested_time')
               .fetch(limit=FETCH_LIMIT))

  @classmethod
  def get_all_telemetry_tasks_of_user(cls, user):
    return (cls.all()
               .filter('username =', user)
               .order('-requested_time')
               .fetch(limit=FETCH_LIMIT))

  @classmethod
  def get_telemetry_task(cls, key):
    return db.GqlQuery(
        'SELECT * FROM TelemetryTasks WHERE __key__ = '
        'Key(\'TelemetryTasks\', %s);' % key)

def add_telemetry_info_to_template(template_values, user_email):
  """Reads TelemetryInfo from the Datastore and adds it to the template."""
  telemetry_info = TelemetryInfo.get_telemetry_info()
  template_values['chrome_last_built'] = telemetry_info.chrome_last_built
  template_values['chromium_rev'] = telemetry_info.chromium_rev
  template_values['skia_rev'] = telemetry_info.skia_rev
  template_values['gce_slaves'] = telemetry_info.gce_slaves
  template_values['num_webpages'] = telemetry_info.num_webpages
  template_values['num_skp_files'] = telemetry_info.num_skp_files
  template_values['last_updated'] = telemetry_info.last_updated
  template_values['admin'] = user_email in TELEMETRY_ADMINS

class LuaScriptPage(BasePage):
  """Displays the lua script page."""

  @utils.require_user
  def get(self):
    return self._handle()

  @utils.require_user
  def post(self):
    requested_time = datetime.datetime.now()
    lua_script = db.Text(self.request.get('lua_script'))
    description = self.request.get('description')
    if not description:
      description = 'None'

    LuaTasks(
        username=self.user.email(),
        lua_script=lua_script,
        requested_time=requested_time,
        description=description).put()

    self.redirect('lua_script')

  def _handle(self):
    """Sets the information to be displayed on the main page."""
    template_values = self.InitializeTemplate(
        'Run Lua scripts on the SKP repository')

    add_telemetry_info_to_template(template_values, self.user.email())

    lua_tasks = LuaTasks.get_all_lua_tasks_of_user(self.user.email())
    template_values['lua_tasks'] = lua_tasks

    self.DisplayTemplate('lua_script.html', template_values)

class AllTasks(BasePage):
  """Displays all tasks (Admin, Lua, Telemetry)."""

  @utils.require_user
  def get(self):
    return self._handle()

  @utils.require_user
  def post(self):
    requested_time = datetime.datetime.now()
    admin_task = self.request.get('admin_task')

    AdminTasks(
        username=self.user.email(),
        task_name=admin_task,
        requested_time=requested_time).put()

    self.redirect('all_tasks')

  def _handle(self):
    """Sets the information to be displayed on the main page."""
    template_values = self.InitializeTemplate(
        'All Tasks')

    add_telemetry_info_to_template(template_values, self.user.email())

    admin_tasks = AdminTasks.get_all_admin_tasks()
    template_values['admin_tasks'] = admin_tasks
    lua_tasks = LuaTasks.get_all_lua_tasks()
    template_values['lua_tasks'] = lua_tasks
    telemetry_tasks = TelemetryTasks.get_all_telemetry_tasks()
    template_values['telemetry_tasks'] = telemetry_tasks

    self.DisplayTemplate('all_tasks.html', template_values)

class LandingPage(BasePage):
  """Displays the main landing page of Skia Telemetry."""

  @utils.require_user
  def get(self):
    return self._handle()

  @utils.require_user
  def post(self):
    benchmark_name = self.request.get('benchmark_name')
    benchmark_arguments = self.request.get('benchmark_arguments')
    requested_time = datetime.datetime.now()

    TelemetryTasks(
        username=self.user.email(),
        benchmark_name=benchmark_name,
        benchmark_arguments=benchmark_arguments,
        requested_time=requested_time).put()

    self.redirect('/skia-telemetry')

  def _handle(self):
    """Sets the information to be displayed on the main page."""

    template_values = self.InitializeTemplate('Skia Telemetry')

    add_telemetry_info_to_template(template_values, self.user.email())

    telemetry_tasks = TelemetryTasks.get_all_telemetry_tasks_of_user(
        self.user.email())
    template_values['telemetry_tasks'] = telemetry_tasks

    self.DisplayTemplate('skia_telemetry_landingpage.html', template_values)

class GetAdminTasksPage(BasePage):
  """Returns a JSON of all pending admin tasks in the queue."""

  def get(self):
    admin_tasks = AdminTasks.get_all_pending_admin_tasks()
    # Create a dict for JSON from the admin pending tasks.
    tasks_dict = {}
    count = 1
    for task in admin_tasks:
      task_dict = {}
      task_dict['key'] = task.key().id_or_name()
      task_dict['username'] = task.username
      task_dict['task_name'] = task.task_name
      tasks_dict[count] = task_dict
      count += 1
    self.response.out.write(json.dumps(tasks_dict, sort_keys=True))

class GetLuaTasksPage(BasePage):
  """Returns a JSON of all pending lua tasks in the queue."""

  def get(self):
    lua_tasks = LuaTasks.get_all_pending_lua_tasks()
    # Create a dict for JSON from the lua pending tasks.
    tasks_dict = {}
    count = 1
    for task in lua_tasks:
      task_dict = {}
      task_dict['key'] = task.key().id_or_name()
      task_dict['username'] = task.username
      task_dict['lua_script'] = task.lua_script
      tasks_dict[count] = task_dict
      count += 1
    self.response.out.write(json.dumps(tasks_dict, sort_keys=True))

class UpdateAdminTasksPage(BasePage):
  """Updates an admin task using its key."""

  def get(self):
    key = int(self.request.get('key'))
    completed_time = datetime.datetime.now()
    admin_task = AdminTasks.get_admin_task(key)[0]
    admin_task.completed_time = completed_time
    admin_task.put()

    self.response.out.write('

Updated the datastore-

')
    self.response.out.write('key: %s
' % key)
    self.response.out.write('completed_time: %s
' % completed_time)

class UpdateTelemetryTasksPage(BasePage):
  """Updates a telemetry task using its key."""

  def get(self):
    key = int(self.request.get('key'))
    completed_time = datetime.datetime.now()
    telemetry_task = TelemetryTasks.get_telemetry_task(key)[0]
    telemetry_task.completed_time = completed_time
    telemetry_task.put()

    self.response.out.write('

Updated the datastore-

')
    self.response.out.write('key: %s
' % key)
    self.response.out.write('completed_time: %s
' % completed_time)

class UpdateLuaTasksPage(BasePage):
  """Updates a lua task using its key."""

  def get(self):
    key = int(self.request.get('key'))
    lua_script_link = self.request.get('lua_script_link')
    lua_output_link = self.request.get('lua_output_link')
    completed_time = datetime.datetime.now()

    lua_task = LuaTasks.get_lua_task(key)[0]
    lua_task.lua_script_link = db.Link(lua_script_link)
    lua_task.lua_output_link = db.Link(lua_output_link)
    lua_task.completed_time = completed_time
    lua_task.put()

    self.response.out.write('

Updated the datastore-

')
    self.response.out.write('key: %s
' % key)
    self.response.out.write('lua_script_link: %s
' % lua_script_link)
    self.response.out.write('lua_output_link: %s
' % lua_output_link)
    self.response.out.write('completed_time: %s
' % completed_time)

class GetTelemetryTasksPage(BasePage):
  """Returns a JSON of all telemetry tasks in the queue."""

  def get(self):
    telemetry_tasks = TelemetryTasks.get_all_pending_telemetry_tasks()
    # Create a dict for JSON from the telemetry pending tasks.
    tasks_dict = {}
    count = 1
    for task in telemetry_tasks:
      task_dict = {}
      task_dict['key'] = task.key().id_or_name()
      task_dict['username'] = task.username
      task_dict['benchmark_name'] = task.benchmark_name
      task_dict['benchmark_arguments'] = task.benchmark_arguments
      tasks_dict[count] = task_dict
      count += 1
    self.response.out.write(json.dumps(tasks_dict, indent=4, sort_keys=True))

class UpdateInfoPage(BasePage):
  """Updates Telemetry info from the GCE master."""

  def get(self):
    chrome_last_built = datetime.datetime.fromtimestamp(
        float(self.request.get('chrome_last_built')))
    chromium_rev = int(self.request.get('chromium_rev'))
    skia_rev = int(self.request.get('skia_rev'))
    gce_slaves = int(self.request.get('gce_slaves'))
    num_webpages = int(self.request.get('num_webpages'))
    num_skp_files = int(self.request.get('num_skp_files'))
    last_updated = datetime.datetime.now()

    # Delete the old entry.
    TelemetryInfo.get_telemetry_info().delete()

    # Add the new updated one.
    TelemetryInfo(
        chrome_last_built=chrome_last_built,
        chromium_rev=chromium_rev,
        skia_rev=skia_rev,
        gce_slaves=gce_slaves,
        num_webpages=num_webpages,
        num_skp_files=num_skp_files,
        last_updated=last_updated).put()

    self.response.out.write('

Added to the datastore-

')
    self.response.out.write('chrome_last_built: %s
' % chrome_last_built)
    self.response.out.write('chromium_rev: %s
' % chromium_rev)
    self.response.out.write('skia_rev: %s
' % skia_rev)
    self.response.out.write('gce_slaves: %s
' % gce_slaves)
    self.response.out.write('num_webpages: %s
' % num_webpages)
    self.response.out.write('num_skp_files: %s
' % num_skp_files)
    self.response.out.write('last_updated: %s' % last_updated)

def bootstrap():
  # Guarantee that at least one instance of the required tables exist.
  if db.GqlQuery('SELECT __key__ FROM TelemetryInfo').get() is None:
    TelemetryInfo(
        chrome_last_built=datetime.datetime(1970, 1, 1),
        skia_rev=0,
        chromium_rev=0,
        gce_slaves=0,
        num_webpages=0,
        num_skp_files=0,
        last_updated=datetime.datetime.now()).put()

  if db.GqlQuery('SELECT __key__ FROM TelemetryTasks').get() is None:
    TelemetryTasks(
        username='Humpty Dumpty',
        benchmark_name='Sitting On A wall',
        benchmark_arguments='--try_not_to_fall',
        requested_time=datetime.datetime.now(),
        completed_time=datetime.datetime.now(),
        logs="I had a great fall").put()

  if db.GqlQuery('SELECT __key__ FROM LuaTasks').get() is None:
    LuaTasks(
        username='Initial Table Creation',
        lua_script='Test Lua Script',
        requested_time=datetime.datetime.now(),
        completed_time=datetime.datetime.now()).put()

  if db.GqlQuery('SELECT __key__ FROM AdminTasks').get() is None:
    AdminTasks(
        username='admin',
        task_name='Initial Table Creation',
        requested_time=datetime.datetime.now(),
        completed_time=datetime.datetime.now()).put()

<commit_msg>Initial AppEngine table creations should be by the Admin.<commit_after># Copyright (c) 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Skia Telemetry pages."""

import datetime
import json

from google.appengine.ext import db

from base_page import BasePage
import utils

FETCH_LIMIT = 50

TELEMETRY_ADMINS = (
    'rmistry@google.com',)

class TelemetryInfo(db.Model):
  """Contains a single row of Skia Telemetry data."""
  chrome_last_built = db.DateTimeProperty(required=True)
  gce_slaves = db.IntegerProperty(required=True)
  num_webpages = db.IntegerProperty(required=True)
  num_skp_files = db.IntegerProperty(required=True)
  last_updated = db.DateTimeProperty(required=True)
  skia_rev = db.IntegerProperty(required=True)
  chromium_rev = db.IntegerProperty(required=True)

  @classmethod
  def get_telemetry_info(cls):
    return cls.all().fetch(limit=1)[0]

class AdminTasks(db.Model):
  """Data model for Admin tasks."""
  username = db.StringProperty(required=True)
  task_name = db.StringProperty(required=True)
  requested_time = db.DateTimeProperty(required=True)
  completed_time = db.DateTimeProperty()

  @classmethod
  def get_all_admin_tasks(cls):
    return (cls.all()
               .order('-requested_time')
               .fetch(limit=FETCH_LIMIT))

  @classmethod
  def get_admin_task(cls, key):
    return db.GqlQuery(
        'SELECT * FROM AdminTasks WHERE __key__ = Key(\'AdminTasks\', %s);' % (
            key))

  @classmethod
  def get_all_pending_admin_tasks(cls):
    return (cls.all()
               .filter('completed_time =', None)
               .order('requested_time')
               .fetch(limit=FETCH_LIMIT))

class LuaTasks(db.Model):
  """Data model for Lua tasks."""
  username = db.StringProperty(required=True)
  lua_script = db.TextProperty(required=True)
  lua_script_link = db.LinkProperty()
  requested_time = db.DateTimeProperty(required=True)
  completed_time = db.DateTimeProperty()
  lua_output_link = db.LinkProperty()
  description = db.StringProperty()

  @classmethod
  def get_all_pending_lua_tasks(cls):
    return (cls.all()
               .filter('completed_time =', None)
               .order('requested_time')
               .fetch(limit=FETCH_LIMIT))

  @classmethod
  def get_all_lua_tasks(cls):
    return (cls.all()
               .order('-requested_time')
               .fetch(limit=FETCH_LIMIT))

  @classmethod
  def get_all_lua_tasks_of_user(cls, user):
    return (cls.all()
               .filter('username =', user)
               .order('-requested_time')
               .fetch(limit=FETCH_LIMIT))

  @classmethod
  def get_lua_task(cls, key):
    return db.GqlQuery(
        'SELECT * FROM LuaTasks WHERE __key__ = Key(\'LuaTasks\', %s);' %  key)

class TelemetryTasks(db.Model):
  """Data model for Telemetry tasks."""
  username = db.StringProperty(required=True)
  benchmark_name = db.StringProperty(required=True)
  benchmark_arguments = db.StringProperty()
  requested_time = db.DateTimeProperty(required=True)
  completed_time = db.DateTimeProperty()

  @classmethod
  def get_all_pending_telemetry_tasks(cls):
    return (cls.all()
               .filter('completed_time =', None)
               .order('requested_time')
               .fetch(limit=FETCH_LIMIT))

  @classmethod
  def get_all_telemetry_tasks(cls):
    return (cls.all()
               .order('-requested_time')
               .fetch(limit=FETCH_LIMIT))

  @classmethod
  def get_all_telemetry_tasks_of_user(cls, user):
    return (cls.all()
               .filter('username =', user)
               .order('-requested_time')
               .fetch(limit=FETCH_LIMIT))

  @classmethod
  def get_telemetry_task(cls, key):
    return db.GqlQuery(
        'SELECT * FROM TelemetryTasks WHERE __key__ = '
        'Key(\'TelemetryTasks\', %s);' % key)

def add_telemetry_info_to_template(template_values, user_email):
  """Reads TelemetryInfo from the Datastore and adds it to the template."""
  telemetry_info = TelemetryInfo.get_telemetry_info()
  template_values['chrome_last_built'] = telemetry_info.chrome_last_built
  template_values['chromium_rev'] = telemetry_info.chromium_rev
  template_values['skia_rev'] = telemetry_info.skia_rev
  template_values['gce_slaves'] = telemetry_info.gce_slaves
  template_values['num_webpages'] = telemetry_info.num_webpages
  template_values['num_skp_files'] = telemetry_info.num_skp_files
  template_values['last_updated'] = telemetry_info.last_updated
  template_values['admin'] = user_email in TELEMETRY_ADMINS

class LuaScriptPage(BasePage):
  """Displays the lua script page."""

  @utils.require_user
  def get(self):
    return self._handle()

  @utils.require_user
  def post(self):
    requested_time = datetime.datetime.now()
    lua_script = db.Text(self.request.get('lua_script'))
    description = self.request.get('description')
    if not description:
      description = 'None'

    LuaTasks(
        username=self.user.email(),
        lua_script=lua_script,
        requested_time=requested_time,
        description=description).put()

    self.redirect('lua_script')

  def _handle(self):
    """Sets the information to be displayed on the main page."""
    template_values = self.InitializeTemplate(
        'Run Lua scripts on the SKP repository')

    add_telemetry_info_to_template(template_values, self.user.email())

    lua_tasks = LuaTasks.get_all_lua_tasks_of_user(self.user.email())
    template_values['lua_tasks'] = lua_tasks

    self.DisplayTemplate('lua_script.html', template_values)

class AllTasks(BasePage):
  """Displays all tasks (Admin, Lua, Telemetry)."""

  @utils.require_user
  def get(self):
    return self._handle()

  @utils.require_user
  def post(self):
    requested_time = datetime.datetime.now()
    admin_task = self.request.get('admin_task')

    AdminTasks(
        username=self.user.email(),
        task_name=admin_task,
        requested_time=requested_time).put()

    self.redirect('all_tasks')

  def _handle(self):
    """Sets the information to be displayed on the main page."""
    template_values = self.InitializeTemplate(
        'All Tasks')

    add_telemetry_info_to_template(template_values, self.user.email())

    admin_tasks = AdminTasks.get_all_admin_tasks()
    template_values['admin_tasks'] = admin_tasks
    lua_tasks = LuaTasks.get_all_lua_tasks()
    template_values['lua_tasks'] = lua_tasks
    telemetry_tasks = TelemetryTasks.get_all_telemetry_tasks()
    template_values['telemetry_tasks'] = telemetry_tasks

    self.DisplayTemplate('all_tasks.html', template_values)

class LandingPage(BasePage):
  """Displays the main landing page of Skia Telemetry."""

  @utils.require_user
  def get(self):
    return self._handle()

  @utils.require_user
  def post(self):
    benchmark_name = self.request.get('benchmark_name')
    benchmark_arguments = self.request.get('benchmark_arguments')
    requested_time = datetime.datetime.now()

    TelemetryTasks(
        username=self.user.email(),
        benchmark_name=benchmark_name,
        benchmark_arguments=benchmark_arguments,
        requested_time=requested_time).put()

    self.redirect('/skia-telemetry')

  def _handle(self):
    """Sets the information to be displayed on the main page."""

    template_values = self.InitializeTemplate('Skia Telemetry')

    add_telemetry_info_to_template(template_values, self.user.email())

    telemetry_tasks = TelemetryTasks.get_all_telemetry_tasks_of_user(
        self.user.email())
    template_values['telemetry_tasks'] = telemetry_tasks

    self.DisplayTemplate('skia_telemetry_landingpage.html', template_values)

class GetAdminTasksPage(BasePage):
  """Returns a JSON of all pending admin tasks in the queue."""

  def get(self):
    admin_tasks = AdminTasks.get_all_pending_admin_tasks()
    # Create a dict for JSON from the admin pending tasks.
    tasks_dict = {}
    count = 1
    for task in admin_tasks:
      task_dict = {}
      task_dict['key'] = task.key().id_or_name()
      task_dict['username'] = task.username
      task_dict['task_name'] = task.task_name
      tasks_dict[count] = task_dict
      count += 1
    self.response.out.write(json.dumps(tasks_dict, sort_keys=True))

class GetLuaTasksPage(BasePage):
  """Returns a JSON of all pending lua tasks in the queue."""

  def get(self):
    lua_tasks = LuaTasks.get_all_pending_lua_tasks()
    # Create a dict for JSON from the lua pending tasks.
    tasks_dict = {}
    count = 1
    for task in lua_tasks:
      task_dict = {}
      task_dict['key'] = task.key().id_or_name()
      task_dict['username'] = task.username
      task_dict['lua_script'] = task.lua_script
      tasks_dict[count] = task_dict
      count += 1
    self.response.out.write(json.dumps(tasks_dict, sort_keys=True))

class UpdateAdminTasksPage(BasePage):
  """Updates an admin task using its key."""

  def get(self):
    key = int(self.request.get('key'))
    completed_time = datetime.datetime.now()
    admin_task = AdminTasks.get_admin_task(key)[0]
    admin_task.completed_time = completed_time
    admin_task.put()

    self.response.out.write('

Updated the datastore-

')
    self.response.out.write('key: %s
' % key)
    self.response.out.write('completed_time: %s
' % completed_time)

class UpdateTelemetryTasksPage(BasePage):
  """Updates a telemetry task using its key."""

  def get(self):
    key = int(self.request.get('key'))
    completed_time = datetime.datetime.now()
    telemetry_task = TelemetryTasks.get_telemetry_task(key)[0]
    telemetry_task.completed_time = completed_time
    telemetry_task.put()

    self.response.out.write('

Updated the datastore-

')
    self.response.out.write('key: %s
' % key)
    self.response.out.write('completed_time: %s
' % completed_time)

class UpdateLuaTasksPage(BasePage):
  """Updates a lua task using its key."""

  def get(self):
    key = int(self.request.get('key'))
    lua_script_link = self.request.get('lua_script_link')
    lua_output_link = self.request.get('lua_output_link')
    completed_time = datetime.datetime.now()

    lua_task = LuaTasks.get_lua_task(key)[0]
    lua_task.lua_script_link = db.Link(lua_script_link)
    lua_task.lua_output_link = db.Link(lua_output_link)
    lua_task.completed_time = completed_time
    lua_task.put()

    self.response.out.write('

Updated the datastore-

')
    self.response.out.write('key: %s
' % key)
    self.response.out.write('lua_script_link: %s
' % lua_script_link)
    self.response.out.write('lua_output_link: %s
' % lua_output_link)
    self.response.out.write('completed_time: %s
' % completed_time)

class GetTelemetryTasksPage(BasePage):
  """Returns a JSON of all telemetry tasks in the queue."""

  def get(self):
    telemetry_tasks = TelemetryTasks.get_all_pending_telemetry_tasks()
    # Create a dict for JSON from the telemetry pending tasks.
    tasks_dict = {}
    count = 1
    for task in telemetry_tasks:
      task_dict = {}
      task_dict['key'] = task.key().id_or_name()
      task_dict['username'] = task.username
      task_dict['benchmark_name'] = task.benchmark_name
      task_dict['benchmark_arguments'] = task.benchmark_arguments
      tasks_dict[count] = task_dict
      count += 1
    self.response.out.write(json.dumps(tasks_dict, indent=4, sort_keys=True))

class UpdateInfoPage(BasePage):
  """Updates Telemetry info from the GCE master."""

  def get(self):
    chrome_last_built = datetime.datetime.fromtimestamp(
        float(self.request.get('chrome_last_built')))
    chromium_rev = int(self.request.get('chromium_rev'))
    skia_rev = int(self.request.get('skia_rev'))
    gce_slaves = int(self.request.get('gce_slaves'))
    num_webpages = int(self.request.get('num_webpages'))
    num_skp_files = int(self.request.get('num_skp_files'))
    last_updated = datetime.datetime.now()

    # Delete the old entry.
    TelemetryInfo.get_telemetry_info().delete()

    # Add the new updated one.
    TelemetryInfo(
        chrome_last_built=chrome_last_built,
        chromium_rev=chromium_rev,
        skia_rev=skia_rev,
        gce_slaves=gce_slaves,
        num_webpages=num_webpages,
        num_skp_files=num_skp_files,
        last_updated=last_updated).put()

    self.response.out.write('

Added to the datastore-

')
    self.response.out.write('chrome_last_built: %s
' % chrome_last_built)
    self.response.out.write('chromium_rev: %s
' % chromium_rev)
    self.response.out.write('skia_rev: %s
' % skia_rev)
    self.response.out.write('gce_slaves: %s
' % gce_slaves)
    self.response.out.write('num_webpages: %s
' % num_webpages)
    self.response.out.write('num_skp_files: %s
' % num_skp_files)
    self.response.out.write('last_updated: %s' % last_updated)

def bootstrap():
  # Guarantee that at least one instance of the required tables exist.
  if db.GqlQuery('SELECT __key__ FROM TelemetryInfo').get() is None:
    TelemetryInfo(
        chrome_last_built=datetime.datetime(1970, 1, 1),
        skia_rev=0,
        chromium_rev=0,
        gce_slaves=0,
        num_webpages=0,
        num_skp_files=0,
        last_updated=datetime.datetime.now()).put()

  if db.GqlQuery('SELECT __key__ FROM TelemetryTasks').get() is None:
    TelemetryTasks(
        username='Admin',
        benchmark_name='Test benchmark',
        benchmark_arguments='--test_arg',
        requested_time=datetime.datetime.now(),
        completed_time=datetime.datetime.now()).put()

  if db.GqlQuery('SELECT __key__ FROM LuaTasks').get() is None:
    LuaTasks(
        username='Admin',
        lua_script='Test Lua Script',
        requested_time=datetime.datetime.now(),
        completed_time=datetime.datetime.now()).put()

  if db.GqlQuery('SELECT __key__ FROM AdminTasks').get() is None:
    AdminTasks(
        username='Admin',
        task_name='Initial Table Creation',
        requested_time=datetime.datetime.now(),
        completed_time=datetime.datetime.now()).put()

<|endoftext|>


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

#Copyright (C) 2012 by Glenn Hickey (hickey@soe.ucsc.edu)
#
#Released under the MIT license, see LICENSE.txtimport unittest

import unittest
import sys
import os
from teHmm.tests.bedTrackTest import TestCase as bedTrackTest
from teHmm.tests.emissionTest import TestCase as emissionTest
from teHmm.tests.hmmTest import TestCase as hmmTest
from teHmm.tests.compareTest import TestCase as compareTest

def allSuites():
    allTests = unittest.TestSuite((unittest.makeSuite(bedTrackTest, 'test'),
                                   unittest.makeSuite(emissionTest, 'test'),
                                   unittest.makeSuite(hmmTest, 'test'),
                                   unittest.makeSuite(compareTest, 'test')))
    return allTests

def main():
    suite = allSuites()
    runner = unittest.TextTestRunner()
    i = runner.run(suite)
    return len(i.failures) + len(i.errors)

if __name__ == '__main__':
    import sys
    sys.exit(main())

<commit_msg>add cfg tests<commit_after>#!/usr/bin/env python

#Copyright (C) 2012 by Glenn Hickey (hickey@soe.ucsc.edu)
#
#Released under the MIT license, see LICENSE.txtimport unittest

import unittest
import sys
import os
from teHmm.tests.bedTrackTest import TestCase as bedTrackTest
from teHmm.tests.emissionTest import TestCase as emissionTest
from teHmm.tests.hmmTest import TestCase as hmmTest
from teHmm.tests.compareTest import TestCase as compareTest
from teHmm.tests.cfgTest import TestCase as cfgTest

def allSuites():
    allTests = unittest.TestSuite((unittest.makeSuite(bedTrackTest, 'test'),
                                   unittest.makeSuite(emissionTest, 'test'),
                                   unittest.makeSuite(hmmTest, 'test'),
                                   unittest.makeSuite(compareTest, 'test'),
                                   unittest.makeSuite(cfgTest, 'test')))
    return allTests

def main():
    suite = allSuites()
    runner = unittest.TextTestRunner()
    i = runner.run(suite)
    return len(i.failures) + len(i.errors)

if __name__ == '__main__':
    import sys
    sys.exit(main())

<|endoftext|>


<commit_before>#!/usr/bin/python

"""Clipster - Clipboard manager."""

from __future__ import print_function
from gi.repository import Gtk, Gdk, GLib, GObject
import signal
import argparse
import json
import socket
import os
import errno
import sys
import select
try:
    # py 3.x
    import configparser
except ImportError:
    # py 2.x
    import ConfigParser as configparser

class Clipster(object):
    """Clipboard Manager."""

    def __init__(self, config, stdin):
        self.stdin = stdin

    def client(self, client_action):
        """Send a signal and (optional) data from STDIN to daemon socket."""

        message = "{0}:{1}:{2}".format(client_action,
                                       config.get('clipster', 'default_board'),
                                       self.stdin)

        sock_c = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        sock_c.connect(config.get('clipster', "socket_file"))
        sock_c.sendall(message.encode('utf-8'))
        sock_c.close()

    class Daemon(object):
        """Handles clipboard events, client requests, stores history."""

        def __init__(self, config):
            """Set up clipboard objects and history dict."""
            self.window = self.p_id = self.c_id = self.sock_s = None
            self.sock_file = config.get('clipster', 'socket_file')
            self.primary = Gtk.Clipboard.get(Gdk.SELECTION_PRIMARY)
            self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
            self.boards = {"PRIMARY": [], "CLIPBOARD": []}
            self.hist_file = config.get('clipster', 'history_file')
            self.run_file = config.get('clipster', 'run_file')
            self.max_input = config.getint('clipster', 'max_input')

        def keypress_handler(self, widget, event):
            """Handler for selection_widget keypress events."""
            # Hide window if ESC is pressed
            if event.keyval == Gdk.KEY_Escape:
                self.window.hide()

        def selection_handler(self, tree, path, col, board):
            """Handler for selection widget 'select' event."""

            # Get selection
            model, treeiter = tree.get_selection().get_selected()
            data = model[treeiter][0]
            self.update_board(board, data)
            model.clear()
            self.window.hide()

        def selection_widget(self, board):
            """GUI window for selecting items from clipboard history."""

            self.window = Gtk.Dialog(title="Clipster")
            self.window.set_size_request(500,500)

            scrolled = Gtk.ScrolledWindow()

            model = Gtk.ListStore(str)
            for item in self.boards[board][::-1]:
                model.append([item])

            tree = Gtk.TreeView(model)

            # Allow alternating color for rows, if WM theme supports it
            tree.set_rules_hint(True)

            renderer = Gtk.CellRendererText()
            column = Gtk.TreeViewColumn("{0} clipboard:".format(board),
                                        renderer, text=0)
            tree.append_column(column)
            # Handle keypresses (looking for escape key)
            self.window.connect("key-press-event", self.keypress_handler)
            # Row is clicked on, or enter pressed
            tree.connect("row-activated", self.selection_handler, board)

            scrolled.add(tree)
            # GtkDialog comes with a vbox already active, so pack into this
            self.window.vbox.pack_start(scrolled, True, True, 0)

            self.window.show_all()

        def read_history_file(self):
            """Read clipboard history from file."""
            try:
                with open(self.hist_file, 'r') as hist_f:
                    self.boards.update(json.load(hist_f))
            except IOError as exc:
                if exc.errno == errno.ENOENT:
                    # Not an error if there is no history file
                    pass

        def write_history_file(self):
            """Write clipboard history to file."""

            with open(self.hist_file, 'w') as hist_f:
                json.dump(self.boards, hist_f)

        def update_board(self, board, data):
            """Update a clipboard."""

            getattr(self, board.lower()).set_text(data, -1)

        def update_history(self, board, text):
            """Update the in-memory clipboard history."""

            # If an item already exists in the clipboard, remove it
            if text in self.boards[board]:
                self.boards[board].remove(text)
            self.boards[board].append(text)
            print(self.boards[board])

        def owner_change(self, board, event):
            """Handler for owner-change clipboard events."""

            selection = str(event.selection)
            if selection == "PRIMARY":
                event_id = self.p_id
            else:
                event_id = self.c_id
            # Some apps update primary during mouse drag (chrome)
            # Block at start to prevent repeated triggering
            board.handler_block(event_id)
            # FIXME: this devs hack is a bit verbose. Look instead at
            # gdk_seat_get_pointer -> gdk_device_get_state
            # once GdkSeat is in stable
            # FIXME: Emacs does this with ctrl-space + kb movement.
            # How to deal with this?
            # Something to do with change-owner always being same owner?
            mouse = None
            for dev in self.window.get_display().get_device_manager().list_devices(Gdk.DeviceType.MASTER):
                if dev.get_source() == Gdk.InputSource.MOUSE:
                    mouse = dev
                    break
            while Gdk.ModifierType.BUTTON1_MASK & self.window.get_root_window().get_device_position(mouse)[3]:
                # Do nothing while mouse button is held down (selection dragging)
                pass

            # Read clipboard
            text = board.wait_for_text()
            if text:
                self.update_history(selection, text)
            # Unblock event handling
            board.handler_unblock(event_id)
            return text

        def socket_listen(self, sock_s, _):
            """Establish a socket listening for client connections."""

            conn, _ = sock_s.accept()
            conn.setblocking(0)
            data = []
            recv_total = 0
            while True:
                try:
                    recv = conn.recv(8192)
                    if not recv:
                        break
                    data.append(recv.decode('utf-8'))
                    recv_total += len(recv)
                    if recv_total > self.max_input:
                        break
                except socket.error:
                    break
            if data:
                sent = ''.join(data)
                sig, board, content = sent.split(':', 2)
                if sig == "SELECT":
                    self.selection_widget(board)
                elif sig == "BOARD":
                    if content:
                        self.update_board(board, content)
            conn.close()
            return True

        def prepare_files(self):
            """Ensure that all files and sockets used
            by the daemon are available."""

            # check for existing run_file, and tidy up if appropriate
            try:
                with open(self.run_file, 'r') as runf_r:
                    pid = int(runf_r.read())
                    try:
                        # Do nothing, but raise an error if no such process
                        os.kill(pid, 0)
                        print("Daemon already running: pid {0}".format(pid))
                        sys.exit(1)
                    except OSError:
                        try:
                            os.unlink(self.run_file)
                        except IOError as exc:
                            if exc.errno == errno.ENOENT:
                                # File already gone
                                pass
                            else:
                                raise
            except IOError as exc:
                if exc.errno == errno.ENOENT:
                    pass

            # Create pid file
            with open(self.run_file, 'w') as runf_w:
                runf_w.write(str(os.getpid()))

            # Create the clipster dir if necessary
            try:
                os.makedirs(clipster_dir)
            except OSError as exc:
                if exc.errno == errno.EEXIST:
                    # ok if directory already exists
                    pass

            # Read in history from file
            self.read_history_file()

            # Create the socket
            try:
                os.unlink(self.sock_file)
            except OSError as exc:
                if exc.errno == errno.ENOENT:
                    pass

            self.sock_s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
            self.sock_s.setblocking(0)
            self.sock_s.bind(self.sock_file)
            self.sock_s.listen(5)

        def exit(self):
            """Clean up things before exiting."""

            try:
                os.unlink(self.sock_file)
            except OSError:
                print("Warning: Failed to remove socket file: {0}".format(self.sock_file))
            try:
                os.unlink(self.run_file)
            except OSError:
                print("Warning:Failed to remove run file: {0}".format(self.run_file))
            self.write_history_file()
            sys.exit(0)

        def run(self):
            """Launch the clipboard manager daemon.
            Connect signals for clipboard events, and client (socket) connections."""

            # Set up socket, pid file etc
            self.prepare_files()

            # We need to get the display instance from the window
            # for use in obtaining mouse state.
            # POPUP windows can do this without having to first show the window
            self.window = Gtk.Window(type=Gtk.WindowType.POPUP)

            # Handle clipboard changes
            self.p_id = self.primary.connect('owner-change', self.owner_change)
            self.c_id = self.clipboard.connect('owner-change', self.owner_change)
            # Handle socket connections
            GObject.io_add_watch(self.sock_s, GObject.IO_IN, self.socket_listen)
            # Handle unix signals
            GLib.unix_signal_add(GLib.PRIORITY_HIGH, signal.SIGINT, self.exit)
            GLib.unix_signal_add(GLib.PRIORITY_HIGH, signal.SIGTERM, self.exit)
            Gtk.main()

if __name__ == "__main__":

    # Set a default config file
    clipster_dir = os.path.join(os.environ.get('HOME'), ".clipster")
    config_file = os.path.join(clipster_dir, "config")

    parser = argparse.ArgumentParser(description='Clipster clipboard manager.')
    parser.add_argument('-f', '--config', action="store",
                        default=config_file,
                        help="Path to config file.")
    parser.add_argument('-p', '--primary', action="store_true",
                        help="Write STDIN to the PRIMARY clipboard.")
    parser.add_argument('-c', '--clipboard', action="store_true",
                        help="Write STDIN to the CLIPBOARD clipboard.")
    parser.add_argument('-d', '--daemon', action="store_true",
                        help="Launch the daemon.")
    parser.add_argument('-s', '--select', action="store_true",
                        help="Launch the clipboard history selection window.")

    args = parser.parse_args()

    # Set some config defaults
    config_defaults = {"clipster_dir": clipster_dir,
                       "default_board": "PRIMARY",
                       "history_file": "%(clipster_dir)s/history",
                       "socket_file": "%(clipster_dir)s/clipster_sock",
                       "run_file":  "%(clipster_dir)s/clipster.pid",
                       "max_input": "50000",}

    config = configparser.SafeConfigParser(config_defaults)
    config.add_section('clipster')

    # If a config file arg is passed in, try reading it
    if args.config:
        config.read(args.config)
    else:
        # Try reading the config file from the defauilt dir
        config.read(config_file)

    # Override clipdir, if it's an option in the config file
    try:
        clipdir = config.get('clipster', 'clipster_dir')
    except configparser.Error:
        # Otherwise, set the value back into the config
        config.set('clipster', 'clipster_dir', clipdir)

    stdin = ""
    if not sys.stdin.isatty():
        if sys.stdin in select.select([sys.stdin], [], [], 0)[0]:
            stdin = sys.stdin.read()

    clipster = Clipster(config, stdin)

    # Launch the daemon
    if args.daemon:
        clipster.Daemon(config).run()

    client_action = "BOARD"
    if args.select:
        client_action = "SELECT"

    if args.primary:
        config.set('clipster', 'default_board', 'PRIMARY')
    elif args.clipboard:
        config.set('clipster', 'default_board', 'CLIPBOARD')

    clipster.client(client_action)
<commit_msg>Refactor main(). Pass config properly. pylint/pep8.<commit_after>#!/usr/bin/python

"""Clipster - Clipboard manager."""

from __future__ import print_function
from gi.repository import Gtk, Gdk, GLib, GObject
import signal
import argparse
import json
import socket
import os
import errno
import sys
import select
try:
    # py 3.x
    import configparser
except ImportError:
    # py 2.x
    import ConfigParser as configparser

class Clipster(object):
    """Clipboard Manager."""

    def __init__(self, config, stdin):
        self.config = config
        self.stdin = stdin

    def client(self, client_action):
        """Send a signal and (optional) data from STDIN to daemon socket."""

        message = "{0}:{1}:{2}".format(client_action,
                                       self.config.get('clipster',
                                                       'default_selection'),
                                       self.stdin)

        sock_c = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        sock_c.connect(self.config.get('clipster', "socket_file"))
        sock_c.sendall(message.encode('utf-8'))
        sock_c.close()

    class Daemon(object):
        """Handles clipboard events, client requests, stores history."""

        def __init__(self, config):
            """Set up clipboard objects and history dict."""
            self.config = config
            self.window = self.p_id = self.c_id = self.sock_s = None
            self.sock_file = self.config.get('clipster', 'socket_file')
            self.primary = Gtk.Clipboard.get(Gdk.SELECTION_PRIMARY)
            self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
            self.boards = {"PRIMARY": [], "CLIPBOARD": []}
            self.hist_file = self.config.get('clipster', 'history_file')
            self.pid_file = self.config.get('clipster', 'pid_file')
            self.max_input = self.config.getint('clipster', 'max_input')

        def keypress_handler(self, widget, event):
            """Handler for selection_widget keypress events."""

            # Hide window if ESC is pressed
            if event.keyval == Gdk.KEY_Escape:
                self.window.hide()

        def selection_handler(self, tree, path, col, board):
            """Handler for selection widget 'select' event."""

            # Get selection
            model, treeiter = tree.get_selection().get_selected()
            data = model[treeiter][0]
            self.update_board(board, data)
            model.clear()
            self.window.hide()

        def selection_widget(self, board):
            """GUI window for selecting items from clipboard history."""

            self.window = Gtk.Dialog(title="Clipster")
            self.window.set_size_request(500,500)

            scrolled = Gtk.ScrolledWindow()

            model = Gtk.ListStore(str)
            for item in self.boards[board][::-1]:
                model.append([item])

            tree = Gtk.TreeView(model)

            # Allow alternating color for rows, if WM theme supports it
            tree.set_rules_hint(True)

            renderer = Gtk.CellRendererText()
            column = Gtk.TreeViewColumn("{0} clipboard:".format(board),
                                        renderer, text=0)
            tree.append_column(column)
            # Handle keypresses (looking for escape key)
            self.window.connect("key-press-event", self.keypress_handler)
            # Row is clicked on, or enter pressed
            tree.connect("row-activated", self.selection_handler, board)

            scrolled.add(tree)
            # GtkDialog comes with a vbox already active, so pack into this
            self.window.vbox.pack_start(scrolled, True, True, 0)

            self.window.show_all()

        def read_history_file(self):
            """Read clipboard history from file."""
            try:
                with open(self.hist_file, 'r') as hist_f:
                    self.boards.update(json.load(hist_f))
            except IOError as exc:
                if exc.errno == errno.ENOENT:
                    # Not an error if there is no history file
                    pass

        def write_history_file(self):
            """Write clipboard history to file."""

            with open(self.hist_file, 'w') as hist_f:
                json.dump(self.boards, hist_f)

        def update_board(self, board, data):
            """Update a clipboard."""

            getattr(self, board.lower()).set_text(data, -1)

        def update_history(self, board, text):
            """Update the in-memory clipboard history."""

            # If an item already exists in the clipboard, remove it
            if text in self.boards[board]:
                self.boards[board].remove(text)
            self.boards[board].append(text)
            print(self.boards[board])

        def owner_change(self, board, event):
            """Handler for owner-change clipboard events."""

            selection = str(event.selection)
            if selection == "PRIMARY":
                event_id = self.p_id
            else:
                event_id = self.c_id
            # Some apps update primary during mouse drag (chrome)
            # Block at start to prevent repeated triggering
            board.handler_block(event_id)
            # FIXME: this devs hack is a bit verbose. Look instead at
            # gdk_seat_get_pointer -> gdk_device_get_state
            # once GdkSeat is in stable
            # FIXME: Emacs does this with ctrl-space + kb movement.
            # How to deal with this?
            # Something to do with change-owner always being same owner?
            mouse = None
            for dev in self.window.get_display().get_device_manager().list_devices(Gdk.DeviceType.MASTER):
                if dev.get_source() == Gdk.InputSource.MOUSE:
                    mouse = dev
                    break
            while Gdk.ModifierType.BUTTON1_MASK & self.window.get_root_window().get_device_position(mouse)[3]:
                # Do nothing while mouse button is held down (selection drag)
                pass

            # Read clipboard
            text = board.wait_for_text()
            if text:
                self.update_history(selection, text)
            # Unblock event handling
            board.handler_unblock(event_id)
            return text

        def socket_listen(self, sock_s, _):
            """Establish a socket listening for client connections."""

            conn, _ = sock_s.accept()
            conn.setblocking(0)
            data = []
            recv_total = 0
            while True:
                try:
                    recv = conn.recv(8192)
                    if not recv:
                        break
                    data.append(recv.decode('utf-8'))
                    recv_total += len(recv)
                    if recv_total > self.max_input:
                        break
                except socket.error:
                    break
            if data:
                sent = ''.join(data)
                sig, board, content = sent.split(':', 2)
                if sig == "SELECT":
                    self.selection_widget(board)
                elif sig == "BOARD":
                    if content:
                        self.update_board(board, content)
            conn.close()
            return True

        def prepare_files(self):
            """Ensure that all files and sockets used
            by the daemon are available."""

            # check for existing pid_file, and tidy up if appropriate
            try:
                with open(self.pid_file, 'r') as runf_r:
                    pid = int(runf_r.read())
                    try:
                        # Do nothing, but raise an error if no such process
                        os.kill(pid, 0)
                        print("Daemon already running: pid {0}".format(pid))
                        sys.exit(1)
                    except OSError:
                        try:
                            os.unlink(self.pid_file)
                        except IOError as exc:
                            if exc.errno == errno.ENOENT:
                                # File already gone
                                pass
                            else:
                                raise
            except IOError as exc:
                if exc.errno == errno.ENOENT:
                    pass

            # Create pid file
            with open(self.pid_file, 'w') as runf_w:
                runf_w.write(str(os.getpid()))

            # Create the clipster dir if necessary
            try:
                os.makedirs(self.config.get('clipster', 'clipster_dir'))
            except OSError as exc:
                if exc.errno == errno.EEXIST:
                    # ok if directory already exists
                    pass

            # Read in history from file
            self.read_history_file()

            # Create the socket
            try:
                os.unlink(self.sock_file)
            except OSError as exc:
                if exc.errno == errno.ENOENT:
                    pass

            self.sock_s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
            self.sock_s.setblocking(0)
            self.sock_s.bind(self.sock_file)
            self.sock_s.listen(5)

        def exit(self):
            """Clean up things before exiting."""

            try:
                os.unlink(self.sock_file)
            except OSError:
                print("Warning: Failed to remove socket file: {0}".format(self.sock_file))
            try:
                os.unlink(self.pid_file)
            except OSError:
                print("Warning: Failed to remove run file: {0}".format(self.pid_file))
            self.write_history_file()
            sys.exit(0)

        def run(self):
            """Launch the clipboard manager daemon.
            Listen for clipboard events & client socket connections."""

            # Set up socket, pid file etc
            self.prepare_files()

            # We need to get the display instance from the window
            # for use in obtaining mouse state.
            # POPUP windows can do this without having to first show the window
            self.window = Gtk.Window(type=Gtk.WindowType.POPUP)

            # Handle clipboard changes
            self.p_id = self.primary.connect('owner-change',
                                             self.owner_change)
            self.c_id = self.clipboard.connect('owner-change',
                                               self.owner_change)
            # Handle socket connections
            GObject.io_add_watch(self.sock_s, GObject.IO_IN,
                                 self.socket_listen)
            # Handle unix signals
            GLib.unix_signal_add(GLib.PRIORITY_HIGH, signal.SIGINT, self.exit)
            GLib.unix_signal_add(GLib.PRIORITY_HIGH, signal.SIGTERM, self.exit)
            Gtk.main()

def main():
    """Start the application."""

    # Set a default config file
    clipster_dir = os.path.join(os.environ.get('HOME'), ".clipster")
    config_file = os.path.join(clipster_dir, "config")

    parser = argparse.ArgumentParser(description='Clipster clipboard manager.')
    parser.add_argument('-f', '--config', action="store",
                        default=config_file,
                        help="Path to config file.")
    parser.add_argument('-p', '--primary', action="store_true",
                        help="Write STDIN to the PRIMARY clipboard.")
    parser.add_argument('-c', '--clipboard', action="store_true",
                        help="Write STDIN to the CLIPBOARD clipboard.")
    parser.add_argument('-d', '--daemon', action="store_true",
                        help="Launch the daemon.")
    parser.add_argument('-s', '--select', action="store_true",
                        help="Launch the clipboard history selection window.")

    args = parser.parse_args()

    # Set some config defaults
    config_defaults = {"clipster_dir": clipster_dir,  # clipster 'root' dir
                       "default_selection": "PRIMARY",  # PRIMARY or CLIPBOARD
                       "history_file": "%(clipster_dir)s/history",
                       "socket_file": "%(clipster_dir)s/clipster_sock",
                       "pid_file":  "%(clipster_dir)s/clipster.pid",
                       "max_input": "50000",}  # max length of selection input

    config = configparser.SafeConfigParser(config_defaults)
    config.add_section('clipster')

    # If a config file arg is passed in, try reading it
    if args.config:
        config.read(args.config)
    else:
        # Try reading the config file from the defauilt dir
        config.read(config_file)

    # Override clipdir, if it's an option in the config file
    try:
        clipdir = config.get('clipster', 'clipster_dir')
    except configparser.Error:
        # Otherwise, set the value back into the config
        config.set('clipster', 'clipster_dir', clipdir)

    stdin = ""
    if not sys.stdin.isatty():
        if sys.stdin in select.select([sys.stdin], [], [], 0)[0]:
            stdin = sys.stdin.read()

    clipster = Clipster(config, stdin)

    # Launch the daemon
    if args.daemon:
        clipster.Daemon(config).run()

    client_action = "BOARD"
    if args.select:
        client_action = "SELECT"

    if args.primary:
        config.set('clipster', 'default_selection', 'PRIMARY')
    elif args.clipboard:
        config.set('clipster', 'default_selection', 'CLIPBOARD')

    clipster.client(client_action)

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


<commit_before>import hashlib

from aql_value import Value
from aql_depends_value import DependsValue
from aql_logging import logError
from aql_utils import toSequence

class Node (object):

  __slots__ = \
  (
    'builder',
    'source_nodes',
    'source_values',
    'dep_nodes',
    'dep_values',

    'idep_values',
    'target_values',
    'itarget_values',

    'name',
    'long_name',
    'targets_name',
    'itargets_name',
    'deps_name',
    'ideps_name',

    'sources_value',
    'deps_value',
  )

  #//-------------------------------------------------------//

  def   __init__( self, builder, sources ):

    self.builder = builder
    self.source_nodes, self.source_values = self.__getSourceNodes( sources )
    self.dep_values = []
    self.dep_nodes = []

  #//=======================================================//

  def   __getSourceNodes( self, sources ):

    source_nodes = []
    source_values = []

    source_nodes_append = source_nodes.append
    source_values_append = source_values.append

    for source in toSequence( sources ):
      if isinstance(source, Node):
        source_nodes_append( source )
      elif isinstance(source, Value):
        source_values_append( source )
      else:
        raise Exception( "Unknown source type: %s" % type(source) )

    return source_nodes, source_values

  #//=======================================================//

  def   __getLongName( self ):
    names = []
    names_append = names.append
    names_append( self.builder.long_name )

    for source in self.source_nodes:
      names += source.long_name

    for source in self.source_values:
      names_append( source.name )

    names.sort()

    return names

  #//=======================================================//

  def   __getNames( self ):
     chcksum = hashlib.md5()

     for name in self.long_name:
       chcksum.update( name.encode() )

     name = chcksum.digest()
     chcksum.update( b'target_values' )
     targets_name = chcksum.digest()

     chcksum.update( b'itarget_values' )
     itargets_name = chcksum.digest()

     chcksum.update( b'dep_values' )
     deps_name = chcksum.digest()

     chcksum.update( b'idep_values' )
     ideps_name = chcksum.digest()

     return name, targets_name, itargets_name, deps_name, ideps_name

  #//=======================================================//

  def   __sourcesValue( self ):
    source_values = list(self.source_values)

    for node in self.source_nodes:
      source_values += node.target_values

    return DependsValue( self.name, source_values )

  #//=======================================================//

  def   __depsValue( self ):
    dep_values = list(self.dep_values)

    for node in self.dep_nodes:
      dep_values += node.target_values

    dep_values += self.builder.values()

    return DependsValue( self.deps_name, dep_values )

  #//=======================================================//

  def   __builderValue( self ):
    dep_values = list(self.dep_values)

    for node in self.dep_nodes:
      dep_values += node.target_values

    return DependsValue( self.deps_name, dep_values )

  #//=======================================================//

  def   __getattr__( self, attr ):
    if attr in ('name', 'targets_name', 'itargets_name', 'deps_name', 'ideps_name'):
      self.name, self.targets_name, self.itargets_name, self.deps_name, self.ideps_name = self.__getNames()
      return getattr(self, attr)

    elif attr == 'long_name':
      long_name = self.__getLongName()
      self.long_name = long_name
      return long_name

    elif attr == 'sources_value':
      self.sources_value = self.__sourcesValue()
      return self.sources_value

    elif attr == 'deps_value':
      self.deps_value = self.__depsValue()
      return self.deps_value

    raise AttributeError("Unknown attribute: '%s'" % str(attr) )

  #//=======================================================//

  def   __save( self, vfile ):

    values = []
    values += self.source_values
    values += self.dep_values
    values += self.builder.values()
    values += self.idep_values
    values += self.target_values
    values += self.itarget_values

    values.append( self.sources_value )
    values.append( self.deps_value )
    values.append( DependsValue( self.ideps_name,     self.idep_values )    )
    values.append( DependsValue( self.targets_name,   self.target_values )  )
    values.append( DependsValue( self.itargets_name,  self.itarget_values ) )

    vfile.addValues( values )

  #//=======================================================//

  def   build( self, vfile ):

    self.target_values, self.itarget_values, self.idep_values = self.builder.build( self.sources_value.content )

    self.__save( vfile )

  #//=======================================================//

  def   actual( self, vfile ):
    sources_value = self.sources_value
    deps_value    = self.deps_value

    targets_value   = DependsValue( self.targets_name   )
    itargets_value  = DependsValue( self.itargets_name  )
    ideps_value     = DependsValue( self.ideps_name     )

    values = [ sources_value, deps_value, targets_value, itargets_value, ideps_value ]
    values = vfile.findValues( values )

    if sources_value != values.pop(0):
      return False

    if deps_value != values.pop(0):
      return False

    for value in values:
      if not value.actual():
        return False

    targets_value, itargets_value, ideps_value = values

    self.target_values  = list( targets_value.content   )
    self.itarget_values = list( itargets_value.content  )
    self.idep_values    = list( ideps_value.content     )

    return True

<commit_msg><commit_after>import hashlib

from aql_value import Value
from aql_depends_value import DependsValue
from aql_logging import logError
from aql_utils import toSequence

class Node (object):

  __slots__ = \
  (
    'builder',
    'source_nodes',
    'source_values',
    'dep_nodes',
    'dep_values',

    'idep_values',
    'target_values',
    'itarget_values',

    'name',
    'long_name',
    'targets_name',
    'itargets_name',
    'deps_name',
    'ideps_name',

    'sources_value',
    'deps_value',
  )

  #//-------------------------------------------------------//

  def   __init__( self, builder, sources ):

    self.builder = builder
    self.source_nodes, self.source_values = self.__getSourceNodes( sources )
    self.dep_values = []
    self.dep_nodes = []

  #//=======================================================//

  def   __getSourceNodes( self, sources ):

    source_nodes = []
    source_values = []

    source_nodes_append = source_nodes.append
    source_values_append = source_values.append

    for source in toSequence( sources ):
      if isinstance(source, Node):
        source_nodes_append( source )
      elif isinstance(source, Value):
        source_values_append( source )
      else:
        raise Exception( "Unknown source type: %s" % type(source) )

    return source_nodes, source_values

  #//=======================================================//

  def   __getLongName( self ):
    names = []
    names_append = names.append
    names_append( self.builder.long_name )

    for source in self.source_nodes:
      names += source.long_name

    for source in self.source_values:
      names_append( source.name )

    names.sort()

    return names

  #//=======================================================//

  def   __getNames( self ):
     chcksum = hashlib.md5()

     for name in self.long_name:
       chcksum.update( name.encode() )

     name = chcksum.digest()
     chcksum.update( b'target_values' )
     targets_name = chcksum.digest()

     chcksum.update( b'itarget_values' )
     itargets_name = chcksum.digest()

     chcksum.update( b'dep_values' )
     deps_name = chcksum.digest()

     chcksum.update( b'idep_values' )
     ideps_name = chcksum.digest()

     return name, targets_name, itargets_name, deps_name, ideps_name

  #//=======================================================//

  def   __sourcesValue( self ):
    source_values = list(self.source_values)

    for node in self.source_nodes:
      source_values += node.target_values

    return DependsValue( self.name, source_values )

  #//=======================================================//

  def   __depsValue( self ):
    dep_values = list(self.dep_values)

    for node in self.dep_nodes:
      dep_values += node.target_values

    dep_values += self.builder.values()

    return DependsValue( self.deps_name, dep_values )

  #//=======================================================//

  def   __getattr__( self, attr ):
    if attr in ('name', 'targets_name', 'itargets_name', 'deps_name', 'ideps_name'):
      self.name, self.targets_name, self.itargets_name, self.deps_name, self.ideps_name = self.__getNames()
      return getattr(self, attr)

    elif attr == 'long_name':
      long_name = self.__getLongName()
      self.long_name = long_name
      return long_name

    elif attr == 'sources_value':
      self.sources_value = self.__sourcesValue()
      return self.sources_value

    elif attr == 'deps_value':
      self.deps_value = self.__depsValue()
      return self.deps_value

    raise AttributeError("Unknown attribute: '%s'" % str(attr) )

  #//=======================================================//

  def   __save( self, vfile ):

    values = []
    values += self.source_values
    values += self.dep_values
    values += self.builder.values()
    values += self.idep_values
    values += self.target_values
    values += self.itarget_values

    values.append( self.sources_value )
    values.append( self.deps_value )
    values.append( DependsValue( self.ideps_name,     self.idep_values )    )
    values.append( DependsValue( self.targets_name,   self.target_values )  )
    values.append( DependsValue( self.itargets_name,  self.itarget_values ) )

    vfile.addValues( values )

  #//=======================================================//

  def   build( self, vfile ):

    self.target_values, self.itarget_values, self.idep_values = self.builder.build( self.sources_value.content )

    self.__save( vfile )

  #//=======================================================//

  def   actual( self, vfile ):
    sources_value = self.sources_value
    deps_value    = self.deps_value

    targets_value   = DependsValue( self.targets_name   )
    itargets_value  = DependsValue( self.itargets_name  )
    ideps_value     = DependsValue( self.ideps_name     )

    values = [ sources_value, deps_value, targets_value, itargets_value, ideps_value ]
    values = vfile.findValues( values )

    if sources_value != values.pop(0):
      return False

    if deps_value != values.pop(0):
      return False

    for value in values:
      if not value.actual():
        return False

    targets_value, itargets_value, ideps_value = values

    self.target_values  = list( targets_value.content   )
    self.itarget_values = list( itargets_value.content  )
    self.idep_values    = list( ideps_value.content     )

    return True

<|endoftext|>


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

import os
import sys
import curses
import threading

import pafy
import pyperclip

TITLE = '.:. Clipy .:.'
VIDEO = None

class Window(object):
    """Window absraction with border"""
    testing = False
    resource = None
    stream = None
    video = None

    def __init__(self, stdscr, lines, cols, y, x):
        self.stdscr = stdscr
        self.box = curses.newwin(lines, cols, y, x)
        Y, X = self.box.getmaxyx()
        # import pdb; pdb.set_trace()
        self.win = self.box.subwin(Y-2, X-4, y+1, 2)
        self.win.scrollok(True)
        self.win.keypad(True)

        self.box.box()

        # self._coord(Y-4, X-5)
        # for i in range(Y-2):
        #     self._coord(i, i)

    def _coord(self, y, x):
        self.win.addstr(y, x, '+ {} {}'.format(y, x))

    def display(self):
        if self.video:
            self.printstr(self.video)
            self.printstr('allstreams: {}'.format(self.video.allstreams))

    def freshen(self):
        self.stdscr.noutrefresh()
        self.box.noutrefresh()
        self.win.noutrefresh()
        if not self.testing:
            curses.doupdate()

    def getch(self):
        return self.win.getch()

    def printstr(self, object, error=False):
        string = '{}\n'.format(object)
        if error:
            self.win.addstr(string, curses.A_BOLD | curses.color_pair(1))
        else:
            self.win.addstr(string)
        self.freshen()

    def progress(self, total, *progress_stats):
        status_string = ('{:,} Bytes [{:.2%}] received. Rate: [{:4.0f} '
                         'KB/s].  ETA: [{:.0f} secs]')
        status = status_string.format(*progress_stats)
        self.stdscr.addstr(0, 15, status, curses.A_REVERSE)
        self.freshen()

def inquire(panel, console):
    console.printstr('Inquiring')
    try:
        if panel.resource:
            console.printstr('Resourse found: {}'.format(panel.resource))
            panel.video = pafy.new(panel.resource)
            panel.resource = None

    except (OSError, ValueError) as e:
        console.printstr(e, error=True)

def select(panel, console):
    if panel.video:
        panel.printstr('Streams:')
        for i, stream in enumerate(panel.video.allstreams):
            panel.printstr('{}: {} {} {} {}'.format(i, stream.mediatype, stream.quality, stream.extension, stream.notes))
        panel.printstr('Press one of the numbers above to download (0-{})'.format(i))

    else:
        console.printstr('No video to select streams, Inquire first', error=True)

def download(panel, console):
    download_dir = os.path.expanduser('~')
    try:
        stream = panel.stream
        path = '%s.%s' % (os.path.join(download_dir, stream.title), stream.extension)
        console.printstr('Downloading {} to {}'.format(stream, download_dir))
        f = stream.download(filepath=path, quiet=True, callback=panel.progress)

    except (OSError, ValueError, FileNotFoundError) as e:
        console.printstr(e, error=True)

    else:
        if f:
            console.printstr('Downloaded: "{}"'.format(f))

def cancel(panel, console):
    if panel.stream:
        console.printstr('Cancelling {}'.format(panel.stream))
        if panel.stream.cancel():
            console.printstr('Cancelled "{}"'.format(panel.stream.title))
    else:
        console.printstr('Nothing to cancel')

    panel.stream = None

def spawn(panel, console, index=None):
    if panel.video is None:
        console.printstr('No video to download, Inquire first', error=True)
        return
    if index is None:
        try:
            panel.stream = panel.video.getbest(preftype="mp4")
        except (OSError, ValueError) as e:
            console.printstr(e, error=True)
            return
    else:
        if index >= len(panel.video.allstreams):
            console.printstr('Stream {} not available'.format(index), error=True)
            return
        else:
            panel.stream = panel.video.allstreams[index]

    t = threading.Thread(target=download, args=(panel, console))
    t.daemon = True
    t.start()

def loop(stdscr, panel, console):
    KEYS_CANCEL   = (ord('c'), ord('C'))
    KEYS_DOWNLOAD = (ord('d'), ord('D'))
    KEYS_INQUIRE  = (ord('i'), ord('I'))
    KEYS_NUMERIC  = range(48, 58)
    KEYS_PASTE    = (ord('p'), ord('P'))
    KEYS_QUIT     = (ord('q'), ord('Q'), 27)  # 27 is escape
    KEYS_SELECT   = (ord('s'), ord('S'))

    while True:
        c = panel.getch()

        if c in KEYS_QUIT:
            break

        if c in KEYS_PASTE:
            panel.resource = pyperclip.paste().strip()
            console.printstr('Checking clipboard: {}'.format(panel.resource))
            inquire(panel, console)

        if c in KEYS_INQUIRE:
            inquire(panel, console)

        if c in KEYS_SELECT:
            select(panel, console)

        if c in KEYS_DOWNLOAD:
            spawn(panel, console)

        if c in KEYS_NUMERIC:
            spawn(panel, console, c-48)

        if c in KEYS_CANCEL:
            cancel(panel, console)

        # Debug
        # stdscr.addstr(curses.LINES-1, 108, 'c={}, t={}      '.format(c, threading.active_count()))

        # Refresh screen
        stdscr.noutrefresh()
        panel.freshen()
        console.freshen()
        curses.doupdate()

def init(stdscr):
    if curses.has_colors():
        curses.start_color()

    # Setup curses
    menu_options = 'Press "P": paste, "I": inquire, "S": select, "D": download, "C": cancel, "Q": quit'
    curses.curs_set(False)
    curses.init_pair(1, curses.COLOR_RED  , curses.COLOR_BLACK)

    # Title bar at top
    stdscr.addstr(TITLE, curses.A_REVERSE)
    stdscr.chgat(-1, curses.A_REVERSE)

    # Menu options at bottom
    stdscr.addstr(curses.LINES-1, 0, menu_options)

    # Create container box
    # console = Window(stdscr, curses.LINES-2, curses.COLS, 1, 0)
    panel = Window(stdscr, curses.LINES-9, curses.COLS, 1, 0)
    console = Window(stdscr, 7, curses.COLS, curses.LINES-8, 0)

    # Load specified video if available
    if VIDEO:
        panel.video = VIDEO
        console.printstr('Loading video')
        inquire(panel, console)

    # Display video if available
    panel.display()

    # Refresh screen
    stdscr.noutrefresh()
    panel.freshen()
    console.freshen()
    curses.doupdate()

    # Enter event loop
    loop(stdscr, panel, console)

def main(video=None):
    global VIDEO
    VIDEO = video
    curses.wrapper(init)

if __name__ == '__main__':
    main()
<commit_msg>tighten up display<commit_after>from __future__ import absolute_import, division, print_function, unicode_literals

import os
import sys
import curses
import threading

import pafy
import pyperclip

TITLE = '.:. Clipy .:.'
VIDEO = None

class Window(object):
    """Window absraction with border"""
    testing = False
    resource = None
    stream = None
    video = None

    def __init__(self, stdscr, lines, cols, y, x):
        self.stdscr = stdscr
        self.box = curses.newwin(lines, cols, y, x)
        Y, X = self.box.getmaxyx()
        # import pdb; pdb.set_trace()
        self.win = self.box.subwin(Y-2, X-4, y+1, 2)
        self.win.scrollok(True)
        self.win.keypad(True)

        self.box.box()

        # self._coord(Y-4, X-5)
        # for i in range(Y-2):
        #     self._coord(i, i)

    def _coord(self, y, x):
        self.win.addstr(y, x, '+ {} {}'.format(y, x))

    def display(self):
        self.win.erase()

        if self.video:
            self.printstr(self.video)

            self.printstr('Streams:')

            for i, stream in enumerate(self.video.allstreams):
                self.printstr('{}: {} {} {} {} {}'.format(i,
                    stream.mediatype,
                    stream.quality,
                    stream.extension,
                    stream.notes,
                    stream.bitrate or ''))

    def freshen(self):
        self.stdscr.noutrefresh()
        self.box.noutrefresh()
        self.win.noutrefresh()
        if not self.testing:
            curses.doupdate()

    def wait_for_input(self):
        return self.win.getch()

    def printstr(self, object, error=False):
        string = '{}\n'.format(object)
        if error:
            self.win.addstr(string, curses.A_BOLD | curses.color_pair(1))
        else:
            self.win.addstr(string)
        self.freshen()

    def progress(self, total, *progress_stats):
        status_string = ('{:,} Bytes [{:.2%}] received. Rate: [{:4.0f} '
                         'KB/s].  ETA: [{:.0f} secs]')
        status = status_string.format(*progress_stats)
        self.stdscr.addstr(0, 15, status, curses.A_REVERSE)
        self.freshen()

def inquire(panel, console):
    console.printstr('Inquiring')
    try:
        if panel.resource:
            console.printstr('Resourse found: {}'.format(panel.resource))
            panel.video = pafy.new(panel.resource)
            panel.resource = None

    except (OSError, ValueError) as e:
        console.printstr(e, error=True)

def select(panel, console):
    if panel.video:
        panel.printstr('Press one of the numbers above to download (0-{})'
            .format(len(panel.video.allstreams)))

    else:
        console.printstr('No video to select streams, Inquire first', error=True)

def download(panel, console):
    download_dir = os.path.expanduser('~')
    try:
        stream = panel.stream
        path = '%s.%s' % (os.path.join(download_dir, stream.title), stream.extension)
        console.printstr('Downloading {} to {}'.format(stream, download_dir))
        f = stream.download(filepath=path, quiet=True, callback=panel.progress)

    except (OSError, ValueError, FileNotFoundError) as e:
        console.printstr(e, error=True)

    else:
        if f:
            console.printstr('Downloaded: "{}"'.format(f))

def cancel(panel, console):
    if panel.stream:
        console.printstr('Cancelling {}'.format(panel.stream))
        if panel.stream.cancel():
            console.printstr('Cancelled "{}"'.format(panel.stream.title))
    else:
        console.printstr('Nothing to cancel')

    panel.stream = None

def spawn(panel, console, index=None):
    if panel.video is None:
        console.printstr('No video to download, Inquire first', error=True)
        return
    if index is None:
        try:
            panel.stream = panel.video.getbest(preftype="mp4")
        except (OSError, ValueError) as e:
            console.printstr(e, error=True)
            return
    else:
        if index >= len(panel.video.allstreams):
            console.printstr('Stream {} not available'.format(index), error=True)
            return
        else:
            panel.stream = panel.video.allstreams[index]

    t = threading.Thread(target=download, args=(panel, console))
    t.daemon = True
    t.start()

def loop(stdscr, panel, console):
    KEYS_CANCEL   = (ord('c'), ord('C'))
    KEYS_DOWNLOAD = (ord('d'), ord('D'))
    KEYS_INQUIRE  = (ord('i'), ord('I'))
    KEYS_NUMERIC  = range(48, 58)
    KEYS_PASTE    = (ord('p'), ord('P'))
    KEYS_QUIT     = (ord('q'), ord('Q'), 27)  # 27 is escape
    KEYS_SELECT   = (ord('s'), ord('S'))

    while True:

        # Display video if available
        panel.display()

        # Refresh screen
        stdscr.noutrefresh()
        panel.freshen()
        console.freshen()
        curses.doupdate()

        # Blocking
        c = panel.wait_for_input()

        if c in KEYS_QUIT:
            break

        if c in KEYS_PASTE:
            panel.resource = pyperclip.paste().strip()
            console.printstr('Checking clipboard: {}'.format(panel.resource))
            inquire(panel, console)

        if c in KEYS_INQUIRE:
            inquire(panel, console)

        if c in KEYS_SELECT:
            select(panel, console)

        if c in KEYS_DOWNLOAD:
            spawn(panel, console)

        if c in KEYS_NUMERIC:
            spawn(panel, console, c-48)

        if c in KEYS_CANCEL:
            cancel(panel, console)

        # Debug
        # stdscr.addstr(curses.LINES-1, 108, 'c={}, t={}      '.format(c, threading.active_count()))

def init(stdscr):
    if curses.has_colors():
        curses.start_color()

    # Setup curses
    menu_options = 'Press "P": paste, "I": inquire, "S": select, "D": download, "C": cancel, "Q": quit'
    curses.curs_set(False)
    curses.init_pair(1, curses.COLOR_RED  , curses.COLOR_BLACK)

    # Title bar at top
    stdscr.addstr(TITLE, curses.A_REVERSE)
    stdscr.chgat(-1, curses.A_REVERSE)

    # Menu options at bottom
    stdscr.addstr(curses.LINES-1, 0, menu_options)

    # Create container box
    # console = Window(stdscr, curses.LINES-2, curses.COLS, 1, 0)
    panel = Window(stdscr, curses.LINES-9, curses.COLS, 1, 0)
    console = Window(stdscr, 7, curses.COLS, curses.LINES-8, 0)

    # Load specified video if available
    if VIDEO:
        panel.video = VIDEO
        console.printstr('Loading video')
        inquire(panel, console)

    # Enter event loop
    loop(stdscr, panel, console)

def main(video=None):
    global VIDEO
    VIDEO = video
    curses.wrapper(init)

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