#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""A panel applet which periodically runs a command and displays its output."""

__author__ = 'Marcin Owsiany <marcin@owsiany.pl>'
__copyright__ = 'Copyright 2010-2011 Marcin Owsiany <marcin@owsiany.pl>'
__version__ = '0.4'
__license__ = '''
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program 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 General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
'''

import gi
import logging
from logging import handlers
import os
import subprocess

gi.require_version('Gtk', '3.0')
gi.require_version('PanelApplet', '5.0')

from gi.repository import GObject, Gtk, GdkPixbuf, Gio, PanelApplet


menu_xml = '''<section>
  <item>
    <attribute name="label" translatable="yes">Preferences</attribute>
    <attribute name="action">command-runner-applet.prefs</attribute>
  </item>
  <item>
    <attribute name="label" translatable="yes">About</attribute>
    <attribute name="action">command-runner-applet.about</attribute>
  </item>
</section>'''

def logged(f):
	"""Wraps function with some logging."""
	def wrapped(*args, **kwargs):
		try:
			return f(*args, **kwargs)
		except Exception, e:
			logging.exception(e)
			raise e
	wrapped.__doc__ = f.__doc__
	return wrapped


class CommandRunner(object):
	DEFAULT_COMMAND = 'echo Hello.'

	def __init__(self, applet, iid):
		"""Sets up the UI and retrieves configuration from settings."""
		logging.info('Initializing CommandRunner: %s', [self, applet, iid])
		self.applet = applet
		self.setup_ui()
		self.settings = applet.settings_new('org.gnome.gnome-panel.applet.command-runner')
		self.settings.connect('changed', self.settings_callback)
		self.read_configuration()
		action_group = Gio.SimpleActionGroup()

		action = Gio.SimpleAction.new('prefs')
		action.connect('activate', self.show_prefs)
		action_group.add_action(action)

		action = Gio.SimpleAction.new('about')
		action.connect('activate', self.show_about)
		action_group.add_action(action)

		applet.setup_menu(menu_xml, action_group, '')
		applet.insert_action_group('command-runner-applet', action_group)

	def setup_ui(self):
		"""Creates the display label object, and a preferences window."""
		self.label = Gtk.Label('...')
		self.applet.add(self.label)
		self.setup_prefs_window()

	def setup_prefs_window(self):
		"""Create, but do not show the preferences window."""
		self.prefs = Gtk.Window()
		self.prefs.set_title('Command Runner Preferences')
		# TODO: internationalization
		self.prefs.set_resizable(False)
		# We never actually delete the window, but just hide it, as
		# this makes it easier to keep track of places which need to be
		# updated when the configured command changes - see
		# set_current_command()
		self.prefs.connect('delete-event', lambda widget, data: self.prefs.hide() or True)
		vbox = Gtk.VBox(homogeneous=False, spacing=2)
		vbox.set_border_width(5)
		hbox = Gtk.HBox(homogeneous=False, spacing=2)
		label = Gtk.Label('Command:')
		self.entry = Gtk.Entry()
		hbox.pack_start(label, False, False, 0)
		hbox.pack_end(self.entry, False, False, 0)
		vbox.pack_start(hbox, True, False, 0)
		# Save configuration on pressing ENTER or leaving the entry box
		self.entry.connect('focus_out_event', self.config_entry_commit)
		self.entry.connect('activate', self.config_entry_commit)
		button = Gtk.Button(stock=Gtk.STOCK_CLOSE)
		button.connect('clicked', lambda widget: self.prefs.hide())
		vbox.pack_start(button, True, False, 0)
		self.prefs.add(vbox)

	@logged
	def config_entry_commit(self, entry, *args):
		"""Called when user wants to save configuration.

		Sets or deletes the command in settings, depending on the entry field state.
		"""
		text = entry.get_text()
		if text:
			self.settings.set_string('command', text)
		else:
			self.settings.reset('command')

	def read_configuration(self):
		"""Sets the command to whatever was configured or the default."""
		configured = self.settings.get_string('command')
		if configured:
			command = configured
		else:
			command = CommandRunner.DEFAULT_COMMAND
		self.set_current_command(command)

	def set_current_command(self, value):
		"""Sets the command for the runner and preferences dialog."""
		logging.info('Setting command [%s]', value)
		self.command = value
		self.entry.set_text(value)

	@logged
	def settings_callback(self, settings, key, data=None):
		"""Called by settings when configuration changes.
		"""
		logging.info('Got config notification for %s' % key)
		self.read_configuration()

	@logged
	def show_prefs(self, *data):
		"""Called when user selects 'preferences' menu item.

		Shows the preferences dialog.
		"""
		self.prefs.show_all()

	@logged
	def show_about(self, *data):
		"""Called when user selects 'about' menu item.

		Creates and shows the About dialog.
		"""
		about = Gtk.AboutDialog()
		about.set_name('Command applet')
		about.set_version(__version__)
		about.set_authors([__author__])
		about.set_artists(['Original icon taken from gnome-subtitles:',
		                   'Stefan A. Keel (Sak) <http://sak.102010.org>'])
		about.set_logo(GdkPixbuf.Pixbuf.new_from_file('/usr/share/pixmaps/command_runner.png'))
		about.set_copyright(__copyright__)
		about.set_license(__license__)
		about.set_wrap_license(False)
		about.run()
		about.destroy()

	def run(self):
		"""Shows the UI and starts periodically running the command."""
		self.applet.show_all()
		self.run_command()

	def run_command(self):
		"""Runs the command, shows its output, and schedules itself to run again in 5 seconds."""
		try:
			logging.debug('Running command [%s]', self.command)
			t = subprocess.Popen(self.command, stdout=subprocess.PIPE, shell=True).communicate()[0].rstrip()
			# TODO: the output could be large, it needs to be trimmed to a sensible size
			logging.debug('Got [%s] from command', t)
			self.label.set_text(t)
		except Exception, e:
			logging.exception(e)
			raise e
		finally:
			GObject.timeout_add(5000, self.run_command)


def command_applet_factory(applet, iid, data):
	"""Bonobo factory callback.

	Creates the applet and starts running.
	"""
	try:
		runner = CommandRunner(applet, iid)
		logging.info('Applet created successfully. Showing it now.')
		runner.run()
		return True
	except Exception, e:
		logging.info('Failed to create or run applet.')
		logging.exception(e)
		os._exit(1)
		# TODO: Returning FALSE does not seem to have any effect. The _exit()
		# is the only way I've found to abort visibly. There should be a better
		# way, at least something pointing user at syslog.
		return False


def start_factory(level):
	"""Sets up logging and starts bonobo factory."""
	syslog = handlers.SysLogHandler('/dev/log')
	syslog.setLevel(level)
	syslog.setFormatter(logging.Formatter('%(asctime)-15s command_runner %(levelname)-8s %(message)s'))
	logging.getLogger('').setLevel(logging.DEBUG)
	logging.getLogger('').addHandler(syslog)
	logging.info('Starting applet factory, wating for gnome-panel to connect.')
	PanelApplet.Applet.factory_main(
		'CommandRunnerAppletFactory',
		PanelApplet.Applet.__gtype__,
		command_applet_factory, None)
	logging.info('Exited the factory method.')


if __name__ == '__main__':
	# TODO: make it possible to set DEBUG logging without modifying code
	start_factory(logging.INFO)

