#!/usr/bin/python3

import sys
sys.path.append("/usr/share/system-config-printer")
import socket
import logging.handlers
import threading
import gi
gi.require_version('Notify', '0.7')
from gi.repository import Notify
from gi.repository import GLib
from gi.repository import GObject
from gi.repository import GdkPixbuf
import os, shutil
from gi.repository import Pango
import pwd
import subprocess
import time, re
import _thread
import urllib.request, urllib.parse, urllib.error
import datetime
import getpass
import math
import traceback
from functools import reduce
import json
import operator

try:
    gi.require_version('Polkit', '1.0')
    from gi.repository import Polkit
except:
    Polkit = False

gi.require_version('GdkPixbuf', '2.0')
from gi.repository import GdkPixbuf
try:
    gi.require_version('Gdk', '3.0')
    from gi.repository import Gdk
    gi.require_version('Gtk', '3.0')
    from gi.repository import Gtk
    Gtk.init (sys.argv)
except RuntimeError as e:
    print ("xsanescanjob-history:", e)
    print ("This is a graphical application and requires DISPLAY to be set.")
    sys.exit (1)

import locale
try:
    locale.setlocale (locale.LC_ALL, "")
except locale.Error:
    os.environ['LC_ALL'] = 'C'
    locale.setlocale (locale.LC_ALL, "")
import gettext

import config

key = 'XSANE_SCANJOB_HISTORY'
value = os.getenv(key);
state = "0"

if value is not None:
    _debug=True
else:
    _debug=False

pkgdata = config.pkgdatadir
iconpath = os.path.join (pkgdata, 'icons/')
sys.path.append (pkgdata)

SCANJOB_HISTORY_SERVICE_VERSION = '0.0.1'

PROJECTROOT = '../'
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), PROJECTROOT)))

if getpass.getuser() == 'root':
    if os.path.exists("/" + getpass.getuser() + "/.hanguang/scan") == False:
        os.system("mkdir -p /" + getpass.getuser() + "/.hanguang/scan/")
    server_address = "/" + getpass.getuser() + "/.hanguang/scan/scan_domain_sockets"
    scanjob_json_path = "/" + getpass.getuser() + "/.hanguang/scan/hanguangscanjob.json"
else: 
    if os.path.exists("/home/" + getpass.getuser() + "/.hanguang/scan") == False:
        os.system("mkdir -p /home/" + getpass.getuser() + "/.hanguang/scan/")
    server_address = "/home/" + getpass.getuser() + "/.hanguang/scan/scan_domain_sockets"
    scanjob_json_path = "/home/" + getpass.getuser() + "/.hanguang/scan/hanguangscanjob.json"

# Make sure the socket does not already exist
try:
    os.unlink(server_address)
except OSError:
    if os.path.exists(server_address):
        raise

the_server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
the_server_socket.bind(server_address)
the_server_socket.listen(5)
global the_control_thread

log = logging.getLogger('snowdeer_log')
log.setLevel(logging.DEBUG)

formatter = logging.Formatter('[%(levelname)s] (%(filename)s:%(lineno)d) > %(message)s')

if _debug is True:
    fileHandler = logging.FileHandler('/tmp/xsanescanjob_log.txt')
streamHandler = logging.StreamHandler()

if _debug is True:
    fileHandler.setFormatter(formatter)
streamHandler.setFormatter(formatter)

if _debug is True:
    log.addHandler(fileHandler)
log.addHandler(streamHandler)

def debugprint (x):
    if _debug is True:
        log.debug(x)

def get_debugging ():
    return _debug

def set_debugging (d):
    global _debug
    _debug = d

def fatalException (exitcode=1):
    nonfatalException (type="fatal", end="Exiting")
    sys.exit (exitcode)

def nonfatalException (type="non-fatal", end="Continuing anyway.."):
    d = get_debugging ()
    set_debugging (True)
    debugprint ("Caught %s exception.  Traceback:" % type)
    (type, value, tb) = sys.exc_info ()
    extxt = traceback.format_exception_only (type, value)
    for line in traceback.format_tb(tb):
        debugprint (line.strip ())
    debugprint (extxt[0].strip ())
    debugprint (end)
    set_debugging (d)
    

log.info("XSane Service Start")

the_active_thread = 0
global the_scanjob_viwer

try:
    gi.require_version('Secret', '1')
    from gi.repository import Secret
    USE_SECRET=True
