import time

from MeteorClient import MeteorClient

client = MeteorClient('ws://127.0.0.1:3000/websocket', debug=True)
client.connect()

client.subscribe('SessionsList')

time.sleep(1)

sessions = client.find('sessions')
print(sessions)

time.sleep(1)
Esempio n. 2
0
class MWorker:
    multiprocessPluginManager = ""  # yapsy manager
    meteorClient = ""  # python-meteor client
    # session=requests.Session()               #http client
    meteorUrl = "ws://127.0.0.1:3000/websocket"
    CYCLE_DEFAULT_DELAY = 20  # 10 seconds
    SYNCHRO_TIMEOUT = 10  # meteor functions are asynchron. this is delay for how long i should wait till
    # it is considered as fail... 2 sec are default
    subscription_status = {}  # this will assure, subscription is ready...    "subscription_name":true/false
    workername = "localhostworker"  # this is worker name - this must be initialised at moment of start, and
    # must by uniqued...

    data = ""
    error = ""
    syncwaitdone = False

    def __init__(self, meteorUrl):
        """
        constructor
        :return:
        """

        self.meteorUrl = meteorUrl
        self.logm("MWorker:constructor", "python meteor client initialisation:")
        self.initMeteorConnect()
        self.logm("MWorker:constructor", "getting unassigned")
        # self.infiniteCycle()
        self.getUnassigned()

    def infiniteCycle(self):
        """
        this is to keep thread running - it can be interupted by ctrl+c


        :return:
        """
        while True:
            try:
                time.sleep(1)
            except KeyboardInterrupt:
                break

    def initMeteorConnect(self):
        """
        this will use library python-meteor in order to escablish session to selected meteor

        :return:
        """
        self.logm("MWorkerLib:initMeteorConnect:", "meteor python lib client iniciaisation...")
        self.meteorClient = MeteorClient('ws://127.0.0.1:3000/websocket', auto_reconnect=True)
        self.meteorClient.connect()
        self.subscribeCollectionSynchron(['unassigned', 'active', 'trash'], self.SYNCHRO_TIMEOUT)

        self.logm("MWorkerLib:initMeteorConnect:", "meteor python lib client iniciaisation done...")

    def meteorCollectionSubscribed(self, subscription):
        self.subscription_status[subscription] = True;
        self.logm("MWorkerLib:subscribed:", 'SUBSCRIBED {}'.format(subscription))

    def meteorConnected(self):
        self.logm("MWorkerLib:connected:", ' CONNECTED')

    def initYapsy(self):
        """
        inicialisation of yapsi subsystems...
        :return:
        """
        self.multiprocessPluginManager = MultiprocessPluginManager()
        self.multiprocessPluginManager.setPluginPlaces(["plugins"])
        self.multiprocessPluginManager.collectPlugins()
        self.logm("MWorkerLib:initYapsy:", "following pluggins ready")
        for pluginInfo in self.multiprocessPluginManager.getAllPlugins():
            self.logm("MWorkerLib:initYapsy:", ">" + pluginInfo.name)

    def isSubscriptionProcessDone(self):
        """
        this will check, if in self.subscription_status   all collections are set to true
        :return: true if all requested collections are subscribed, orthervice false..
        """
        kk = self.subscription_status.keys()

        for col in kk:
            if self.subscription_status[col] == False:
                self.logm("MWorkerLib:isSubscriptionProcessDone:", self.subscription_status)
                return False
        return True

    def subscribeCollectionSynchron(self, collections, timeout):
        """
        this is synchron method. this means, there is loop for "timeout" seconds,
        so subscription can be estalished...
        :param: timeout  - number of seconds for cycle this function will have
        :param: collections   - array of collections to be subscribed with self.meteorClient...
                                ['unassigned', 'active', 'trash']
        :return: will return true, or collection if some is false
        """
        self.logm("MWorkerLib:subscribeCollectionsynchron:", "begining with subscription")
        self.meteorClient.on('subscribed', self.meteorCollectionSubscribed)

        self.meteorClient.on('connected', self.meteorConnected)

        for col in collections:
            self.subscription_status[col] = False
            self.meteorClient.subscribe(col)
        self.logm("MWorkerLib:subscribeCollectionSynchron",
                  "subscription init done, now waiting...:" + str(self.meteorClient.subscriptions.keys()))

        epoch = int(time.time())
        while (((int(time.time()) - epoch) < timeout) and (
                (self.isSubscriptionProcessDone() == False))):
            try:
                time.sleep(1)
                self.logm("MWorkerLib:subscribeCollectionSynchron",
                          "inside waiting cycle :" + str(self.meteorClient.subscriptions.keys()))
            except KeyboardInterrupt:
                break
        if (self.isSubscriptionProcessDone() == False):
            self.logm("MWorkerLib:subscribeCollectionSynchron",
                      "some requested subscription failed...:" + str(self.subscription_status))
            return False
        else:
            self.logm("MWorkerLib:subscribeCollectionSynchron", "requested subscription successfuly subscribed...")
            return True

    def getUnassigned(self):
        """
        get list of unassigned tasks
        :return: list of unassigned tasks
        """

        self.logm("MWorkerLib:getUnassigned", "geting all unassigned")
        all_posts = self.meteorClient.find('unassigned')

        self.logm("MWorkerLib:getUnassigned", "found following unassigned:" + str(all_posts))

    def subscription_callback(self, error):
        if error:
            self.logm("MWorkerLib:subscription_callback", "subsribing to unassigned failed")
            return
        self.logm("MWorkerLib:subscription_callback", "subsribing to unassigned succeed")

    def logm(self, tag="undefined tag", text="undefined text"):
        """
         logger wrapper ....
        :param tag:  tag
        :param level:   level ( 1..10 )
        :param text:   dscription of logging
        :return:
        """
        timestamp = self.getTimestamp()
        print("{d} t:{a}   t:{c}   ".format(a=tag, c=text, d=timestamp))

    ####################################### support methods
    def getTimestamp(self):
        ts = time.time()
        return datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')

    def callViewToolkit(self, method, parameters, callback):
        """
        this will execute call...
        please note, for simple geting collection data, no need to create method...


        :param method:    name of function
        :param parameters: what to give to viewtoolkit
        :callback  -  as all meteor calls are async, callback is required
            def callback_function(error, result):
                if error:
                    print(error)
                    return
                print(result)


        :return:
        """
        self.logm("MWorkerLib:callViewToolkit", "method:" + method + " with parameters:" + parameters + " called")
        self.meteorClient.call(method, parameters, callback)

    def isPluginActiveInPluginManager(self, pluginname):
        """
        this is to check if plugin which is parameter exist in worker, and if yes, if it is activated...
        :param pluginname:
        :return: true in case plugin is activated
        """

        for pluginInfo in self.multiprocessPluginManager.getAllPlugins():
            self.logm("MWorkerLib:isPlugginActiveInPluginManager:",
                      "checking plugin " + pluginInfo.name + "against " + pluginname)
            if (pluginname == pluginInfo.name):
                self.logm("MWorkerLib:isPlugginActiveInPluginManager:",
                          "plugin was found, let see if it is activated " + str(pluginInfo.is_activated))
                if pluginInfo.is_activated:
                    self.logm("MWorkerLib:isPlugginActiveInPluginManager:",
                              "yes, plugin exist and was activated. returning true")
                    return True
        self.logm("MWorkerLib:isPlugginActiveInPluginManager:",
                  "could not found plugin you uare asking for, or it is not activated, returning False")
        return False

    def activatePlugin(self, pluginname):
        """
        plugin activation itself
        :param pluginname:
        :return:
        """
        self.logm("MWorkerLib:isPlugginActiveInPluginManager:activatePlugin",
                  "plugin {a} will be activated now...".format(a=pluginname))
        self.multiprocessPluginManager.activatePluginByName(pluginname)
        self.logm("MWorkerLib:isPlugginActiveInPluginManager:activatePlugin",
                  "plugin {a} was activated...".format(a=pluginname))

    def moveJobFromActiveToTrash(self,jobname):
        """
        will move job by its name  from active to trash
        :param jobname:
        :return:
        """
        #kontrola, zda trash ma v sobe vytvoreneho workera:
        if(self.isWorkerInTrash()==False):
            self.createWorkerRecordInTrash()

        job=self.getJobInActiveByName(jobname)
        if (job==None):
            return False
        if self.createJobInTrashSynchronised(job):
            self.deleteJobFromActiveSynchronised(jobname)

    def deleteJobFromActiveSynchronised(self,jobname):
        """
        delete job from active
        :param jobname:
        :return:
        """
        result = False
        self.syncwaitdone = False  # zacatek locku
        self.logm("MWorkerLib:deleteJobFromActiveSynchronised",
                  "worker:" + self.workername + " is going to remove job from active " + str(jobname))
        self.meteorClient.remove( 'active',{'_id':self.workername,'jobs._id':jobname}, callback=self.callback_synchro)
        res = self.syncWait()  # konec locku
        self.logm("MWorkerLib:deleteJobFromActiveSynchronised",
                  "worker:" + self.workername + " removed " + str(jobname)+ "result is "+str(res))
        return res




    def createJobInTrashSynchronised(self,job):
        """
        this will create job in trash collection under worker name...

        :param job:
        :return:
        """

        #inseritng job
        self.logm("MWorkerLib:createJobInTrash",
                  "worker:" + self.workername + " is going to insert to trash following: " + str(job))
        self.syncwaitdone = False  # zacatek locku
        self.meteorClient.update('trash', {'_id': self.workername}, {'$push': {"jobs":job}},
                                 callback=self.callback_synchro)
        res = self.syncWait()  # konec locku
        return res

    def     getJobInActiveByName(self,jobname):
        """
        this will get job back
        :param jobname:  name of job in active ...
        :return:  return job, or None
        """
        res = self.meteorClient.find_one('active',{'_id':self.workername,'jobs.jobname':jobname})
        if res==[]:
            return None
        return res

    def moveJobFromUnassignedToActiveSynchronised(self, jobname):
        """
        this will shift job from unassigned to active... based on job name, which must be unique in hwhile system
        :param jobname:
        :return:
        """
        #kontrola, zda existuje worker v active, a kdyz ne, tak se vlozi podle template
        if(self.isWorkerInActive()==False):
            self.logm("MWorkerLib:moveJobFromUnassignedToActiveSynchronised",
                  " dont see record for worker {w} in active !! creating it ".format(w=self.workername))
            self.createWorkerRecordInActive()


        resulttmp = self.findJobinUnassigned(jobname)
        if(len(resulttmp)!=1):
            raise NoJobFound(" given job name "+jobname+" doesnt exist")
        job=resulttmp[0]
        self.logm("MWorkerLib:moveJobFromUnassignedToActiveSynchronised",
                  " job  {a} will be activated now...".format(a=str(job)))
        if (self.createMyJobInActiveSynchronised(job)):
            if(self.deleteJobFromUnassignedSynchronised(job)):
                self.logm("MWorkerLib:moveJobFromUnassignedToActiveSynchronised",
                  " job inserting to db is ok, job deleted from unassigned ")
                return True
        self.logm("MWorkerLib:moveJobFromUnassignedToActiveSynchronised",
                  "job inserting to db failed")
        return False


    def isWorkerInTrash(self):
        """
        will check is self.workername can be found in trash
        :return: true if it can be found, false if not
        """
        result = self.meteorClient.find('trash',{"_id":self.workername})
        self.logm("MWorkerLib:isWorkerInTrash",
                  "checking, if i see my worker name in trash "+str(result))
        if len(result)==1:
            self.logm("MWorkerLib:isWorkerInTrash",
                  " worker with my name found in trash, erturning true")
            return True
        else:
            self.logm("MWorkerLib:isWorkerInTrash",
                  "found nothing, returning False")
            return False


    def isWorkerInActive(self):
        """
        will check is self.workername can be found in active
        :return: true if it can be found, false if not
        """
        result = self.meteorClient.find('active',{"_id":self.workername})
        self.logm("MWorkerLib:isWorkerInActive",
                  "checking, if i see my worker name in active "+str(result))
        if len(result)==1:
            self.logm("MWorkerLib:isWorkerInActive",
                  " worker with my name found in active, erturning true")
            return True
        else:
            self.logm("MWorkerLib:isWorkerInActive",
                  "found nothing, returning False")
            return False

    def createWorkerRecordInActive(self):
        """
        will create new worker - it will not be checking if there is already worker created in active...
        :return:
        """
        template={"_id":self.workername,"worker":self.workername,"workerusername":"******","jobs":[]}
        self.logm("MWorkerLib:createWorkerRecordInActive",
                  "worker:" + self.workername + " is going to by added to collection active ")
        self.syncwaitdone = False  # zacatek locku
        self.meteorClient.insert('active',   template,
                                 callback=self.callback_synchro)
        res = self.syncWait()  # konec locku
        return res


    def createWorkerRecordInTrash(self):
        """
        will create new worker - it will not be checking if there is already worker created in active...
        :return:
        """
        template={"_id":self.workername,"worker":self.workername,"workerusername":"******","jobs":[]}
        self.logm("MWorkerLib:createWorkerRecordInTrash",
                  "worker:" + self.workername + " is going to by added to collection trash ")
        self.syncwaitdone = False  # zacatek locku
        self.meteorClient.insert('trash',   template,
                                 callback=self.callback_synchro)
        res = self.syncWait()  # konec locku
        return res


    def findJobinUnassigned(self, jobname):
        """
        will return job from unassigned by jobname
        this is synchron method... it will wait for ( synchron_timeout ) seconds, and return false
        :param job:
        :return:  false on timeout ( synchron_timeout ) or if return is different from 1.
        """
        return self.meteorClient.find('unassigned', {"_id": jobname})

    def createMyJobInActiveSynchronised(self, job):
        """
        this will take as input dict of job... and add it to jobs[] in proper worker...
        :param job:  dict of job...
        :return: false on timeout ( synchron_timeout ) or if return is different from 1.
        """
        template = {
            'pluginname': job['pluginname'],
            'jobname': job['jobname'],
            
            'starttime': int(time.time()),  # epocha
            'ttl': job['expectedttl'],
            'inputparams': job['inputparams'],
            'progress': 0,
            'result': "undefined"
        }

        self.logm("MWorkerLib:createMyJobInActive",
                  "worker:" + self.workername + " is going to insert to active following: " + str(job))
        self.syncwaitdone = False  # zacatek locku
        self.meteorClient.update('active', {'_id': self.workername}, {'$push': {'jobs': template}},
                                 callback=self.callback_synchro)
        res = self.syncWait()  # konec locku
        return res

    def callback_synchro(self, error, data):

        self.data = data
        self.error = error
        if error:
            self.logm("MWorkerLib:update_callback_synchro",
                      "worker:" + self.workername + " failed error is " + str(error))
            self.syncwaitdone = True
            return

        self.logm("MWorkerLib:update_callback_synchro", "is ok. number of updates is " + str(data))
        self.syncwaitdone = True

    def syncWait(self):
        """
        this will wait till data of error are not empty.
        it will do for self.SYNCHRO_TIMEOUT seconds. then it will return False

        also it will check what is return value from request. if update is 0,
        false, othervice true
        """

        epoch = int(time.time())
        while (((int(time.time()) - epoch) < self.SYNCHRO_TIMEOUT) and (self.syncwaitdone == False)):
            try:
                time.sleep(1)
                self.logm("MWorkerLib:syncWait",
                          "inside waiting cycle :")
            except KeyboardInterrupt:
                break

        if (self.syncwaitdone == False):
            # cycle was broken before timeou
            return False

        try:
            # zkouska, zda se updatoval aspon jeden radek
            if (int(self.data) == 0):
                # mongo changed nothing, but there was no error
                return False
            else:
                return True
        except:
            pass

        # nothing found, cykluus end on timeout
        return False

    def deleteJobFromUnassignedSynchronised(self, job):
        """
        this will delete selected job from unassigned
        this is synchron method... it will wait for ( synchron_timeout ) seconds, and return false

        :param job:
        :return: false on timeout ( synchron_timeout ) or if return is different from 1.
        """
        self.syncwaitdone = False  # zacatek locku
        self.logm("MWorkerLib:createMyJobInActive",
                  "worker:" + self.workername + " is going to remove job from unassigned " + str(job))
        self.meteorClient.remove('unassigned', {'_id': job['_id']}, callback=self.callback_synchro)
        res = self.syncWait()  # konec locku
        self.logm("MWorkerLib:createMyJobInActive",
                  "worker:" + self.workername + " removed " + str(job)+ "result is "+str(res))
        return res
    #########################testing


    def testScenario1(self):
        """
        this is scenario 1...
        this will move job from unassigned to active, then from active to trash...
        also it will list unassigned tasks
        :return:



        self.logm("MWorkerLib:testScenario1", "start test scenario 1")
        self.logm("MWorkerLib:testScenario1", "moving from unassigned to active")
        all_posts = self.meteorClient.find('unassigned')
        #job = (all_posts).keys()

        #get first unassigned:
        firstUnassigned=self.meteorClient.find('unassigned')[0]
        ttltmp=100
        #pro testovaci ucely jsem vytvoril v active workera:
        # db.active.insert({"_id":"localhostworker","worker":"localhostworker","workerusername":"******","jobs":[]})

        template={
             'pluginname':firstUnassigned['pluginname'],
             'starttime':int(time.time()),    #epocha
             'ttl':ttltmp,
             'inputparams':firstUnassigned['inputparams'],
             'progress':0,
             'result':"undefined"
        }
        #insert to active:
        #musim pouzit id_ protoze klient rve ze kod je insecure a musi se pouzit id
        #s _id je dalsi problem. protoze interne mongodb pouziva objekt, ne stringu
        #tak bude vyhodnejsi nahradit pri vytvoreni workeru v active _id s nejakym retezcem  _
        worker=self.meteorClient.find_one('active',{"worker" : "localhostworker"})
        #worker["workerusername"]="******"
        self.logm("MWorkerLib:testScenario1", "worker:"+str(worker)+" "+worker['_id'])
        worker1=self.meteorClient.find_one('active',{"_id":worker["_id"]})

        self.logm("MWorkerLib:testScenario1", "worker:"+str(worker1)+" "+worker1['_id'])
        self.meteorClient.update('active',{'_id':worker['_id']},{'$push':{'jobs':template}},callback=update_callback)

        #self.infiniteCycle()
        self.logm("MWorkerLib:testScenario1", "deleting job from unassigned")
        self.meteorClient.remove('unassigned',{'_id':firstUnassigned['_id']},callback=remove_callback)




        time.sleep(5)
        """
        job = {'_id': "asdasdsad", 'jobname': "asdasd", 'pluginname': "asdasda", 'inputparams': "assad",
               'expectedttl': "100"}


        #otestovane a funkcni 11.2.2016:
        #tohle presune job s danym jmenem do active pod jmeno mistniho workera...
        #self.moveJobFromUnassignedToActiveSynchronised("blabla")

        self.moveJobFromActiveToTrash("blabla")
