def next_step(self): # We already sent the details, but may still be on the same step. if self.wizard.tef_request_done: return StoqAdminPasswordStep(self.wizard, self.previous) webapi = WebService() response = webapi.tef_request(self.model.name, self.model.email, self.model.phone) response.addCallback(self._on_response_done) response.addErrback(self._on_response_error) # FIXME: This is a hack, remove it when we can avoid # calling dialog.run() if not reactor.running: reactor.run() self.send_progress.show() self.send_progress.set_text(_('Sending...')) self.send_progress.set_pulse_step(0.05) self.details_table.set_sensitive(False) self.wizard.next_button.set_sensitive(False) glib.timeout_add(50, self._pulse) # Cancel the request after 30 seconds without a reply glib.timeout_add(30000, self._cancel_request) # Stay on the same step while sending the details return self
def download_plugin(self, plugin_name): """Download a plugin from webservice :param plugin_name: the name of the plugin to download :returns: a deferred """ from stoqlib.lib.webservice import WebService def callback(filename): md5sum = unicode(md5sum_for_filename(filename)) with open(filename) as f: with new_store() as store: existing_egg = store.find(PluginEgg, plugin_name=plugin_name).one() if existing_egg is not None: existing_egg.egg_content = f.read() existing_egg.md5sum = md5sum else: PluginEgg( store=store, plugin_name=plugin_name, egg_md5sum=md5sum, egg_content=f.read(), ) self._reload() default_store = get_default_store() existing_egg = default_store.find(PluginEgg, plugin_name=plugin_name).one() md5sum = existing_egg and existing_egg.egg_md5sum webapi = WebService() return webapi.download_plugin(plugin_name, callback=callback, md5sum=md5sum)
def next_step(self): # We already sent the details, but may still be on the same step. # Also, if the user didn't choose to "register now", respect his # decision if not self.model.register_now or self.wizard.link_request_done: return FinishInstallationStep(self.wizard) webapi = WebService() response = webapi.link_registration( self.model.name, self.model.email, self.model.phone) response.addCallback(self._on_response_done) response.addErrback(self._on_response_error) # FIXME: This is a hack, remove it when we can avoid # calling dialog.run() if not reactor.running: reactor.run() self.send_progress.show() self.send_progress.set_text(_('Sending...')) self.send_progress.set_pulse_step(0.05) self.wizard.next_button.set_sensitive(False) glib.timeout_add(50, self._pulse) # Cancel the request after 30 seconds without a reply glib.timeout_add(30000, self._cancel_request) # Stay on the same step while sending the details return self
def download_plugin(self, plugin_name): """Download a plugin from webservice :param plugin_name: the name of the plugin to download :returns: a deferred """ from stoqlib.lib.webservice import WebService default_store = get_default_store() existing_egg = default_store.find(PluginEgg, plugin_name=plugin_name).one() md5sum = existing_egg and existing_egg.egg_md5sum webapi = WebService() r = webapi.download_plugin(plugin_name, md5sum=md5sum) try: response = r.get_response() except Exception as e: return False, _("Failed to do the request: %s" % (e, )) code = response.status_code if code == 204: msg = _("No update needed. The plugin is already up to date.") log.info(msg) return True, msg if code != 200: return_messages = { 400: _("Plugin not available for this stoq version"), 401: _("The instance is not authorized to download the plugin"), 404: _("Plugin does not exist"), 405: _("This instance has not acquired the specified plugin"), } msg = return_messages.get(code, str(code)) log.warning(msg) return False, msg content = response.content md5sum = unicode(hashlib.md5(content).hexdigest()) with new_store() as store: existing_egg = store.find(PluginEgg, plugin_name=plugin_name).one() if existing_egg is not None: existing_egg.egg_content = content existing_egg.egg_md5sum = md5sum else: PluginEgg( store=store, plugin_name=plugin_name, egg_md5sum=md5sum, egg_content=content, ) self._reload() return True, _("Plugin download successful")
def validate_confirm(self): if not self._can_submit_feedback(): return False webapi = WebService() d = webapi.feedback(self.application_screen, self.model.email, self.model.feedback) d.addCallback(self._on_feedback_reply) self.disable_ok() return False
def _check_information(self): """Check some information with Stoq Web API - Check if there are new versions of Stoq Available - Check if this Stoq Instance uses Stoq Link (and send data to us if it does). """ # Check version self._version_checker = VersionChecker(self.store, self) self._version_checker.check_new_version() if not api.sysparam.get_bool('ONLINE_SERVICES'): return # Check Stoq Link usage webapi = WebService() webapi.link_update(self.store)
def download_plugin(self, plugin_name): """Download a plugin from webservice :param plugin_name: the name of the plugin to download :returns: a deferred """ from stoqlib.lib.webservice import WebService def callback(filename): md5sum = unicode(md5sum_for_filename(filename)) with open(filename) as f: with new_store() as store: PluginEgg( store=store, plugin_name=plugin_name, egg_md5sum=md5sum, egg_content=f.read(), ) self._reload() webapi = WebService() return webapi.download_plugin(plugin_name, callback=callback)
def next_step(self): # We already sent the details, but may still be on the same step. # Also, if the user didn't choose to "register now", respect his # decision if not self.model.register_now or self.wizard.link_request_done: return FinishInstallationStep(self.wizard) webapi = WebService() webapi.link_registration( self.model.name, self.model.email, self.model.phone, callback=lambda r: schedule_in_main_thread(self._on_response_done, r), errback=lambda e: schedule_in_main_thread(self._on_response_error, e)) self.send_progress.show() self.send_progress.set_text(_('Sending...')) self.send_progress.set_pulse_step(0.05) self.wizard.next_button.set_sensitive(False) GLib.timeout_add(50, self._pulse) # Cancel the request after 30 seconds without a reply GLib.timeout_add(30000, self._cancel_request) # Stay on the same step while sending the details return self
class ReportSubmitter(GObject.GObject): gsignal('failed', object) gsignal('submitted', object) def __init__(self): GObject.GObject.__init__(self) self._count = 0 self._api = WebService() self.report = collect_report() def _done(self, args): self.emit('submitted', args) def _error(self, args): self.emit('failed', args) def submit(self): return self._api.bug_report(self.report, callback=self._on_report__callback, errback=self._on_report__errback) def _on_report__callback(self, response): if response.status_code == 200: self._on_success(response.json()) else: self._on_error() def _on_report__errback(self, failure): self._on_error(failure) def _on_error(self, data=None): log.info('Failed to report bug: %r count=%d' % (data, self._count)) if self._count < _N_TRIES: self.submit() else: schedule_in_main_thread(self.emit, 'failed', data) self._count += 1 def _on_success(self, data): log.info('Finished sending bugreport: %r' % (data, )) schedule_in_main_thread(self.emit, 'submitted', data)
class ReportSubmitter(gobject.GObject): gsignal("failed", object) gsignal("submitted", object) def __init__(self): gobject.GObject.__init__(self) self._count = 0 self._api = WebService() self.report = collect_report() def _done(self, args): self.emit("submitted", args) def _error(self, args): self.emit("failed", args) def submit(self): return self._api.bug_report(self.report, callback=self._on_report__callback, errback=self._on_report__errback) def _on_report__callback(self, response): if response.status_code == 200: self._on_success(response.json()) else: self._on_error() def _on_report__errback(self, failure): self._on_error(failure) def _on_error(self, data=None): log.info("Failed to report bug: %r count=%d" % (data, self._count)) if self._count < _N_TRIES: self.submit() else: schedule_in_main_thread(self.emit, "failed", data) self._count += 1 def _on_success(self, data): log.info("Finished sending bugreport: %r" % (data,)) schedule_in_main_thread(self.emit, "submitted", data)
class ReportSubmitter(gobject.GObject): gsignal("failed", object) gsignal("submitted", object) def __init__(self): gobject.GObject.__init__(self) self._api = WebService() self._report = collect_report() self._count = 0 def _done(self, args): self.emit("submitted", args) def _error(self, args): self.emit("failed", args) @property def report(self): return self._report def submit(self): response = self._api.bug_report(self._report) response.addCallback(self._on_report__callback) response.addErrback(self._on_report__errback) return response def _on_report__callback(self, data): log.info("Finished sending bugreport: %r" % (data,)) self._done(data) def _on_report__errback(self, failure): log.info("Failed to report bug: %r count=%d" % (failure, self._count)) if self._count < _N_TRIES: self.submit() else: self._error(failure) self._count += 1
class ReportSubmitter(gobject.GObject): gsignal('failed', object) gsignal('submitted', object) def __init__(self): gobject.GObject.__init__(self) self._api = WebService() self._report = collect_report() self._count = 0 def _done(self, args): self.emit('submitted', args) def _error(self, args): self.emit('failed', args) @property def report(self): return self._report def submit(self): response = self._api.bug_report(self._report) response.addCallback(self._on_report__callback) response.addErrback(self._on_report__errback) return response def _on_report__callback(self, data): log.info('Finished sending bugreport: %r' % (data, )) self._done(data) def _on_report__errback(self, failure): log.info('Failed to report bug: %r count=%d' % (failure, self._count)) if self._count < _N_TRIES: self.submit() else: self._error(failure) self._count += 1
def __init__(self): ResourceStatus.__init__(self) self._webservice = WebService() self._server = ServerProxy()
class _BackupStatus(ResourceStatus): name = "backup" label = _("Backup") priority = 98 def __init__(self): ResourceStatus.__init__(self) self._webservice = WebService() self._server = ServerProxy() @threaded def _get_key(self): return self._server.call('get_backup_key') @threaded def _get_server_status(self): request = self._webservice.status() return request.get_response() def refresh(self): if stoq.trial_mode: self.status = ResourceStatus.STATUS_NA self.reason = (_('Online features are not available in trial mode')) self.reason_long = _('Online features require a subscription of Stoq.link') return if not api.sysparam.get_bool('ONLINE_SERVICES'): self.status = ResourceStatus.STATUS_NA self.reason = _('Backup service not running because ' '"Online Services" is disabled') self.reason_long = _('Enable the parameter "Online Services" ' 'on the "Admin" app to solve this issue') return try: key = self._get_key() except ServerError: pass else: if not key: self.status = self.STATUS_WARNING self.reason = _("Backup key not configured") self.reason_long = _('Click on "Configure" button to ' 'configure the backup key') return try: response = self._get_server_status() except Exception as e: self.status = self.STATUS_WARNING self.reason = _("Could not communicate with Stoq.link") self.reason_long = str(e) return if response.status_code != 200: self.status = self.STATUS_WARNING self.reason = _("Could not communicate with Stoq.link") self.reason_long = None return data = response.json() if data['latest_backup_date']: backup_date = dateutil.parser.parse(data['latest_backup_date']) delta = datetime.datetime.today() - backup_date if delta.days > 3: self.status = self.STATUS_WARNING self.reason = _("Backup is late. Last backup date is %s") % ( backup_date.strftime('%x')) self.reason_long = _("Check your Stoq Server logs to see if " "there's any problem with it") else: self.status = self.STATUS_OK self.reason = _("Backup is up-to-date. Last backup date is %s") % ( backup_date.strftime('%x')) self.reason_long = None else: self.status = self.STATUS_WARNING self.reason = _("There's no backup data yet") self.reason_long = None def get_actions(self): if self.status != ResourceStatus.STATUS_NA: yield ResourceStatusAction( self, 'backup-now', _("Backup now"), self._on_backup_now, threaded=True) yield ResourceStatusAction( self, 'configure', _("Configure"), self._on_configure, threaded=False) def _on_configure(self): key = self._server.call('get_backup_key') with api.new_store() as store: rv = run_dialog(BackupSettingsEditor, None, store, Settable(key=key)) if rv: key = self._server.call('set_backup_key', rv.key) def _on_backup_now(self): self._server.call('backup_database')
def _download_details(self): log.debug('Downloading new version information') webapi = WebService() response = webapi.version(self.store, stoq.version) response.addCallback(self._on_response_done)
def _setup_stoq_link(self): from stoqlib.domain.events import SaleStatusChangedEvent from stoqlib.lib.webservice import WebService self._api = WebService() SaleStatusChangedEvent.connect(self._update_stoq_link)
def __init__(self): gobject.GObject.__init__(self) self._count = 0 self._api = WebService() self.report = collect_report()
def _download_details(self): log.debug('Downloading new version information') webapi = WebService() webapi.version(self.store, stoq.version, callback=self._on_response_done)
class ShellBootstrap(object): """Bootstraps the Stoq application, it's responsible for: - Setting up log files - Checking dependencies - Setting up libraries (gobject, gtk, kiwi, twisted) - Checking version - Locale - User settings and keybindings When this is completed Stoq is ready to connect to a database. """ def __init__(self, options, initial): self._initial = initial self._log_filename = None self._options = options self.entered_main = False self.stream = None def bootstrap(self): self._setup_gobject() self._set_uptime() # Do this as soon as possible, before we attempt to use the # external libraries/resources self._set_user_locale() # Do this as early as possible to get as much as possible into the # log file itself, which means we cannot depend on the config or # anything else self._prepare_logfiles() self._set_app_info() self._check_dependencies() self._setup_exception_hook() self._setup_gtk() self._setup_kiwi() self._show_splash() self._setup_twisted() self._setup_psycopg() self._check_version_policy() self._setup_ui_dialogs() self._setup_cookiefile() self._register_stock_icons() self._setup_domain_slave_mapper() self._load_key_bindings() self._setup_debug_options() self._check_locale() self._setup_autoreload() self._setup_stoq_link() def _setup_gobject(self): if not self._initial: return assert not 'gobject' in sys.modules assert not 'gtk' in sys.modules if 'STOQ_USE_GI' in os.environ: from stoq.lib import gicompat gicompat.enable() import gobject gobject.threads_init() def _set_uptime(self): from stoqlib.lib.uptime import set_initial set_initial() def _set_user_locale(self): from stoqlib.lib.settings import get_settings from stoqlib.lib.translation import stoqlib_gettext as _ self._locale_error = None settings = get_settings() lang = settings.get('user-locale', None) if not lang: return lang += '.UTF-8' try: locale.setlocale(locale.LC_ALL, lang) except locale.Error as err: msg = _("Could not set locale to %s. Make sure that you have " "the packages for this locale installed.") % lang[:-6] self._locale_error = (msg, err) log.warning(msg) else: os.environ['LC_ALL'] = lang os.environ['LANGUAGE'] = lang def _setup_autoreload(self): if not self._options.autoreload: return from stoqlib.lib.autoreload import install_autoreload install_autoreload() def _setup_stoq_link(self): from stoqlib.domain.events import SaleStatusChangedEvent from stoqlib.lib.webservice import WebService self._api = WebService() SaleStatusChangedEvent.connect(self._update_stoq_link) def _update_stoq_link(self, sale, old_status): if sale.status != sale.STATUS_CONFIRMED: return self._api.link_update(sale.store) def _prepare_logfiles(self): from stoqlib.lib.osutils import get_application_dir stoqdir = get_application_dir("stoq") log_dir = os.path.join(stoqdir, 'logs', time.strftime('%Y'), time.strftime('%m')) if not os.path.exists(log_dir): os.makedirs(log_dir) filename = 'stoq_%s.%s.log' % (time.strftime('%Y-%m-%d_%H-%M-%S'), os.getpid()) self._log_filename = os.path.join(log_dir, filename) from kiwi.log import set_log_file self._stream = set_log_file(self._log_filename, 'stoq*') if hasattr(os, 'symlink'): link_file = os.path.join(stoqdir, 'stoq.log') if os.path.exists(link_file): os.unlink(link_file) os.symlink(self._log_filename, link_file) # We want developers to see deprecation warnings. from stoqlib.lib.environment import is_developer_mode if is_developer_mode(): import warnings if self._options.non_fatal_warnings: action = "default" else: action = "error" warnings.filterwarnings(action, category=DeprecationWarning, module="^(stoq|kiwi)") def _set_app_info(self): from kiwi.component import provide_utility from stoqlib.lib.appinfo import AppInfo from stoqlib.lib.kiwilibrary import library from stoqlib.lib.interfaces import IAppInfo import stoq # FIXME: use only stoq.stoq_version here and all other callsites of # IAppInfo stoq_version = stoq.version stoq_ver = stoq.stoq_version if hasattr(library, 'get_revision'): rev = library.get_revision() if rev is not None: stoq_version += ' ' + rev stoq_ver += (rev, ) info = AppInfo() info.set("name", "Stoq") info.set("version", stoq_version) info.set("ver", stoq_ver) info.set("log", self._log_filename) provide_utility(IAppInfo, info) def _check_dependencies(self): from stoq.lib.dependencies import check_dependencies check_dependencies() def _setup_exception_hook(self): if self._options.debug: hook = self._debug_hook else: hook = self._write_exception_hook sys.excepthook = hook def _setup_gtk(self): import gtk from kiwi.environ import environ # Total madness to make sure we can draw treeview lines, # this affects the GtkTreeView::grid-line-pattern style property # # Two bytes are sent in, see gtk_tree_view_set_grid_lines in gtktreeview.c # Byte 1 should be as high as possible, gtk+ 0x7F appears to be # the highest allowed for Gtk+ 2.22 while 0xFF worked in # earlier versions # Byte 2 should ideally be allowed to be 0, but neither C nor Python # allows that. # data = environ.get_resource_string("stoq", "misc", "stoq.gtkrc") data = data.replace('\\x7f\\x01', '\x7f\x01') gtk.rc_parse_string(data) # Creating a button as a temporary workaround for bug # https://bugzilla.gnome.org/show_bug.cgi?id=632538, until gtk 3.0 gtk.Button() settings = gtk.settings_get_default() settings.props.gtk_button_images = True from stoqlib.lib.environment import is_developer_mode if is_developer_mode() and gtk.gtk_version[0] == 2: from gtk import gdk # Install a Control-Q handler that forcefully exits # the program without saving any kind of state def event_handler(event): if (event.type == gdk.KEY_PRESS and event.state & gdk.CONTROL_MASK and event.keyval == gtk.keysyms.q): os._exit(0) gtk.main_do_event(event) gdk.event_handler_set(event_handler) def _setup_kiwi(self): from kiwi.datatypes import get_localeconv from kiwi.ui.widgets.label import ProxyLabel ProxyLabel.replace('$CURRENCY', get_localeconv()['currency_symbol']) def _show_splash(self): if not self._options.splashscreen: return from stoqlib.gui.widgets.splash import show_splash show_splash() def _setup_twisted(self, raise_=True): # FIXME: figure out why twisted is already loaded # assert not 'twisted' in sys.modules from stoqlib.net import gtk2reactor from twisted.internet.error import ReactorAlreadyInstalledError try: gtk2reactor.install() except ReactorAlreadyInstalledError: if self._initial and raise_: raise def _setup_psycopg(self): # This will only be required when we use uuid.UUID instances # for UUIDCol #from psycopg2.extras import register_uuid #register_uuid() return def _check_version_policy(self): # No need to bother version checking when not running in developer mode from stoqlib.lib.environment import is_developer_mode if not is_developer_mode(): return import stoq # # Policies for stoq/stoqlib versions, # All these policies here are made so that stoqlib version is tightly # tied to the stoq versioning # # We reserve the first 89 for the stable series. FIRST_UNSTABLE_MICRO_VERSION = 90 # Stable series of Stoq must: # 1) have extra_version set to < 90 # 2) Depend on a stoqlib version with extra_version < 90 # if stoq.stable: if (stoq.micro_version >= FIRST_UNSTABLE_MICRO_VERSION and not 'rc' in stoq.extra_version): raise SystemExit( "Stable stoq release should set micro_version to " "%d or lower" % (FIRST_UNSTABLE_MICRO_VERSION, )) # Unstable series of Stoq must have: # 1) have extra_version set to >= 90 # 2) Must depend stoqlib version with extra_version >= 90 # else: if stoq.micro_version < FIRST_UNSTABLE_MICRO_VERSION: raise SystemExit( "Unstable stoq (%s) must set micro_version to %d or higher, " "or did you forget to set stoq.stable to True?" % (stoq.version, FIRST_UNSTABLE_MICRO_VERSION)) def _setup_ui_dialogs(self): # This needs to be here otherwise we can't install the dialog if 'STOQ_TEST_MODE' in os.environ: return log.debug('providing graphical notification dialogs') from stoqlib.gui.base.dialogs import DialogSystemNotifier from stoqlib.lib.interfaces import ISystemNotifier from kiwi.component import provide_utility provide_utility(ISystemNotifier, DialogSystemNotifier(), replace=True) import gtk from kiwi.environ import environ from kiwi.ui.pixbufutils import pixbuf_from_string data = environ.get_resource_string('stoq', 'pixmaps', 'stoq-stock-app-24x24.png') gtk.window_set_default_icon(pixbuf_from_string(data)) if platform.system() == 'Darwin': from AppKit import NSApplication, NSData, NSImage bytes = environ.get_resource_string('stoq', 'pixmaps', 'stoq-stock-app-48x48.png') data = NSData.alloc().initWithBytes_length_(bytes, len(bytes)) icon = NSImage.alloc().initWithData_(data) app = NSApplication.sharedApplication() app.setApplicationIconImage_(icon) def _setup_cookiefile(self): log.debug('setting up cookie file') from kiwi.component import provide_utility from stoqlib.lib.cookie import Base64CookieFile from stoqlib.lib.interfaces import ICookieFile from stoqlib.lib.osutils import get_application_dir app_dir = get_application_dir() cookiefile = os.path.join(app_dir, "cookie") provide_utility(ICookieFile, Base64CookieFile(cookiefile)) def _register_stock_icons(self): from stoqlib.gui.stockicons import register log.debug('register stock icons') register() def _setup_domain_slave_mapper(self): from kiwi.component import provide_utility from stoqlib.gui.interfaces import IDomainSlaveMapper from stoqlib.gui.slaves.domainslavemapper import DefaultDomainSlaveMapper provide_utility(IDomainSlaveMapper, DefaultDomainSlaveMapper(), replace=True) def _load_key_bindings(self): from stoqlib.gui.utils.keybindings import load_user_keybindings load_user_keybindings() def _check_locale(self): if not self._locale_error: return from stoqlib.lib.message import warning warning(self._locale_error[0], str(self._locale_error[1])) def _setup_debug_options(self): if not self._options.debug: return from gtk import keysyms from stoqlib.gui.utils.introspection import introspect_slaves from stoqlib.gui.utils.keyboardhandler import install_global_keyhandler install_global_keyhandler(keysyms.F12, introspect_slaves) # # Global functions # def _debug_hook(self, exctype, value, tb): self._write_exception_hook(exctype, value, tb) traceback.print_exception(exctype, value, tb) print() print('-- Starting debugger --') print() import pdb pdb.pm() def _write_exception_hook(self, exctype, value, tb): # NOTE: This exception hook depends on gtk, kiwi, twisted being present # In the future we might want it to run without some of these # dependencies, so we can crash reports that happens really # really early on for users with weird environments. if not self.entered_main: self._setup_twisted(raise_=False) try: from psycopg2 import OperationalError if exctype == OperationalError: from stoqlib.lib.message import error from stoqlib.lib.translation import stoqlib_gettext as _ return error(_('There was an error quering the database'), str(value)) except ImportError: pass appname = 'unknown' try: from stoq.gui.shell.shell import get_shell shell = get_shell() if shell: appname = shell.get_current_app_name() except ImportError: pass window_name = 'unknown' try: from stoqlib.gui.base.dialogs import get_current_toplevel window = get_current_toplevel() if window: window_name = window.get_name() except ImportError: pass log.info('An error occurred in application "%s", toplevel window=%s' % (appname, window_name)) exc_lines = traceback.format_exception(exctype, value, tb) for line in ''.join(exc_lines).split('\n')[:-1]: log.error(line) from stoqlib.lib.crashreport import collect_traceback collect_traceback((exctype, value, tb)) if self.entered_main: return from stoqlib.gui.dialogs.crashreportdialog import show_dialog d = show_dialog() from twisted.internet import reactor d.addCallback(lambda *x: reactor.stop()) reactor.run() raise SystemExit
class _BackupStatus(ResourceStatus): name = "backup" label = _("Backup") priority = 98 def __init__(self): ResourceStatus.__init__(self) self._webservice = WebService() self._server = ServerProxy() def refresh(self): if not api.sysparam.get_bool("ONLINE_SERVICES"): self.status = ResourceStatus.STATUS_NA self.reason = _("Backup service not running because " '"Online Services" is disabled') self.reason_long = _('Enable the parameter "Online Services" ' 'on the "Admin" app to solve this issue') return try: key = self._server.call("get_backup_key") except ServerError: pass else: if not key: self.status = self.STATUS_WARNING self.reason = _("Backup key not configured") self.reason_long = _('Click on "Configure" button to ' "configure the backup key") return request = self._webservice.status() try: response = request.get_response() except Exception as e: self.status = self.STATUS_WARNING self.reason = _("Could not communicate with Stoq.link") self.reason_long = str(e) return if response.status_code != 200: self.status = self.STATUS_WARNING self.reason = _("Could not communicate with Stoq.link") self.reason_long = None return data = response.json() if data["latest_backup_date"]: backup_date = dateutil.parser.parse(data["latest_backup_date"]) delta = datetime.datetime.today() - backup_date if delta.days > 3: self.status = self.STATUS_WARNING self.reason = _("Backup is late. Last backup date is %s") % (backup_date.strftime("%x")) self.reason_long = _("Check your Stoq Server logs to see if " "there's any problem with it") else: self.status = self.STATUS_OK self.reason = _("Backup is up-to-date. Last backup date is %s") % (backup_date.strftime("%x")) self.reason_long = None else: self.status = self.STATUS_WARNING self.reason = _("There's no backup data yet") self.reason_long = None def get_actions(self): if self.status != ResourceStatus.STATUS_NA: yield ResourceStatusAction(self, "backup-now", _("Backup now"), self._on_backup_now, threaded=True) yield ResourceStatusAction(self, "configure", _("Configure"), self._on_configure, threaded=False) def _on_configure(self): key = self._server.call("get_backup_key") with api.new_store() as store: rv = run_dialog(BackupSettingsEditor, None, store, Settable(key=key)) if rv: key = self._server.call("set_backup_key", rv.key) def _on_backup_now(self): self._server.call("backup_database")
def __init__(self): gobject.GObject.__init__(self) self._api = WebService() self._report = collect_report() self._count = 0
def download_plugin(self, plugin_name, channel=None): """Download a plugin from webservice :param plugin_name: the name of the plugin to download :param channel: the channel the plugin belongs :returns: a deferred """ from stoqlib.lib.webservice import WebService default_store = get_default_store() existing_egg = default_store.find(PluginEgg, plugin_name=plugin_name).one() md5sum = existing_egg and existing_egg.egg_md5sum webapi = WebService() r = webapi.download_plugin(plugin_name, md5sum=md5sum, channel=channel) try: response = r.get_response() except Exception as e: return False, _("Failed to do the request: %s" % (e, )) code = response.status_code if code == 204: msg = _("No update needed. The plugin is already up to date.") log.info(msg) return True, msg if code != 200: return_messages = { 400: _("Plugin not available for this stoq version"), 401: _("The instance is not authorized to download the plugin"), 404: _("Plugin does not exist"), 405: _("This instance has not acquired the specified plugin"), } msg = return_messages.get(code, str(code)) log.warning(msg) return False, msg try: with io.BytesIO() as f: f.write(response.content) with ZipFile(f) as egg: if egg.testzip() is not None: raise BadZipfile md5sum = hashlib.md5(f.getvalue()).hexdigest() with new_store() as store: existing_egg = store.find(PluginEgg, plugin_name=plugin_name).one() if existing_egg is not None: existing_egg.egg_content = f.getvalue() existing_egg.egg_md5sum = md5sum else: PluginEgg( store=store, plugin_name=plugin_name, egg_md5sum=md5sum, egg_content=f.getvalue(), ) except BadZipfile: return False, _("The downloaded plugin is corrupted") self._reload() return True, _("Plugin download successful")
def download_plugin(self, plugin_name, channel=u'stable'): """Download a plugin from webservice :param plugin_name: the name of the plugin to download :param channel: the channel the plugin belongs :returns: a deferred """ from stoqlib.lib.webservice import WebService default_store = get_default_store() existing_egg = default_store.find(PluginEgg, plugin_name=plugin_name).one() md5sum = existing_egg and existing_egg.egg_md5sum webapi = WebService() r = webapi.download_plugin(plugin_name, md5sum=md5sum, channel=channel) try: response = r.get_response() except Exception as e: return False, _("Failed to do the request: %s" % (e, )) code = response.status_code if code == 204: msg = _("No update needed. The plugin is already up to date.") log.info(msg) return True, msg if code != 200: return_messages = { 400: _("Plugin not available for this stoq version"), 401: _("The instance is not authorized to download the plugin"), 404: _("Plugin does not exist"), 405: _("This instance has not acquired the specified plugin"), } msg = return_messages.get(code, str(code)) log.warning(msg) return False, msg try: with io.BytesIO() as f: f.write(response.content) with ZipFile(f) as egg: if egg.testzip() is not None: raise BadZipfile md5sum = hashlib.md5(f.getvalue()).hexdigest() with new_store() as store: existing_egg = store.find(PluginEgg, plugin_name=plugin_name).one() if existing_egg is not None: existing_egg.egg_content = f.getvalue() existing_egg.egg_md5sum = md5sum else: PluginEgg( store=store, plugin_name=plugin_name, egg_md5sum=md5sum, egg_content=f.getvalue(), ) except BadZipfile: return False, _("The downloaded plugin is corrupted") self._reload() return True, _("Plugin download successful")
class ShellBootstrap(object): """Bootstraps the Stoq application, it's responsible for: - Setting up log files - Checking dependencies - Setting up libraries (gobject, gtk, kiwi, twisted) - Checking version - Locale - User settings and keybindings When this is completed Stoq is ready to connect to a database. """ def __init__(self, options, initial): self._initial = initial self._log_filename = None self._options = options self.entered_main = False self.stream = None def bootstrap(self): self._setup_gobject() self._set_uptime() # Do this as soon as possible, before we attempt to use the # external libraries/resources self._set_user_locale() # Do this as early as possible to get as much as possible into the # log file itself, which means we cannot depend on the config or # anything else self._prepare_logfiles() self._set_app_info() self._check_dependencies() self._setup_exception_hook() self._setup_gtk() self._setup_kiwi() self._show_splash() self._setup_twisted() self._setup_psycopg() self._check_version_policy() self._setup_ui_dialogs() self._setup_cookiefile() self._register_stock_icons() self._setup_domain_slave_mapper() self._load_key_bindings() self._setup_debug_options() self._check_locale() self._setup_autoreload() self._setup_stoq_link() def _setup_gobject(self): if not self._initial: return assert not 'gobject' in sys.modules assert not 'gtk' in sys.modules if 'STOQ_USE_GI' in os.environ: from stoq.lib import gicompat gicompat.enable() import gobject gobject.threads_init() def _set_uptime(self): from stoqlib.lib.uptime import set_initial set_initial() def _set_user_locale(self): from stoqlib.lib.settings import get_settings from stoqlib.lib.translation import stoqlib_gettext as _ self._locale_error = None settings = get_settings() lang = settings.get('user-locale', None) if not lang: return lang += '.UTF-8' try: locale.setlocale(locale.LC_ALL, lang) except locale.Error as err: msg = _("Could not set locale to %s. Make sure that you have " "the packages for this locale installed.") % lang[:-6] self._locale_error = (msg, err) log.warning(msg) else: os.environ['LC_ALL'] = lang os.environ['LANGUAGE'] = lang def _setup_autoreload(self): if not self._options.autoreload: return from stoqlib.lib.autoreload import install_autoreload install_autoreload() def _setup_stoq_link(self): from stoqlib.domain.events import SaleStatusChangedEvent from stoqlib.lib.webservice import WebService self._api = WebService() SaleStatusChangedEvent.connect(self._update_stoq_link) def _update_stoq_link(self, sale, old_status): if sale.status != sale.STATUS_CONFIRMED: return self._api.link_update(sale.store) def _prepare_logfiles(self): from stoqlib.lib.osutils import get_application_dir stoqdir = get_application_dir("stoq") log_dir = os.path.join(stoqdir, 'logs', time.strftime('%Y'), time.strftime('%m')) if not os.path.exists(log_dir): os.makedirs(log_dir) filename = 'stoq_%s.%s.log' % (time.strftime('%Y-%m-%d_%H-%M-%S'), os.getpid()) self._log_filename = os.path.join(log_dir, filename) from kiwi.log import set_log_file self._stream = set_log_file(self._log_filename, 'stoq*') if hasattr(os, 'symlink'): link_file = os.path.join(stoqdir, 'stoq.log') if os.path.exists(link_file): os.unlink(link_file) os.symlink(self._log_filename, link_file) # We want developers to see deprecation warnings. from stoqlib.lib.environment import is_developer_mode if is_developer_mode(): import warnings if self._options.non_fatal_warnings: action = "default" else: action = "error" warnings.filterwarnings( action, category=DeprecationWarning, module="^(stoq|kiwi)") def _set_app_info(self): from kiwi.component import provide_utility from stoqlib.lib.appinfo import AppInfo from stoqlib.lib.kiwilibrary import library from stoqlib.lib.interfaces import IAppInfo import stoq # FIXME: use only stoq.stoq_version here and all other callsites of # IAppInfo stoq_version = stoq.version stoq_ver = stoq.stoq_version if hasattr(library, 'get_revision'): rev = library.get_revision() if rev is not None: stoq_version += ' ' + rev stoq_ver += (rev,) info = AppInfo() info.set("name", "Stoq") info.set("version", stoq_version) info.set("ver", stoq_ver) info.set("log", self._log_filename) provide_utility(IAppInfo, info) def _check_dependencies(self): from stoq.lib.dependencies import check_dependencies check_dependencies() def _setup_exception_hook(self): if self._options.debug: hook = self._debug_hook else: hook = self._write_exception_hook sys.excepthook = hook def _setup_gtk(self): import gtk from kiwi.environ import environ # Total madness to make sure we can draw treeview lines, # this affects the GtkTreeView::grid-line-pattern style property # # Two bytes are sent in, see gtk_tree_view_set_grid_lines in gtktreeview.c # Byte 1 should be as high as possible, gtk+ 0x7F appears to be # the highest allowed for Gtk+ 2.22 while 0xFF worked in # earlier versions # Byte 2 should ideally be allowed to be 0, but neither C nor Python # allows that. # data = environ.get_resource_string("stoq", "misc", "stoq.gtkrc") data = data.replace('\\x7f\\x01', '\x7f\x01') gtk.rc_parse_string(data) # Creating a button as a temporary workaround for bug # https://bugzilla.gnome.org/show_bug.cgi?id=632538, until gtk 3.0 gtk.Button() settings = gtk.settings_get_default() settings.props.gtk_button_images = True from stoqlib.lib.environment import is_developer_mode if is_developer_mode() and gtk.gtk_version[0] == 2: from gtk import gdk # Install a Control-Q handler that forcefully exits # the program without saving any kind of state def event_handler(event): if (event.type == gdk.KEY_PRESS and event.state & gdk.CONTROL_MASK and event.keyval == gtk.keysyms.q): os._exit(0) gtk.main_do_event(event) gdk.event_handler_set(event_handler) def _setup_kiwi(self): from kiwi.datatypes import get_localeconv from kiwi.ui.widgets.label import ProxyLabel ProxyLabel.replace('$CURRENCY', get_localeconv()['currency_symbol']) def _show_splash(self): if not self._options.splashscreen: return from stoqlib.gui.widgets.splash import show_splash show_splash() def _setup_twisted(self, raise_=True): # FIXME: figure out why twisted is already loaded # assert not 'twisted' in sys.modules from stoqlib.net import gtk2reactor from twisted.internet.error import ReactorAlreadyInstalledError try: gtk2reactor.install() except ReactorAlreadyInstalledError: if self._initial and raise_: raise def _setup_psycopg(self): # This will only be required when we use uuid.UUID instances # for UUIDCol #from psycopg2.extras import register_uuid #register_uuid() return def _check_version_policy(self): # No need to bother version checking when not running in developer mode from stoqlib.lib.environment import is_developer_mode if not is_developer_mode(): return import stoq # # Policies for stoq/stoqlib versions, # All these policies here are made so that stoqlib version is tightly # tied to the stoq versioning # # We reserve the first 89 for the stable series. FIRST_UNSTABLE_MICRO_VERSION = 90 # Stable series of Stoq must: # 1) have extra_version set to < 90 # 2) Depend on a stoqlib version with extra_version < 90 # if stoq.stable: if (stoq.micro_version >= FIRST_UNSTABLE_MICRO_VERSION and not 'rc' in stoq.extra_version): raise SystemExit( "Stable stoq release should set micro_version to " "%d or lower" % (FIRST_UNSTABLE_MICRO_VERSION, )) # Unstable series of Stoq must have: # 1) have extra_version set to >= 90 # 2) Must depend stoqlib version with extra_version >= 90 # else: if stoq.micro_version < FIRST_UNSTABLE_MICRO_VERSION: raise SystemExit( "Unstable stoq (%s) must set micro_version to %d or higher, " "or did you forget to set stoq.stable to True?" % ( stoq.version, FIRST_UNSTABLE_MICRO_VERSION)) def _setup_ui_dialogs(self): # This needs to be here otherwise we can't install the dialog if 'STOQ_TEST_MODE' in os.environ: return log.debug('providing graphical notification dialogs') from stoqlib.gui.base.dialogs import DialogSystemNotifier from stoqlib.lib.interfaces import ISystemNotifier from kiwi.component import provide_utility provide_utility(ISystemNotifier, DialogSystemNotifier(), replace=True) import gtk from kiwi.environ import environ from kiwi.ui.pixbufutils import pixbuf_from_string data = environ.get_resource_string( 'stoq', 'pixmaps', 'stoq-stock-app-24x24.png') gtk.window_set_default_icon(pixbuf_from_string(data)) if platform.system() == 'Darwin': from AppKit import NSApplication, NSData, NSImage bytes = environ.get_resource_string( 'stoq', 'pixmaps', 'stoq-stock-app-48x48.png') data = NSData.alloc().initWithBytes_length_(bytes, len(bytes)) icon = NSImage.alloc().initWithData_(data) app = NSApplication.sharedApplication() app.setApplicationIconImage_(icon) def _setup_cookiefile(self): log.debug('setting up cookie file') from kiwi.component import provide_utility from stoqlib.lib.cookie import Base64CookieFile from stoqlib.lib.interfaces import ICookieFile from stoqlib.lib.osutils import get_application_dir app_dir = get_application_dir() cookiefile = os.path.join(app_dir, "cookie") provide_utility(ICookieFile, Base64CookieFile(cookiefile)) def _register_stock_icons(self): from stoqlib.gui.stockicons import register log.debug('register stock icons') register() def _setup_domain_slave_mapper(self): from kiwi.component import provide_utility from stoqlib.gui.interfaces import IDomainSlaveMapper from stoqlib.gui.slaves.domainslavemapper import DefaultDomainSlaveMapper provide_utility(IDomainSlaveMapper, DefaultDomainSlaveMapper(), replace=True) def _load_key_bindings(self): from stoqlib.gui.utils.keybindings import load_user_keybindings load_user_keybindings() def _check_locale(self): if not self._locale_error: return from stoqlib.lib.message import warning warning(self._locale_error[0], str(self._locale_error[1])) def _setup_debug_options(self): if not self._options.debug: return from gtk import keysyms from stoqlib.gui.utils.introspection import introspect_slaves from stoqlib.gui.utils.keyboardhandler import install_global_keyhandler install_global_keyhandler(keysyms.F12, introspect_slaves) # # Global functions # def _debug_hook(self, exctype, value, tb): self._write_exception_hook(exctype, value, tb) traceback.print_exception(exctype, value, tb) print() print('-- Starting debugger --') print() import pdb pdb.pm() def _write_exception_hook(self, exctype, value, tb): # NOTE: This exception hook depends on gtk, kiwi, twisted being present # In the future we might want it to run without some of these # dependencies, so we can crash reports that happens really # really early on for users with weird environments. if not self.entered_main: self._setup_twisted(raise_=False) try: from psycopg2 import OperationalError if exctype == OperationalError: from stoqlib.lib.message import error from stoqlib.lib.translation import stoqlib_gettext as _ return error(_('There was an error quering the database'), str(value)) except ImportError: pass appname = 'unknown' try: from stoq.gui.shell.shell import get_shell shell = get_shell() if shell: appname = shell.get_current_app_name() except ImportError: pass window_name = 'unknown' try: from stoqlib.gui.base.dialogs import get_current_toplevel window = get_current_toplevel() if window: window_name = window.get_name() except ImportError: pass log.info('An error occurred in application "%s", toplevel window=%s' % ( appname, window_name)) exc_lines = traceback.format_exception(exctype, value, tb) for line in ''.join(exc_lines).split('\n')[:-1]: log.error(line) from stoqlib.lib.crashreport import collect_traceback collect_traceback((exctype, value, tb)) if self.entered_main: return from stoqlib.gui.dialogs.crashreportdialog import show_dialog d = show_dialog() from twisted.internet import reactor d.addCallback(lambda *x: reactor.stop()) reactor.run() raise SystemExit
def _download_details(self): log.debug('Downloading new version information') api = WebService() response = api.version(self.store, stoq.version) response.addCallback(self._on_response_done)