Source code for ez_client

#==============================================================================#
#                                  ez_client                                   #
#==============================================================================#
# TODO: (bcn 2014-08-08) when this file only contains the client class, it
# should be renamed to ez_client.py ?!

#============#
#  Includes  #
#============#
#from __future__ import print_function

import sys, errno
import socket, select
import Queue, threading
import cPickle as pickle
import ez_message  as em

from datetime import datetime
from ez_process      import p2pCommand, p2pReply, ez_process
from ez_simple_cli   import ez_simple_cli
#from ez_user_methods import ez_user_methods
#from ez_process      import ez_process_base

CLIENT_TIMEOUT = 0.1

#==============================================================================#
#                                 class client                                 #
#==============================================================================#

[docs]class client(ez_process, ez_simple_cli, threading.Thread): """ Client class with builtin queue system, p2p via NAT traversal and reliable udp packet system. Commands are executed by appending p2pCommand instances to the client commandQueue. Most commands are not intended to be called by the user himself, but are usually automatically called as a consequence of connection, ping or packet requests. Queue commands are defined in ez_process. The client takes care of input (IO + incomming msges), the user db and pulling commands/results from the queues. """ def __init__(self, fail_connect=False, **kwargs): threading.Thread.__init__(self) super(client, self).__init__(**kwargs) # used to simulate udp-holepunching where one of the clients connection # request is declient by the others client NAT self.fail_connect = fail_connect # As long as the client is alive queue is checked for commands and replies self.alive = threading.Event() self.alive.set() # internal cli enabled self.enableCLI = False try: self.sockfd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) except socket.error, msg: error_msg = 'Bind failed. Error Code: ' + str(msg[0]) + ' Message ' + \ msg[1] self.replyQueue.put(self.error(error_msg)) self.command_history = {} if not 'acception_rules' in kwargs: acception_rules = {} acception_rules['global_rule'] = 'Allow' self.set_acception_rules(**acception_rules) else: acception_rules = kwargs['acception_rules'] self.set_acception_rules(**acception_rules) self.timeout = CLIENT_TIMEOUT
[docs] def set_acception_rules(self, **acception_rules): if 'global_rule' in acception_rules: global_rule = acception_rules['global_rule'] del acception_rules['global_rule'] try: assert(global_rule in ['Allow', 'Deny', 'Auth']) except: print 'global rule error' return else: global_rule = 'Deny' for handler in self.handlers: if handler in acception_rules: self.handler_rules[handler] = acception_rules[handler] else: self.handler_rules[handler] = global_rule #===================# # client receive # #===================#
[docs] def receive(self, cmd): """ The receive function supports 3 types of data: - Dictionaries with p2pCommand keys and appropriate arguments as values - Message instances - Raw printable data UDP packets must be pickled otherwise the data is rejected """ try: readable, _, _ = select.select([self.sockfd], [], [], 0) except: readable = None if not readable: return sdata, user_addr = self.sockfd.recvfrom(4048) if sdata != None: try: data = pickle.loads(sdata) except: print "sdata:", sdata self.replyQueue.put(self.error("data not pickled -> rejected")) return # TODO: nick new interface here Do 07 Aug 2014 12:59:17 CEST # it shouldn't be possible that every user can start commands on other # users clients. We need some security measures here and we may connect # them with options like how often one can be pinged or being requested to # send packages etc. if isinstance(data, dict): for command in data: try: assert(command in self.handler_rules) if self.handler_rules[command] == 'Allow': execute = True elif self.handler_rules[command] == 'Auth': master = (user_addr[0], int(user_addr[1])) if master in self.authentifications: execute = True else: execute = False else: execute = False if execute: cmd_dct = data[command] cmd_dct.update({'host': user_addr[0], 'port': user_addr[1]}) user_cmd = p2pCommand(command, cmd_dct) self.commandQueue.put(user_cmd) except: self.replyQueue.put(self.error('No acception rule set for command '+ command + '.')) elif isinstance(data, em.Message): self.replyQueue.put(self.success("received msg")) self.MsgDatabase.add_entry(data) if self.enableCLI: print "data.clear_text():", data.clear_text() self.replyQueue.put(self.msg(data)) else: # raw data self.replyQueue.put(self.success(data)) return data else: self.replyQueue.put(self.error("Conflict in receive")) #====================# # client main loop # #====================#
[docs] def run(self): """ client main loop: Processes all queued commands. The timeout (0.1) is set in order to allow checking self.alive """ while self.alive.isSet(): try: cmd = self.commandQueue.get(True, self.timeout) msg = self.handlers[cmd.funcStr](self, cmd) except Queue.Empty: pass readable = [] try: if self.enableCLI: readable, _, _ = select.select([0, self.sockfd], [], [], 0) else: readable, _, _ = select.select([self.sockfd], [], [], 0) except: pass for i in readable: if i == 0: self.CLI() # socket activated -> there is incoming data elif i == self.sockfd: self.commandQueue.put(p2pCommand('receive')) # check for messages in the replyQueue if self.enableCLI: try: reply = self.replyQueue.get(block=False) status = "success" if reply.replyType == p2pReply.success else "ERROR" print ('Client reply %s: %s' % (status, reply.data)) except Queue.Empty: pass
[docs]def init_client(name, **kwargs): global cl cl = client(name=name, write_to_pipe=True, **kwargs) cl.start()
if __name__ == "__main__": try: name = sys.argv[1] except (IndexError, ValueError): print ("usage: %s <id>" % sys.argv[0]) sys.exit(65) init_client()