Esempio n. 3
0
class MyProgram:
    def __init__(self):
        slackLog("Gate scanner started.")
        self.parents= {}
        #load parents
        self.client = MeteorClient('ws://chloe.asianhope.org:8080/websocket',debug=False)
        self.client.connect()
        self.client.login(METEOR_USERNAME,METEOR_PASSWORD)
        self.client.subscribe('cards')
        self.client.subscribe('scans')
        time.sleep(3) #give it some time to finish loading everything
        self.all_cards = self.client.find('cards')
        slackLog("Pulled records for: "+str(len(self.all_cards))+" cards")

        for card in self.all_cards:
            try:
                barcode = card['barcode']
                name = card['name']
                cardtype = card['type']
                expires = card['expires']
                profile = card.get('profile',barcode+".JPG") #default picture
                associations = card['associations']
                self.parents.update({barcode:card})

            except KeyError:
                slackLog(barcode+' has missing data',delay=True)

                pass
        # load style css for template
        screen = Gdk.Screen.get_default()
        css_provider = Gtk.CssProvider()
        css_provider.load_from_path('style.css')
        context = Gtk.StyleContext()
        context.add_provider_for_screen(screen, css_provider,
                                        Gtk.STYLE_PROVIDER_PRIORITY_USER)

        # connect to glade teamplate
        self.gladefile = "cardmanager.glade"
        self.glade = Gtk.Builder()
        self.glade.add_from_file(self.gladefile)
        self.glade.connect_signals(self)

        #get window from glade
        self.app_window=self.glade.get_object("main_window") # Window Name in GLADE
        self.app_window.fullscreen()
        # quit app
        self.app_window.connect("delete-event",Gtk.main_quit)

        #change color of window??
        #self.green = gtk.gdk.color_parse('green')
        #self.black = gtk.gdk.color_parse('black')
        #self.app_window.modify_bg(gtk.STATE_NORMAL,self.black)

        # get objects from glade
        self.header = self.glade.get_object("header")
        self.header_context = self.header.get_style_context()
        self.header_title = self.glade.get_object("header_title")
        self.parent_image = self.glade.get_object("img_parent")
        self.child_container = self.glade.get_object("grid1")
        self.button_search = self.glade.get_object("btn_search")
        self.entry = self.glade.get_object("search_input")
        self.pname = self.glade.get_object("lbl_pname")
        self.pbarcode = self.glade.get_object("lbl_pbarcode")
        self.pexpires = self.glade.get_object("lbl_pexpires")
        self.error_message = self.glade.get_object("lbl_error_message")

        #add event to button_search
        self.button_search.connect("clicked", self.search_button_clicked, "3")


        # display children images
        pixbuf = Pixbuf.new_from_file("static/logo.png")
        scaled_buf = pixbuf.scale_simple(CHILD_DIMENSIONS_X,CHILD_DIMENSIONS_Y,InterpType.BILINEAR)

        self.pickup_students = ['0']*9 #seed the list with the size we want
        for i in range(0,9):
            self.pickup_students[i] = Gtk.Image()
            self.pickup_students[i].set_from_pixbuf(scaled_buf)

        self.label=Gtk.Table(3,3,True)
        self.label.attach(self.pickup_students[0],0,1,0,1)
        self.label.attach(self.pickup_students[1],1,2,0,1)
        self.label.attach(self.pickup_students[2],2,3,0,1)

        self.label.attach(self.pickup_students[3],0,1,1,2)
        self.label.attach(self.pickup_students[4],1,2,1,2)
        self.label.attach(self.pickup_students[5],2,3,1,2)
        self.label.attach(self.pickup_students[6],0,1,2,3)
        self.label.attach(self.pickup_students[7],1,2,2,3)
        self.label.attach(self.pickup_students[8],2,3,2,3)
        self.label.set_col_spacings(10)
        self.label.set_row_spacings(10)
        # add lebel of image to container in glade
        self.child_container.add(self.label)

        # display parent picture
        pixbuf = Pixbuf.new_from_file("static/logo.png")
        scaled_buf = pixbuf.scale_simple(PARENT_DIMENSIONS_X,PARENT_DIMENSIONS_Y,InterpType.BILINEAR)
        self.parent_image.set_from_pixbuf(scaled_buf)


        # self.button_search.can_default(True)
        # self.button_search.grab_default()
        self.entry.set_activates_default(True)
        self.app_window.set_focus(self.entry)

        self.error_message.set_text("")
        self.app_window.show_all()
        return

    def search_button_clicked(self, widget, data=None):
        associations = []

        self.error_message.set_text("")
        # remove classes in header
        header_class_list = self.header_context.list_classes()
        for class_name in header_class_list:
            self.header_context.remove_class(class_name)

        for i in range(0,9):
            #make sure all pictures are reset
            pixbuf = Pixbuf.new_from_file("static/logo.png")
            scaled_buf = pixbuf.scale_simple(CHILD_DIMENSIONS_X,CHILD_DIMENSIONS_Y,InterpType.BILINEAR)
            self.pickup_students[i].set_from_pixbuf(scaled_buf)

        #grab pid
        pid = self.entry.get_text()
        slackLog('Scanned card: '+pid,delay=True)

        #do a lookup for the name
        try:
            #get parent information
            #parent_card = self.client.find_one('cards', selector={'barcode': pid})
            parent_card = self.parents[pid]
            slackLog('```'+str(parent_card)+'```',delay=True)

            if not parent_card:
                self.header_title.set_text("Invalid Card!")
                self.header_context.add_class('header_invalid_card')
                self.pname.set_text("Card Not Found!")
                self.pbarcode.set_text("XXXX")
                self.pexpires.set_text("xxxx-xx-xx")
                pixbuf = Pixbuf.new_from_file("static/NA.JPG")
                scaled_buf = pixbuf.scale_simple(PARENT_DIMENSIONS_X,PARENT_DIMENSIONS_Y,InterpType.BILINEAR)
                self.parent_image.set_from_pixbuf(scaled_buf)
            else:
                pname = parent_card.get('name', pid)
                parent_picture = parent_card.get('profile',pid+".JPG")
                expires = parent_card.get('expires',"Expiry not set")
                barcode = parent_card.get('barcode',"Barcode not set")
                associations = parent_card.get('associations',[])

                # if card expired
                if expires < date.today().isoformat():
                    associations = []
                    self.header_title.set_text("Card has expired!")
                    self.header_context.add_class('header_expired')
                else:
                    def getScanCallbackFunction(error, result):
                        # if cannot get scan, display error message
                        if error:
                            self.header_title.set_text('Scan failed!')
                            self.error_message.set_text(error['message'])
                            self.header_context.add_class('header_invalid_card')
                            return
                        else:
                            # if card no scan in, add new scan
                            if result == None:
                                # scan in
                                action = 'Security Scan'
                                value = 0.00
                                products = []
                                user = METEOR_USERNAME
                                self.client.call('scanIn',[pid,action,value,products,user],scanInCallbackFunction)
                            # if card already scan in, update scan
                            else:
                                # scan out
                                scan_id = result['_id']
                                self.client.call('scanOut',[scan_id],scanOutCallbackFunction)

                    def scanInCallbackFunction(error,result):
                        # to check if card scan-in success or error
                        if error:
                            self.header_title.set_text('Scan failed!')
                            self.error_message.set_text(error['message'])
                            self.header_context.add_class('header_invalid_card')
                        else:
                            self.header_title.set_text("Scan-in")
                            self.header_context.add_class('header_scan_in')
                    def scanOutCallbackFunction(error,result):
                        # to check if card scan-out success or error
                        if error:
                            self.header_title.set_text('Scan failed!')
                            self.error_message.set_text(error['message'])
                            self.header_context.add_class('header_invalid_card')
                        else:
                            self.header_title.set_text("Scan-out")
                            self.header_context.add_class('header_scan_out')

                    # get scan to check if scan in or scan out
                    self.client.call('get_scan',[pid],getScanCallbackFunction)

                self.pname.set_text(pname)
                self.pbarcode.set_text(barcode)
                self.pexpires.set_text(expires)
                # load picture
                try:
                    slackLog('loading parent picture: '+str(pid),delay=True)
                    fetchPhotosByID(parent_picture)
                    pixbuf = Pixbuf.new_from_file("resource/"+parent_picture)
                    scaled_buf = pixbuf.scale_simple(PARENT_DIMENSIONS_X,PARENT_DIMENSIONS_Y,InterpType.BILINEAR)
                    self.parent_image.set_from_pixbuf(scaled_buf)
                except Exception as inst:
                    slackLog("No parent picture for: "+pid,delay=True)
                    pixbuf = Pixbuf.new_from_file("static/unknown.jpg")
                    scaled_buf = pixbuf.scale_simple(PARENT_DIMENSIONS_X,PARENT_DIMENSIONS_Y,InterpType.BILINEAR)
                    self.parent_image.set_from_pixbuf(scaled_buf)

        except KeyError:
            slackLog('Scanned card: '+pid+' could not be found',delay=True)
            #display an error
            self.header_title.set_text("Invalid Card!")
            self.header_context.add_class('header_invalid_card')
            self.pname.set_text("Card Not Found!")
            self.pbarcode.set_text("XXXX")
            self.pexpires.set_text("xxxx-xx-xx")

            pixbuf = Pixbuf.new_from_file("static/NA.JPG")
            scaled_buf = pixbuf.scale_simple(PARENT_DIMENSIONS_X,PARENT_DIMENSIONS_Y,InterpType.BILINEAR)
            self.parent_image.set_from_pixbuf(scaled_buf)

            #reset everything
            self.entry.set_text('')
            self.app_window.set_focus(self.entry)
            self.app_window.show()


        #try and load the studnts starting after the parents name
        i = 0
        if(len(associations)):

            pool = ThreadPool(len(associations))
            results = pool.map(fetchPhotosByID,associations)
        for sid in associations:
            #if the student picture exists locally, load it
            try:
                pixbuf = Pixbuf.new_from_file("resource/"+sid+".JPG")
                scaled_buf = pixbuf.scale_simple(CHILD_DIMENSIONS_X,CHILD_DIMENSIONS_Y,InterpType.BILINEAR)
                self.pickup_students[i].set_from_pixbuf(scaled_buf)
            #if not, load the NA picture to indicate a student w/o a picture
            except:
                print("Unexpected error:```")
                print sys.exc_info()[0]
                pixbuf = Pixbuf.new_from_file("static/NA.JPG")
                scaled_buf = pixbuf.scale_simple(CHILD_DIMENSIONS_X,CHILD_DIMENSIONS_Y,InterpType.BILINEAR)
                self.pickup_students[i].set_from_pixbuf(scaled_buf)
            i+=1



        #clear entry box and reset focus
        self.entry.set_text('')
        self.app_window.set_focus(self.entry)
        self.app_window.show()