except ValueError:
    USE_SECRET=False

import gettext
gettext.install(domain=config.PACKAGE, localedir=config.localedir)

ICON="printer"
ICON_SIZE=30
SEARCHING_ICON="document-print-preview"

# We need to call Notify.init before we can check the server for caps
Notify.init('System Config Scanner Notification')

class GtkGUI(GObject.GObject):
    def getWidgets(self, widgets, domain=None):
        ui_dir = os.path.dirname(os.path.abspath(__file__))
        self._bld = []
        for xmlfile, names in widgets.items ():
            bld = Gtk.Builder ()
            self._bld.append (bld)

            if domain:
                bld.set_translation_domain (domain)

            bld.add_from_file (os.path.join (ui_dir, xmlfile + ".ui"))
            for name in names:
                widget = bld.get_object(name)
                if widget is None:
                    raise ValueError("Widget '%s' not found" % name)
                setattr(self, name, widget)

            try:
                win = widget.get_top_level()
            except AttributeError:
                win = None

            if win != None:
                Gtk.Window.set_focus_on_map(widget.get_top_level (),
                                            self.focus_on_map)
                widget.show()

    def connect_signals (self):
        for bld in self._bld:
            bld.connect_signals (self)

class ScanJobViewer (GtkGUI):
    required_job_attributes = set(['job-k-octets',
                                   'job-name',
                                   'job-originating-user-name',
                                   'job-printer-uri',
                                   'job-state',
                                   'time-at-creation',
                                   'auth-info-required',
                                   'job-preserved'])

    __gsignals__ = {
        'finished':    (GObject.SignalFlags.RUN_LAST, None, ())
        }

    def __init__(self, bus=None, loop=None,
                 applet=False, suppress_icon_hide=False,
                 my_jobs=True, specific_dests=None,
                 parent=None):
        GObject.GObject.__init__ (self)
        self.loop = loop
        self.applet = applet
        self.suppress_icon_hide = suppress_icon_hide
        self.my_jobs = my_jobs
        self.specific_dests = specific_dests
        notify_caps = Notify.get_server_caps ()
        self.notify_has_actions = "actions" in notify_caps
        self.notify_has_persistence = "persistence" in notify_caps
        debugprint ("ScanJobViewer __init__")

        self.jobs = {}
        self.jobiters = {}
        self.jobids = []
        self.jobs_attrs = {} # dict of jobid->(GtkListStore, page_index)
        self.active_jobs = set() # of job IDs
        self.stopped_job_prompts = set() # of job IDs
        self.printer_state_reasons = {}
        self.num_jobs_when_hidden = 0
        self.connecting_to_device = {} # dict of printer->time first seen
        self.state_reason_notifications = {}
        self.auth_info_dialogs = {} # by job ID
        self.job_creation_times_timer = None
        self.completed_job_notifications = {}
        self.authenticated_jobs = set() # of job IDs
        self.ops = []
        self.job_id = 0
        self.remove_list = []
        self.remove_model = {}

        self.getWidgets ({"xsanescanjob":
                              ["xsanescanjob",
                               "treeview",
                               "statusbar",
                               "toolbar"]},
                         domain=config.PACKAGE)

        job_action_group = Gtk.ActionGroup (name="JobActionGroup")
        job_action_group.add_actions ([
                ("delete-job", Gtk.STOCK_DELETE, _("_Delete"), None,
                 _("Delete selected jobs"), self.on_job_delete_activate),
                ("job-attributes", None, _("_View Attributes"), None, None,
                 self.on_job_attributes_activate),
                ("close", Gtk.STOCK_CLOSE, None, "<ctrl>w",
                 _("Close this window"), self.on_delete_event)
                ])
        self.job_ui_manager = Gtk.UIManager ()
        self.job_ui_manager.insert_action_group (job_action_group, -1)
        self.job_ui_manager.add_ui_from_string (
"""
<ui>
 <accelerator action="delete-job"/>
 <accelerator action="job-attributes"/>
 <accelerator action="close"/>
</ui>
"""
)
        self.job_ui_manager.ensure_update ()
        self.xsanescanjob.add_accel_group (self.job_ui_manager.get_accel_group ())
        self.job_context_menu = Gtk.Menu ()
        for skip, ellipsize, name, setter in \
                [(False, False, _("Job"), self._set_job_job_number_text)]:
            if name is "Size":
                continue
            if applet and skip:
                # Skip the user column when running as applet.
                continue

            cell = Gtk.CellRendererText()
            if ellipsize:
                # Ellipsize the 'Document' and 'Printer' columns.
                cell.set_property ("ellipsize", Pango.EllipsizeMode.END)
                cell.set_property ("width-chars", 20)
            column = Gtk.TreeViewColumn(name, cell)
            column.set_cell_data_func (cell, setter, None)
            column.set_resizable(True)
            self.treeview.append_column(column)

        cell = Gtk.CellRendererText ()
        column = Gtk.TreeViewColumn (_("User"), cell, text=1)
        column.set_resizable (True)
        self.treeview.append_column (column)

        cell = Gtk.CellRendererText ()
        column = Gtk.TreeViewColumn (_("Document"), cell, text=2)
        column.set_resizable (True)
        self.treeview.append_column (column)

        cell = Gtk.CellRendererText ()
        column = Gtk.TreeViewColumn (_("扫描仪"), cell, text=3)
        column.set_resizable (True)
        self.treeview.append_column (column)

        cell = Gtk.CellRendererText ()
        column = Gtk.TreeViewColumn (_("Size"), cell, text=4)
        column.set_resizable (True)
        self.treeview.append_column (column)

        cell = Gtk.CellRendererText ()
        column = Gtk.TreeViewColumn (_("时间"), cell, text=5)
        column.set_resizable (True)
        self.treeview.append_column (column)

        cell = Gtk.CellRendererText ()
        column = Gtk.TreeViewColumn (_("Status"), cell, text=6)
        column.set_resizable (True)
        self.treeview.append_column (column)
        # column = Gtk.TreeViewColumn (_("Status"))
        # icon = Gtk.CellRendererPixbuf ()
        # column.pack_start (icon, False)
        # text = Gtk.CellRendererText ()
        # text.set_property ("ellipsize", Pango.EllipsizeMode.END)
        # text.set_property ("width-chars", 20)
        # column.pack_start (text, True)
        # column.set_cell_data_func (icon, self._set_job_status_icon, None)
        # column.set_cell_data_func (text, self._set_job_status_text, None)
        # self.treeview.append_column (column)

        self.store = Gtk.TreeStore(int, str, str, str, str, str, str)
        self.store.set_sort_column_id (0, Gtk.SortType.DESCENDING)
        self.treeview.set_model(self.store)
        self.treeview.set_rules_hint (True)
        self.selection = self.treeview.get_selection()
        self.selection.set_mode(Gtk.SelectionMode.MULTIPLE)
        self.selection.connect('changed', self.on_selection_changed)
        self.treeview.connect ('button_press_event',
                               self.on_treeview_button_press_event)
        self.treeview.connect ('button_release_event',
                               self.on_treeview_button_release_event)
        self.treeview.connect ('popup-menu', self.on_treeview_popup_menu)

        self.xsanescanjob.set_icon_name (ICON)
        self.xsanescanjob.hide ()

        if specific_dests:
            the_dests = reduce (lambda x, y: x + ", " + y, specific_dests)

        if my_jobs:
            if specific_dests:
                title = _("my jobs on %s") % the_dests
            else:
                title = _("my jobs")
        else:
            if specific_dests:
                title = "%s" % the_dests
            else:
                title = _("all jobs")
        self.xsanescanjob.set_title (_("工作日志(%s)") % title) # Document Scan Job History

        if parent:
            self.xsanescanjob.set_transient_for (parent)

        def load_icon(theme, icon):
            try:
                pixbuf = theme.load_icon (icon, ICON_SIZE, 0)
            except GObject.GError:
                debugprint ("No %s icon available" % icon)
                # Just create an empty pixbuf.
                pixbuf = GdkPixbuf.Pixbuf.new (GdkPixbuf.Colorspace.RGB,
                                         True, 8, ICON_SIZE, ICON_SIZE)
                pixbuf.fill (0)
            return pixbuf

        theme = Gtk.IconTheme.get_default ()
        self.icon_jobs = load_icon (theme, ICON)
        self.icon_jobs_processing = load_icon (theme, "scanner-bmf-series")
        self.icon_no_jobs = self.icon_jobs.copy ()
        self.icon_no_jobs.fill (0)
        self.icon_jobs.composite (self.icon_no_jobs,
                                  0, 0,
                                  self.icon_no_jobs.get_width(),
                                  self.icon_no_jobs.get_height(),
                                  0, 0,
                                  1.0, 1.0,
                                  GdkPixbuf.InterpType.BILINEAR,
                                  127)
        if self.applet and not self.notify_has_persistence:
            self.statusicon = Gtk.StatusIcon ()
            self.statusicon.set_from_pixbuf (self.icon_no_jobs)
            self.statusicon.connect ('activate', self.toggle_window_display)
            self.statusicon.set_visible (False)

        self.connect_signals ()

        if not self.applet:
            self.xsanescanjob.show ()

        self.JobsAttributesWindow = Gtk.Window()
        self.JobsAttributesWindow.set_title (_("Job attributes"))
        self.JobsAttributesWindow.set_position(Gtk.WindowPosition.MOUSE)
        self.JobsAttributesWindow.set_default_size(800, 600)
        self.JobsAttributesWindow.set_transient_for (self.xsanescanjob)
        self.JobsAttributesWindow.connect("delete_event",
                                          self.job_attributes_on_delete_event)
        self.JobsAttributesWindow.add_accel_group (self.job_ui_manager.get_accel_group ())
        attrs_action_group = Gtk.ActionGroup (name="AttrsActionGroup")
        attrs_action_group.add_actions ([
                ("close", Gtk.STOCK_CLOSE, None, "<ctrl>w",
                 _("Close this window"), self.job_attributes_on_delete_event)
                ])
        self.attrs_ui_manager = Gtk.UIManager ()
        self.attrs_ui_manager.insert_action_group (attrs_action_group, -1)
        self.attrs_ui_manager.add_ui_from_string (
"""
<ui>
 <accelerator action="close"/>
</ui>
"""
)
        self.attrs_ui_manager.ensure_update ()
        self.JobsAttributesWindow.add_accel_group (self.attrs_ui_manager.get_accel_group ())
        vbox = Gtk.VBox ()
        self.JobsAttributesWindow.add (vbox)
        toolbar = Gtk.Toolbar ()
        action = self.attrs_ui_manager.get_action ("/close")
        item = action.create_tool_item ()
        item.set_is_important (True)
        toolbar.insert (item, 0)
        vbox.pack_start (toolbar, False, False, 0)
        self.notebook = Gtk.Notebook()
        vbox.pack_start (self.notebook, True, True, 0)

    def cleanup (self):
        self.xsanescanjob.hide ()

        # Close any open notifications.
        for l in [self.state_reason_notifications.values ()]:
            for notification in l:
                if getattr (notification, 'closed', None) != True:
                    try:
                        notification.close ()
                    except GLib.GError:
                        # Can fail if the notification wasn't even shown
                        # yet (as in bug #571603).
                        pass
                    notification.closed = True

        if self.job_creation_times_timer is not None:
            GLib.source_remove (self.job_creation_times_timer)
            self.job_creation_times_timer = None

        for op in self.ops:
            op.destroy ()

        if self.applet and not self.notify_has_persistence:
            self.statusicon.set_visible (False)

        self.emit ('finished')

    def on_delete_event(self, *args):
        debugprint("on_delete_event####")
        if self.applet or not self.loop:
            self.xsanescanjob.hide ()
            self.xsanescanjob.visible = False
            if not self.applet:
                # Being run from main app, not applet
                self.cleanup ()
        else:
            self.loop.quit ()
        return True

    def job_attributes_on_delete_event(self, widget, event=None):
        print("job_attributes_on_delete_event####")
        for page in range(self.notebook.get_n_pages()):
            self.notebook.remove_page(-1)
        self.jobs_attrs = {}
        self.JobsAttributesWindow.hide()
        return True

    def toggle_window_display(self, icon, force_show=False):
        visible = getattr (self.xsanescanjob, 'visible', None)
        if force_show:
            visible = False

        if self.notify_has_persistence:
            if visible:
                self.xsanescanjob.hide ()
            else:
                self.xsanescanjob.show ()
        else:
            if visible:
                w = self.xsanescanjob.get_window()
                aw = self.JobsAttributesWindow.get_window()
                (loc, s, area, o) = self.statusicon.get_geometry ()

                if loc:
                    w.set_skip_taskbar_hint (True)
                    if aw is not None:
                        aw.set_skip_taskbar_hint (True)
                    self.xsanescanjob.iconify ()
                else:
                    self.xsanescanjob.set_visible (False)
            else:
                self.xsanescanjob.present ()
                self.xsanescanjob.set_skip_taskbar_hint (False)
                aw = self.JobsAttributesWindow.get_window()
                if aw is not None:
                    aw.set_skip_taskbar_hint (False)

        self.xsanescanjob.visible = not visible

    def on_show_completed_jobs_clicked(self, toggletoolbutton):
        if toggletoolbutton.get_active():
            which_jobs = "all"
        else:
            which_jobs = "not-completed"

    def print_error_dialog_response(self, dialog, response, jobid):
        pass

    def on_troubleshoot_quit(self, troubleshooter):
        pass

    def add_job (self, job, data, connection=None):
        self.update_job (job, data, connection=connection)
        #self.job_id = self.job_id + 1
        #job = self.job_id

        store = self.store
        iter = self.store.append (None)
        store.set_value (iter, 0, job)
        store.set_value (iter, 1, data.get(_("User")))
        store.set_value (iter, 2, data.get(_("Document")))
        store.set_value (iter, 3, data.get(_("Scanner")))
        store.set_value (iter, 4, data.get(_("Size")))
        store.set_value (iter, 5, data.get(_("Time")))
        if data.get(_("Status")) == '0':
            store.set_value (iter, 6, "成功")
        else:
            store.set_value (iter, 6, "失败")
        debugprint ("Job %d added" % job)
        self.jobiters[job] = iter

        self.treeview.queue_draw ()

        range = self.treeview.get_visible_range ()
        if range is not None:
            (start, end) = range
            if (self.store.get_sort_column_id () == (0,
                                                     Gtk.SortType.DESCENDING) and
                start == Gtk.TreePath(1)):
                # This job was added job above the visible range, and
                # we are sorting by descending job ID.  Scroll to it.
                self.treeview.scroll_to_cell (Gtk.TreePath(), None,
                                              False, 0.0, 0.0)

    def update_job (self, job, data, connection=None):
        print("update_job####")
        pass

    def set_statusicon_visibility (self):
        if not self.applet:
            return

        if self.suppress_icon_hide:
            # Avoid hiding the icon if we've been woken up to notify
            # about a new printer.
            self.suppress_icon_hide = False
            return

        open_notifications += len (self.completed_job_notifications.keys ())
        for reason, notification in self.state_reason_notifications.items():
            if getattr (notification, 'closed', None) != True:
                open_notifications += 1
        num_jobs = len (self.active_jobs)

        debugprint ("open notifications: %d" % open_notifications)
        debugprint ("num_jobs: %d" % num_jobs)
        debugprint ("num_jobs_when_hidden: %d" % self.num_jobs_when_hidden)

        if self.notify_has_persistence:
            return

        # Don't handle tooltips during the mainloop recursion at the
        # end of this function as it seems to cause havoc (bug #664044,
        # bug #739745).
        self.statusicon.set_has_tooltip (False)

        self.statusicon.set_visible (open_notifications > 0 or
                                     num_jobs > self.num_jobs_when_hidden)

        # Let the icon show/hide itself before continuing.
        while self.process_pending_events and Gtk.events_pending ():
            Gtk.main_iteration ()

    def on_treeview_popup_menu (self, treeview):
        event = Gdk.Event (Gdk.EventType.NOTHING)
        self.show_treeview_popup_menu (treeview, event, 0)

    def on_treeview_button_release_event(self, treeview, event):
        if event.button == 3:
            self.show_treeview_popup_menu (treeview, event, event.button)

    def on_treeview_button_press_event(self, treeview, event):
        if event.button == 1:
            if event.type == Gdk.EventType._2BUTTON_PRESS:
                debugprint("double clicked")
                selection = self.treeview.get_selection ()
                (model, pathlist) = selection.get_selected_rows()
                for path in pathlist :
                    tree_iter = model.get_iter(path)
                    value = model.get_value(tree_iter,2)
                extension = os.path.splitext(value)[1][1:].strip().lower()
                if value == "Viewer Mode":
                    return
                if str(extension) == "pdf":
                    os.system("evince %s &" % value)
                elif str(extension) == "ps":
                    os.system("evince %s &" % value)
                else:
                    os.system("gimp %s &" % value)
                # Remove all single click events

    def update_sensitivity (self, selection = None):
        if (selection is None):
            selection = self.treeview.get_selection ()
        (model, pathlist) = selection.get_selected_rows()
        debugprint("update sensitivity")
        for path in pathlist :
             tree_iter = model.get_iter(path)
             value = model.get_value(tree_iter,2)

        self.remove_model = model
        for p in reversed(pathlist):
            itr = model.get_iter(p)
            new_itr = itr.copy()
            self.remove_list.append(new_itr)

