Subversion Repositories basico

Rev

Rev 142 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

#!/usr/bin/python
# -*- coding: utf-8 -*-
# File: callbacks.py
# Author: Tomás Vírseda
# License: GPL v3
# Description: UI and related callbacks service

import os
import csv
import json
import time
import glob
from os.path import basename
from datetime import datetime

import gi
gi.require_version('Gtk', '3.0')
#~ gi.require_version('WebKit', '3.0')
from gi.repository import GLib
from gi.repository import Gtk
from gi.repository import Gio
from gi.repository import Pango
from gi.repository.GdkPixbuf import Pixbuf
#~ from gi.repository import WebKit

from concurrent.futures import ThreadPoolExecutor as Executor

from .service import Service

SEP = os.path.sep

# PROPKEYS = CSV headers. SAP Note metadata
PROPKEYS = ['id', 'title', 'type', 'componentkey',
            'componenttxt', 'category', 'priority', 'releaseon',
            'language', 'version']

# Extend PROPKEYS with custom basico metadata
PROPKEYS.extend (['Tasks', 'Bookmark'])

class Callback(Service):
    def initialize(self):
        self.get_services()

    def get_services(self):
        self.settings = self.app.get_service('Settings')
        self.db = self.app.get_service('DB')
        self.gui = self.app.get_service('GUI')
        self.uif = self.app.get_service("UIF")
        self.sap = self.app.get_service('SAP')
        self.tasks = self.app.get_service('Tasks')
        self.alert = self.app.get_service('Notify')
        self.im = self.app.get_service('IM')
        self.utils = self.app.get_service('Utils')

    def execute_action(self, *args):
        action = args[0]
        action_name = action.get_name()
        try:
            callback = "self.%s()" % action_name.replace('-', '_')
            return eval(callback)
        except Exception as error:
            self.log.error(error)
            self.log.error("Callback for action '%s' not registered" % action_name)
            raise


    def actions_browse(self, *args):
        SAP_NOTE_URL = self.settings.get('SAP', 'SAP_NOTE_URL')
        sapnoteview = self.gui.get_widget('viewmenu')
        visor = self.gui.get_widget('visor')
        sapnotes = visor.get_toggled()
        lurl = []
        for sid in sapnotes:
            url = SAP_NOTE_URL % sid
            lurl.append(url)
            self.debug("Browsing SAP Note %s" % sid)
        self.utils.browse(lurl)


    def actions_other_delete(self, *args):
        db = self.get_service('DB')
        sapnoteview = self.gui.get_widget('viewmenu')
        visor = self.gui.get_widget('visor')
        sapnotes = visor.get_toggled()
        sapnotes.sort()
        winroot = self.gui.get_widget('mainwindow')

        dialog = Gtk.MessageDialog(winroot, 0, Gtk.MessageType.WARNING,
            Gtk.ButtonsType.OK_CANCEL, "Are you sure?")
        dialog.set_title("Deleting SAP Notes...")
        dialog.set_modal(True)
        dialog.set_transient_for(winroot)
        dialog.format_secondary_text(
            "These SAP Notes will be deleted:\n%s" % ', '.join(sapnotes))

        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            db = self.get_service('DB')
            visor = self.gui.get_widget('visor')
            sapnotes = visor.get_toggled()
            for sapnote in sapnotes:
                db.delete(sapnote)
            self.search_notes()
            self.refresh_view()
            self.alert.show('Delete', 'Selected SAP Notes deleted', 'information')
            self.debug("Selected SAP Notes deleted")
            db.save_notes()
        elif response == Gtk.ResponseType.CANCEL:
            self.alert.show('Delete', 'Delete action canceled by user', 'warning')
            self.debug("Delete action canceled by user")

        dialog.destroy()

        return response

    def update_titlebar_title(self, title, icon, capitalize=True):
        image = self.gui.get_widget('imgtitlebar')
        icon = self.im.get_pixbuf_icon(icon.lower(), 24, 24)
        image.set_from_pixbuf(icon)
        image.show_all()
        titlebar = self.gui.get_widget("lbltitlebar")
        if capitalize:
            title = title.capitalize()
        titlebar.set_markup("<big><b>%15s</b></big>" % title)
        titlebar.set_hexpand(True)


    #~ def show_dashboard(self, *args):
        #~ notebook = self.gui.get_widget('mainbox')
        #~ notebook.set_current_page(0)
        #~ self.update_titlebar_title("Dashboard", "dashboard")


    #~ def show_workplace(self, *args):
        #~ notebook = self.gui.get_widget('mainbox')
        #~ notebook.set_current_page(1)
        #~ self.update_titlebar_title("Workplace", "workplace")


    #~ def show_school(self, *args):
        #~ notebook = self.gui.get_widget('mainbox')
        #~ notebook.set_current_page(2)
        #~ self.update_titlebar_title("My SAP School", "school", False)


    #~ def show_workbook(self, *args):
        #~ notebook = self.gui.get_widget('mainbox')
        #~ notebook.set_current_page(3)
        #~ self.update_titlebar_title("Workbook", "comments")


    def toggle_search(self, *args):
        tgbsearch = self.gui.get_widget('tgbSearch')
        toggled = tgbsearch.get_active()
        tgbsearch.set_active(not toggled)

    def show_about(self, *args):
        from .about import About
        visor = self.gui.get_widget('visor')
        self.gui.swap_widget(visor, About())

    def show_help(self, *args):
        notebook = self.gui.get_widget('mainbox')
        notebook.set_current_page(5)
        self.update_titlebar_title("Help", "basico_help")


    def show_settings(self, button):
        notebook = self.gui.get_widget('mainbox')
        notebook.set_current_page(4)
        self.update_titlebar_title("Settings", "settings")

    def show_popover(self, button, popover):
        if popover.get_visible():
            popover.hide()
        else:
            popover.show_all()


    def show_popover_menu(self, button, popover):
        if popover.get_visible():
            popover.hide()
        else:
            popover.show_all()


    def show_properties(self, *args):
        notebook = self.gui.get_widget('ntbOperations')
        toggle = self.gui.get_widget('tgbShowManage')
        notebook.set_current_page(0)
        toggle.set_active(True)


    def actions_manage_tasks(self, *args):
        self.tasks.show_window(active=True)


    def link_tasks_to_sapnotes(self, *args):
        sapnoteview = self.gui.get_widget('viewmenu')
        visor = self.gui.get_widget('visor')
        sapnotes = visor.get_toggled()
        tasks = self.tasks.get_selected()
        self.sap.link_to_task(sapnotes, tasks)

    def actions_bookmark(self, *args):
        db = self.get_service('DB')
        sapnoteview = self.gui.get_widget('viewmenu')
        visor = self.gui.get_widget('visor')
        sapnotes = visor.get_toggled()
        self.debug("Action bookmark triggered for SAP Notes: %s" % sapnotes)
        view = sapnoteview.get_view()
        self.sap.set_bookmark(sapnotes)
        db.save_notes()
        self.refresh_view(view=view)
        self.alert.show('Bookmarks', 'Selected SAP Notes bookmarked', 'information')


    def actions_unbookmark(self, *args):
        db = self.get_service('DB')
        sapnoteview = self.gui.get_widget('viewmenu')
        visor = self.gui.get_widget('visor')
        sapnotes = visor.get_toggled()
        self.debug("Action unbookmark triggered for SAP Notes: %s" % sapnotes)
        self.sap.set_no_bookmark(sapnotes)
        db.save_notes()
        self.refresh_view(view='bookmarks')
        self.alert.show('Bookmarks', 'Selected SAP Notes unbookmarked', 'information')


    def bookmark(self, lsid):
        self.db.set_bookmark(lsid)

    def unbookmark(self, lsid):
        self.db.set_no_bookmark(lsid)


    def actions_export_csv(self, *args):
        db = self.get_service('DB')
        winroot = self.gui.get_widget('mainwndow')
        sapnoteview = self.gui.get_widget('viewmenu')
        visor = self.gui.get_widget('visor')
        sapnotes = visor.get_toggled()
        dialog = Gtk.FileChooserDialog("Save file", winroot,
            Gtk.FileChooserAction.SAVE,
                (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
                 Gtk.STOCK_SAVE, Gtk.ResponseType.OK))
        response = dialog.run()

        if response == Gtk.ResponseType.OK:
            export_path = dialog.get_filename()
            writer = csv.writer(open(export_path, 'w'), delimiter=';', quoting=csv.QUOTE_ALL)
            csvrow = []

            for key in PROPKEYS:
                csvrow.append(key.capitalize())
            writer.writerow(csvrow)

            for tid in sapnotes:
                sid = "0"*(10 - len(tid)) + tid
                self.debug("Exporting SAP Note %s to CSV file" % sid)
                csvrow = []
                props = db.get_sapnote_metadata(sid)

                for prop in PROPKEYS:
                    if prop == 'tasks':
                        tasks = ', '.join(props[prop])
                        csvrow.append(tasks)
                    else:
                        csvrow.append(props[prop])
                writer.writerow(csvrow)
            self.alert.show('Export', 'Selected SAP Notes exported successfully to CSV format', 'information')
            self.debug("Selected SAP Notes exported to CSV format: %s" % export_path)
        else:
            self.alert.show('Export', 'Export canceled by user', 'warning')
            self.debug("Export canceled by user")
        dialog.destroy()


    def actions_export_txt(self, *args):
        rootwin = self.gui.get_widget('mainwndow')
        sapnoteview = self.gui.get_widget('viewmenu')
        visor = self.gui.get_widget('visor')
        sapnotes = visor.get_toggled()
        dialog = Gtk.FileChooserDialog("Save file", rootwin,
            Gtk.FileChooserAction.SAVE,
                (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
                 Gtk.STOCK_SAVE, Gtk.ResponseType.OK))
        response = dialog.run()

        if response == Gtk.ResponseType.OK:
            export_path = dialog.get_filename()
            fout = open(export_path, 'w')
            for sapnote in sapnotes:
                fout.write("%s\n" % sapnote)
            self.alert.show('Export', 'Selected SAP Notes exported successfully to TXT format', 'information')
            self.debug("Selected SAP Notes exported to TXT format: %s" % export_path)
        else:
            self.alert.show('Export', 'Export canceled by user', 'warning')
            self.debug("Export canceled by user")
        dialog.destroy()


    def actions_export_json(self, *args):
        db = self.get_service('DB')
        rootwin = self.gui.get_widget('mainwndow')
        sapnoteview = self.gui.get_widget('viewmenu')
        visor = self.gui.get_widget('visor')
        sapnotes = visor.get_toggled()
        dialog = Gtk.FileChooserDialog("Save file", rootwin,
            Gtk.FileChooserAction.SELECT_FOLDER,
                (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
                 Gtk.STOCK_SAVE, Gtk.ResponseType.OK))
        response = dialog.run()

        if response == Gtk.ResponseType.OK:
            export_path = dialog.get_filename()
            bag = {}
            for tid in sapnotes:
                sid = "0"*(10 - len(tid)) + tid
                sapnote = db.get_sapnote_metadata(sid)
                bag[sid] = sapnote
            now = datetime.now()
            target = export_path + SEP + 'basico-%s.json' % now.strftime("%Y%m%d_%H%M%S")
            db.export_basico_package(bag, target)
            self.alert.show('Export', 'Selected SAP Notes exported successfully to JSON format', 'information')
            self.debug("Selected SAP Notes exported to JSON: %s" % target)
        else:
            self.alert.show('Export', 'Export canceled by user', 'warning')
            self.debug("Export canceled by user")
        dialog.destroy()


    def actions_import_json(self, *args):
        self.import_notes_from_file()


    def actions_import_launchpad(self, *args):
        self.show_addsapnotes_dialog()


    def set_search_filter_key(self, key):
        self.gui.set_key('cmbvalue', key)


    def get_search_filter_key(self):
        cmbvalue = self.gui.get_key('cmbvalue')


    def set_search_term(self, term):
        self.debug("Search term: %s" % term)
        try:
            searchentry = self.gui.get_widget("stySearchInfo")
            searchentry.set_text(term)
        except:
            pass


    def search_notes(self, *args):
        db = self.get_service('DB')
        searchentry = self.gui.get_widget("stySearchInfo")
        cmbvalue = self.gui.get_key('cmbvalue')

        try:
            term = searchentry.get_text()
        except:
            term = ''

        self.debug("cmbvalue: %s" % cmbvalue)
        self.debug("term: %s" % term)

        visor = self.gui.get_widget('visor')
        sapnotes = db.get_notes()
        found = {}

        if len(term) == 0:
            self.current_notes = found = sapnotes
            self.debug("Found: %s" % len(found))
            visor.populate(found)
            self.debug("--> Displaying all database")
            return

        self.debug("Looking for '%s'" % term)
        if cmbvalue == 'search':
            for sid in sapnotes:
                if term.upper() in sapnotes[sid]['title'].upper():
                   found[sid] = sapnotes[sid]
        elif cmbvalue == 'project':
            for sid in sapnotes:
                try:
                    projects = sapnotes[sid]['projects']
                    for project in projects:
                        if term.upper() in project.upper():
                           found[sid] = sapnotes[sid]
                except: pass
        elif cmbvalue == 'task':
            for sid in sapnotes:
                try:
                    tasks = sapnotes[sid]['tasks']
                    for task in tasks:
                        if term.upper() in task.upper():
                           found[sid] = sapnotes[sid]
                except: pass
        elif cmbvalue == 'title':
            for sid in sapnotes:
                try:
                    title = sapnotes[sid]['title']
                    if term.upper() in title.upper():
                       found[sid] = sapnotes[sid]
                except: pass
        elif cmbvalue == 'priority':
            for sid in sapnotes:
                try:
                    priority = sapnotes[sid]['priority']
                    if term.upper() in priority.upper():
                       found[sid] = sapnotes[sid]
                except: pass
        elif cmbvalue == 'component':
            for sid in sapnotes:
                if term.upper() in sapnotes[sid]['componentkey'].upper():
                   found[sid] = sapnotes[sid]
        elif cmbvalue == 'category':
            for sid in sapnotes:
                if term.upper() in sapnotes[sid]['category'].upper():
                   found[sid] = sapnotes[sid]
        elif cmbvalue == 'type':
            for sid in sapnotes:
                if term.upper() in sapnotes[sid]['type'].upper():
                   found[sid] = sapnotes[sid]
        elif cmbvalue == 'version':
            for sid in sapnotes:
                if term.upper() in sapnotes[sid]['version'].upper():
                   found[sid] = sapnotes[sid]
        elif cmbvalue == 'id':
            for sid in sapnotes:
                if term in sid:
                   found[sid] = sapnotes[sid]
        self.debug("Term: '%s' (%d results)" % (term, len(found)))
        self.current_notes = found
        self.debug("Current Notes: %d" % len(self.current_notes))
        visor.populate(found)


    def import_notes(self, entry):
        self.import_notes_from_sapnet()


    def import_notes_from_file(self):
        db = self.get_service('DB')
        notebook = self.gui.get_widget('notebook')
        dialog = Gtk.FileChooserDialog("Select Basico JSON file", None,
            Gtk.FileChooserAction.OPEN,
                (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
                 Gtk.STOCK_SAVE, Gtk.ResponseType.OK))
        response = dialog.run()

        if response == Gtk.ResponseType.OK:
            source = dialog.get_filename()
            try:
                with open(source, 'r') as fp:
                    bag = json.load(fp)
                    db.import_sapnotes(bag)
                    self.debug ("Imported %d notes from %s" % (len(bag), source))
            except Exception as error:
                self.debug("SAP Notes database not found. Creating a new one")
                self.save_notes()
            sapnoteview = self.gui.get_widget('viewmenu')
            self.current_notes = bag
            sapnoteview.populate(bag)
            db.save_notes()
            self.refresh_view()
            switch = self.gui.get_widget('schSelectNotesAllNone')
        else:
            self.alert.show('Import', 'Nothing imported', 'error')
            self.debug("Nothing imported")
        dialog.destroy()

    def import_notes_from_sapnet(self, bag):
        db = self.get_service('DB')
        driver = self.get_service('Driver')
        winroot = self.gui.get_widget('mainwindow')
        self.debug("%d SAP Notes to be downloaded: %s" % (len(bag), ', '.join(list(bag))))

        result = {}

        self.sap.start_fetching(len(bag))
        dlbag = []

        # FIXME: max_workers = 1 = Threads disabled
        with Executor(max_workers=1) as exe:
            jobs = []
            for sapnote in bag:
                job = exe.submit(self.sap.fetch, sapnote)
                jobs.append(job)

            for job in jobs:
                rc, sapnote = job.result()
                self.debug("\tRC SAP Note %s: %s" % (sapnote, rc))
                result[sapnote] = rc
                if rc:
                    sid = "0"*(10 - len(sapnote)) + sapnote
                    dlbag.append(sid)
                time.sleep(0.2)

        driver.close()
        self.sap.stop_fetching()
        db.save_notes()
        db.build_stats()
        self.debug("Task completed.")
        return result
        # ~ dlbag.sort()
        # ~ package = self.db.get_package(dlbag)
        # ~ menuview = self.gui.get_widget('viewmenu')
        # ~ menuview.populate(package)
        # ~ visor = self.gui.get_widget('visor')
        # ~ visor.populate(dlbag)

        #~ notebook.set_current_page(0)
        #~ dialog = Gtk.MessageDialog(winroot, 0, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, "Task completed")
        #~ msgrc = ""
        #~ ko = 0
        #~ ok = 0
        #~ for sapnote in resnotes:
            #~ rc = resnotes[sapnote]
            #~ if rc:
                #~ ok += 1
            #~ else:
                #~ ko += 1
        #~ msgrc += "Downloaded: %d\nErroneus: %d" % (ok, ko)
        #~ self.alert.show('Download', msgrc, 'information')
        #~ dlbag = {}
        #~ mysapnotes = db.get_notes()
        #~ erroneus = set()
        #~ for sid in bag:
            #~ sid = "0"*(10 - len(sid)) + sid
            #~ try:
                #~ dlbag[sid] = mysapnotes[sid]
            #~ except Exception as error:
                #~ self.log.error(error)
                #~ erroneus.add(sid)

        #~ sapnoteview = self.gui.get_widget('viewmenu')
        #~ self.current_notes = dlbag
        #~ self.refresh_and_clear_view()
        #~ sapnoteview.populate(dlbag)
        #~ sapnoteview.expand_all()


    def stop_dl_notes(self, *args):
        notebook = self.gui.get_widget('notebook')
        txtSAPNotes = self.gui.get_widget('txtSAPNotes')
        buffer = txtSAPNotes.get_buffer()
        buffer.set_text("")
        notebook.set_current_page(0)
        self.refresh_view()
        boxMenu = self.gui.get_widget('boxMenu')
        boxMenu.show_all()
        self.refresh_and_clear_view()


    def rebuild_database(self, *args):
        self.debug("Rebuild database...")

        db = self.get_service('DB')
        CACHE_DIR = self.get_var('CACHE', 'local')
        files = glob.glob("%s%s*.xml" % (CACHE_DIR, SEP))
        sap = self.app.get_service('SAP')
        for filename in files:
            self.debug("Filename: %s" % filename)
            sid = basename(filename)[0:-4]
            self.debug("SAP Note Id: %s" % sid)

            valid = False
            if db.is_stored(sid):
                self.debug("\tSAP Note %s will be analyzed again" % sid)
                content = db.get_sapnote_content(sid)
                sapnote = sap.analyze_sapnote_metadata(sid, content)
                if len(sapnote) > 0:
                    db = self.get_service('DB')
                    db.add(sapnote)
                    db.store(sid, content)
                    valid = True

        self.refresh_view()

    def refresh_and_clear_view(self, *args):
        self.debug("Refresh & clear view ( I)")
        switch = self.gui.get_widget('schSelectNotesAllNone')
        sapnoteview = self.gui.get_widget('viewmenu')
        self.set_search_filter_key('search')
        self.set_search_term('')
        self.search_notes()


    def refresh_view(self, action=None, callback=None, view=None):
        self.debug("Refresh & clear view (II)")
        window = self.gui.get_widget('mainwindow')
        viewmenu = self.gui.get_widget('viewmenu')

        if view is not None:
            viewlabel = self.gui.get_widget('lblViewCurrent')
            name = "<b>%-10s</b>" % view.capitalize()
            viewlabel.set_markup(name)
        viewmenu.set_view(view)


    def setup_dashboard(self):
        lblStatsSAPNotesCount = self.gui.add_widget('lblStatsSAPNotesCount')


    def show_addsapnotes_dialog(self, *args):
        sapnoteview = self.gui.get_widget('viewmenu')
        sapnoteview.set_view('download')
        notebook = self.gui.get_widget('notebook')
        boxMenu = self.gui.get_widget('boxMenu')
        boxMenu.hide()
        notebook.set_current_page(1)


    def default_preferences(self, *args):
        prefs = self.get_service('Settings')
        gui = self.get_service('GUI')
        settings = prefs.get_default_settings()
        for key in settings:
            widget = gui.get_widget(key)
            widget.set_text(str(settings[key]))

        self.config[self.section] = settings
        self.save_config()
        self.debug("Settings reverted to default")


    #~ def update_components_stats(self, *args):
        #~ statsviewer = self.gui.get_widget('scrStatsViewer')
        #~ view = WebKit.WebView()
        #~ chart = self.stats.build_pie_maincomp()
        #~ view.load_string(chart, 'text/html', 'UTF-8','/')
        #~ self.gui.swap_widget(statsviewer, view)


    #~ def update_categories_stats(self, *args):
        #~ statsviewer = self.gui.get_widget('scrStatsViewer')
        #~ view = WebKit.WebView()
        #~ chart = self.stats.build_pie_categories()
        #~ view.load_string(chart, 'text/html', 'UTF-8','/')
        #~ self.gui.swap_widget(statsviewer, view)


    def check_task_link_button_status(self):
        tasks = self.get_service('Tasks')
        button = self.gui.get_widget('btnLinkTasksApply')
        sapnoteview = self.gui.get_widget('viewmenu')

        tasks_selected = len(tasks.get_selected()) > 0
        notes_selected = len(sapnoteview.get_selected_notes()) > 0
        if  tasks_selected and notes_selected:
            button.set_sensitive(True)
            self.debug("Task link button enabled")
        else:
            button.set_sensitive(False)
            self.debug("Task link button disabled")


    def test(self, *args):
        self.debug(args)