#        self.jobids = []
#        for path in pathlist:
#            iter = self.store.get_iter (path)
#            jobid = self.store.get_value (iter, 0)


    def on_selection_changed (self, selection):
        debugprint("on_selection_changed####")
        self.update_sensitivity (selection)

    def show_treeview_popup_menu (self, treeview, event, event_button):
        # Right-clicked.
        self.job_context_menu.popup (None, None, None, None, event_button,
                                     event.get_time ())

    def on_icon_hide_activate(self, menuitem):
        self.num_jobs_when_hidden = len (self.jobs.keys ())
        self.set_statusicon_visibility ()

    def on_icon_configure_printers_activate(self, menuitem):
        env = {}
        for name, value in os.environ.items ():
            if name == "SYSTEM_CONFIG_PRINTER_UI":
                continue
            env[name] = value
        p = subprocess.Popen ([ "system-config-printer" ],
                              close_fds=True, env=env)
        GLib.timeout_add_seconds (10, self.poll_subprocess, p)

    def poll_subprocess(self, process):
        returncode = process.poll ()
        return returncode is None

    def on_icon_quit_activate (self, menuitem):
        self.cleanup ()
        if self.loop:
            self.loop.quit ()

    def on_job_cancel_activate(self, menuitem):
        self.on_job_cancel_activate2(False)

    def on_job_delete_activate(self, menuitem):
        self.on_job_cancel_activate2(True)

    def on_job_cancel_activate2(self, purge_job):
        pass

    def on_canceljobs_finished (self, canceljobsoperation):
        canceljobsoperation.destroy ()
        i = self.ops.index (canceljobsoperation)
        del self.ops[i]

    def on_canceljobs_error (self, canceljobsoperation, jobid, exc):
        raise exc

    def on_job_release_activate(self, menuitem):
        debugprint("on_job_release_activate####")

    def on_job_reprint_activate(self, menuitem):
        pass

    def on_job_retrieve_activate(self, menuitem):
        debugprint("on_job_retrieve_activate####")
    def _submenu_connect_hack (self, item, callback, *args):
        pass

    def on_delete_clicked(self, toolbutton):
        debugprint("Delete clicked")
        remove_jobs = []
        selection = self.treeview.get_selection ()
        (model, pathlist) = selection.get_selected_rows()
        
        for path in pathlist :
             tree_iter = model.get_iter(path)
             job = model.get_value(tree_iter,0)
             remove_jobs.append(str(job))

        for p in reversed(pathlist):
            itr = model.get_iter(p)
            model.remove(itr)

        if os.path.exists(scanjob_json_path):
            jobs = load_json_file(scanjob_json_path)

        for j in remove_jobs:
            del jobs[j]
       
        with open(scanjob_json_path, "w") as write_file:
            json.dump(jobs, write_file)

    def on_job_attributes_activate(self, menuitem):
        pass

    def update_job_attributes_viewer(self, jobid, conn=None):
        pass

    def job_is_active (self, jobdata):
        pass

    def get_icon_pixbuf (self, have_jobs=None):
        pass

    def set_statusicon_tooltip (self, tooltip=None):
        pass

    def update_status (self, have_jobs=None):
        pass

    ## Notifications
    def notify_printer_state_reason_if_important (self, reason):
        pass

    def notify_printer_state_reason (self, reason):
        pass

    def on_state_reason_notification_closed (self, notification, reason=None):
        pass

    def notify_completed_job (self, jobid):
        pass

    def on_completed_job_notification_closed (self, notification, reason=None):
        jobid = notification.jobid
        del self.completed_job_notifications[jobid]
        self.set_statusicon_visibility ()

    ## Monitor signal handlers
    def on_refresh (self, mon):
        self.store.clear ()
        self.jobs = {}
        self.active_jobs = set()
        self.jobiters = {}
        self.printer_uri_index = PrinterURIIndex ()

    def remove_job(self, jobid):
        # If the job has finished, let the user know.
        if jobid in self.jobiters:
            self.store.remove (self.jobiters[jobid])
            del self.jobiters[jobid]
            #del self.jobs[jobid]

        if jobid in self.active_jobs:
            self.active_jobs.remove (jobid)

        if jobid in self.jobs_attrs:
            del self.jobs_attrs[jobid]

        #self.update_status ()

    def state_reason_added (self, mon, reason):
        (title, text) = reason.get_description ()
        printer = reason.get_printer ()

        try:
            l = self.printer_state_reasons[printer]
        except KeyError:
            l = []
            self.printer_state_reasons[printer] = l

        reason.user_notified = False
        l.append (reason)
        self.update_status ()
        self.treeview.queue_draw ()

        if not self.applet:
            return

        # Find out if the user has jobs queued for that printer.
        for job, data in self.jobs.items ():
            if not self.job_is_active (data):
                continue
            if data['job-printer-name'] == printer:
                # Yes!  Notify them of the state reason, if necessary.
                self.notify_printer_state_reason_if_important (reason)
                break

    def state_reason_removed (self, mon, reason):
        printer = reason.get_printer ()
        try:
            reasons = self.printer_state_reasons[printer]
        except KeyError:
            debugprint ("Printer not found")
            return

        try:
            i = reasons.index (reason)
        except IndexError:
            debugprint ("Reason not found")
            return

        del reasons[i]

        self.update_status ()
        self.treeview.queue_draw ()

        if not self.applet:
            return

        tuple = reason.get_tuple ()
        try:
            notification = self.state_reason_notifications[tuple]
            if getattr (notification, 'closed', None) != True:
                try:
                    notification.close ()
                except GLib.GError:
                    # Can fail if the notification wasn't even shown
                    # yet (as in bug #545733).
                    pass

            del self.state_reason_notifications[tuple]
            self.set_statusicon_visibility ()
        except KeyError:
            pass

    def still_connecting (self, mon, reason):
        if not self.applet:
            return

        self.notify_printer_state_reason (reason)

    ### Cell data functions
    def _set_job_job_number_text (self, column, cell, model, iter, *data):
        cell.set_property("text", str (model.get_value (iter, 0)))

    def _set_job_user_text (self, column, cell, model, iter, *data):
        jobid = model.get_value (iter, 0)
        try:
            job = self.jobs[jobid]
        except KeyError:
            return

        cell.set_property("text", job.get ('job-originating-user-name',
                                           _("Unknown")))

    def _set_job_document_text (self, column, cell, model, iter, *data):
        jobid = model.get_value (iter, 0)
        try:
            job = self.jobs[jobid]
        except KeyError:
            return

        cell.set_property("text", job.get('job-name', _("Unknown")))

    def _set_job_scanner_text (self, column, cell, model, iter, *data):
        jobid = model.get_value (iter, 0)
        try:
            reasons = self.jobs[jobid].get('job-state-reasons')
        except KeyError:
            return

        if reasons == 'printer-stopped':
            reason = ' - ' + _("disabled")
        else:
            reason = ''
        cell.set_property("text", self.jobs[jobid]['job-printer-name']+reason)

    def _set_job_size_text (self, column, cell, model, iter, *data):
        jobid = model.get_value (iter, 0)
        try:
            job = self.jobs[jobid]
        except KeyError:
            return

        size = _("Unknown")
        if 'job-k-octets' in job:
            size = str (job['job-k-octets']) + 'k'
        cell.set_property("text", size)

    def _find_job_state_text (self, job):
        pass
        


    def _set_job_status_icon (self, column, cell, model, iter, *data):
        # pass
        icon = self.icon_jobs_processing
        cell.set_property ("pixbuf", icon)

    def _set_job_status_text (self, column, cell, model, iter, *data):
        pass
        # jobid = modl.get_value(iter, 0)
        # try:
        #     job = self.jobs[jobid]
        # except:
        #     return
        # cell.set_property ("text", job["job-state"])
        # if state == '0':
        #     cell.set_property ("text", "   成功      ")
        # elif state == '2':
        #     cell.set_property ("text", "   用户取消      ")
        # else:
        #     cell.set_property ("text", "   失败      ")

def load_json_file( path ):
  data = None
  with open(path, "r") as f:
    data = json.load(f)
  return data

is_gui_thread_running = False

def launch_gui(show_jobs):
    global is_gui_thread_running
    global the_scanjob_viwer

    is_gui_thread_running = True
    Gdk.threads_init ()

    the_scanjob_viwer = ScanJobViewer (None, None, my_jobs=False,
                                  specific_dests=[show_jobs])
    the_scanjob_viwer.connect ('finished', Gtk.main_quit)

    if os.path.exists(scanjob_json_path):
        d = load_json_file(scanjob_json_path)
        for jobid, job in d.items():
            the_scanjob_viwer.add_job(int(jobid), job)

    Gdk.threads_enter ()
    try:
        Gtk.main()
    finally:
        is_gui_thread_running = False
        Gdk.threads_leave ()

def launch_gui_thread():
    # launch_gui("BMF Series Scan Job History")
    launch_gui("BMF 扫描仪工作日志")


def convert_size(size_bytes):
   if size_bytes == 0:
       return "0B"
   size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
   i = int(math.floor(math.log(size_bytes, 1024)))
   p = math.pow(1024, i)
   s = round(size_bytes / p, 2)
   return "%s %s" % (s, size_name[i])

def handle_message():
    global the_active_thread
    global the_scanjob_viwer
    global is_gui_thread_running
    global state

    the_control_thread = _thread.get_ident()
    log.debug("thread id : %d" % the_control_thread)
    log.debug("wait for message")

    while True:
        rdata = client_socket.recv(2048)
        recv_data = rdata.decode()
        if (recv_data == ''):
            log.error("*****Client Closed!! Thread exits")
            _thread.exit()
        message = recv_data.split('#')
        message.pop(-1)

        log.info("recive message")
        for cmd in message:
            log.debug(cmd)
            if (cmd == 'q' or cmd == 'Q'):
                client_socket.close()
                break;
            elif (cmd.rfind('launch_scanjob') == 0):
                values = cmd.split(":")
                # log.info("values ***")
                #scan_deivce_name_path = values[1]
                log.debug("Start gui thread")
                if is_gui_thread_running is False:
                    _thread.start_new_thread(launch_gui_thread, ())
                else:
                    debugprint("Ignore launching")
                msg = 'OK'
                #client_socket.send(msg.encode())
            elif (cmd.rfind('add_scanjob') == 0):
                values = cmd.split(":")
                log.info("add scanjob")
                for value in values:
                    debugprint(value)

                history_file_path = values[1]
                state = values[5];
                log.error(state)
                count = 10
                if values[2] == "Viewer Mode":
                    count = 0
                values[2] = values[2].replace('`', ':')
                time.sleep(1)
                while os.path.exists(values[2]) == False:
                    time.sleep(1)
                    count -= 1
                    if count is 0:
                        break

                if count != 0:
                    size = os.path.getsize(values[2])
                else:
                    size = 0

                if size is not 0:
                    job_file_size = convert_size(size)
                else:
                    job_file_size = "Unknown size"

                if os.path.exists(scanjob_json_path):
                    jobs = load_json_file(scanjob_json_path)
                    if len(jobs) == 0:
                        jobid = 1
                    else:
                        json_string = json.dumps(jobs)
                        debugprint(json_string)
                        max_jobid = max(jobs, key=int)
                        debugprint(max_jobid)
                        jobid = int(max_jobid)  + 1
                else:
                    jobid = 1
                    debugprint(jobid)

                job = {
                    _("User"): getpass.getuser(),
                    _("Document"): values[2],
                    _("Scanner"): values[3].replace('`', ':'),
                    _("Size"): job_file_size,
                    _("Time"): datetime.datetime.now().strftime("%Y-%m-%d, %H:%M:%S"),
                    _("Status"): values[5]
                }

                if jobid is 1:
                    jobs = {
                        jobid:job
                    }
                else:
                    jobs[jobid] = job

                with open(scanjob_json_path, "w") as write_file:
                    json.dump(jobs, write_file)
                the_scanjob_viwer.add_job(jobid, job)
                msg = 'OK'
                #client_socket.send(msg.encode())
            elif (cmd.rfind('remove_scanjob') == 0):
                values = cmd.split(":")
                for value in values:
                    debugprint(value)
                job_number = int(values[1])
                log.info("remove scanjob")
                the_scanjob_viwer.remove_job(job_number)
                msg = 'OK'
                #client_socket.send(msg.encode())
            else:
                log.error("invalid command %s" % cmd)
                log.error("using ctrl-c for thread kill")
                _thread.exit()
                break;
        del message[:]

if __name__ == "__main__":
    while True:
        client_socket, client_address = the_server_socket.accept()
        log.info("Xsane service log.debuger...")
        log.info(client_address)
        _thread.start_new_thread(handle_message, ())
