whisker.py

Top  Previous  Next

# whisker.py

# 7 Feb 2010

 

# -----------------------------------------------------------

# --------------------------- Networking routines.

# -----------------------------------------------------------

 

import re

def connect_both_ports(server, mainport):

   """Connect the main and immediate ports to the server."""

   connect_main(server, mainport) # Log in to the server.

   # Listen to the server until we can connect the immediate socket.

   for line in getlines_mainsock():

       # The server has sent us a message via the main socket.

       if verbosenetwork:

           print "SERVER: " + line # Print the message, for info only.

       m = re.search(r"^ImmPort: (\d+)", line)

       if m:

           immport = m.group(1)

       m = re.search(r"^Code: (\w+)", line)

       if m:

           code = m.group(1)

           connect_immediate(server, immport, code)

           break

 

import socket

def connect_main(server, portstring):

   """Connect the main port to the server."""

   print "Connecting main port to server."

   m = re.match(r"\D", portstring) # search for \D = non-digit characters

   if m:

       port = socket.getservbyname(portstring, "tcp")

   else:

       port = int(portstring)

   proto = socket.getprotobyname("tcp")

   global mainsock

   try:

       mainsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, proto)

       mainsock.connect( (server, port) )

   except socket.error, msg:

       mainsock.close()

       mainsock = None

       print "ERROR creating/connecting main socket: "+msg

   print "Connected to main port " + str(port) + " on server " + server

   mainsock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) # Disable the Nagle algorithm.

 

import time

def connect_immediate(server, portstring, code):

   m = re.match(r"\D", portstring) # search for \D = non-digit characters

   if m:

       port = socket.getservbyname(portstring, "tcp")

   else:

       port = int(portstring)

   proto = socket.getprotobyname("tcp")

   global immsock

   try:

       immsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, proto)

       immsock.connect( (server, port) )

   except socket.error, msg:

       immsock.close()

       immsock = None

       print "ERROR creating/connecting immediate socket: "+msg

   print "Connected to immediate port " + str(port) + " on server " + server

   immsock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) # Disable the Nagle algorithm.

   immsock.setblocking(True)

   send_immediate("Link "+code)

   sleeptime = 0.1

   print "Sleeping for "+str(sleeptime)+" seconds as the Nagle-disabling feature of Python isn't working properly..."

   time.sleep(sleeptime) # the Nagle business isn't working; the Link packet is getting amalgamated with anything the main calling program starts to send. So pause.

   print "... continuing. Immediate socket should now be correctly linked."

 

def log_out():

   try:

       mainsock.close()

   except socket.error, msg:

       print "Error closing main socket: "+msg

   try:

       immsock.close()

   except socket.error, msg:

       print "Error closing immediate socket: "+msg

 

def send(s):

   # Send something to the server on the main socket, with a trailing newline.

   if verbosenetwork:

       print "Main socket command: " + s # For info only.

   mainsock.send(s + "\n")

 

def send_immediate(s):

   # Send a command to the server on the immediate socket, and retrieve its reply.

   if verbosenetwork:

       print "Immediate socket command: " + s # For info only.

   immsock.sendall(s + "\n")

   reply = getlines_immsock().next()

   if verbosenetwork:

       print "Immediate socket reply:", reply # For info only.

   return reply

 

# http://stackoverflow.com/questions/822001/python-sockets-buffering

def getlines_immsock():

   # Yield a set of lines from the socket

   buffer = immsock.recv(4096)

   done = False

   while not done:

       if "\n" in buffer:

           (line, buffer) = buffer.split("\n", 1)

           yield line

       else:

           more = immsock.recv(4096)

           if not more:

               done = True

           else:

               buffer = buffer+more

   if buffer:

       yield buffer

 

def getlines_mainsock():

   # Yield a set of lines from the socket

   buffer = mainsock.recv(4096)

   done = False

   while not done:

       if "\n" in buffer:

           (line, buffer) = buffer.split("\n", 1)

           yield line

       else:

           more = mainsock.recv(4096)

           if not more:

               done = True

           else:

               buffer = buffer+more

   if buffer:

       yield buffer

 

# -----------------------------------------------------------

# --------------------------- User interface routines.

# -----------------------------------------------------------

 

def ask_user(prompt, default):

   """Prompts the user, with a default. Returns a string."""

   result = raw_input(prompt + " [" + default + "]: ")

   return result if len(result)>0 else default

 

def ask_user_password(prompt):

   """Read a password from the console."""

   import getpass

   return getpass.getpass(prompt + ": ")

 

# -----------------------------------------------------------

# --------------------------- Database routines, using ODBC.

# -----------------------------------------------------------

 

# Open database connection.

# There's no direct Python equivalent of DBI (which can talk to e.g. ODBC and MySQL).

# So we'll use ODBC. On Windows, that comes by default.

# For IODBC: sudo apt-get install iodbc libiodbc2-dev. Then see http://www.iodbc.org/dataspace/iodbc/wiki/iODBC/IODBCPythonHOWTO . This uses ~/.odbc.ini .

# Or, for MySQL, sudo apt-get install iodbc libmyodbc. Then see https://help.ubuntu.com/community/ODBC . This uses /etc/odbc.ini .

# Or UnixODBC: sudo apt-get install unixodbc unixodbc-dev. Then see http://ubuntu-virginia.ubuntuforums.org/showthread.php?p=5846508 . WE'RE GOING THIS WAY.

# The Python interface to ODBC is pyodbc: http://code.google.com/p/pyodbc/wiki/GettingStarted

# To install pyodbc (see in part http://www.easysoft.com/developer/languages/python/pyodbc.html):

#   * sudo apt-get install python-all-dev (to get development headers)

#   * download e.g. pyodbc-2.1.6.zip from http://code.google.com/p/pyodbc/downloads/list

#   * unzip pyodbc-2.1.6.zip

#   * cd pyodbc-2.1.6

#   * amend setup.py: FOR IODBC: change "libraries.append('odbc')" to "libraries.append('iodbc')"...

#   * amend setup.py: FOR LIBMYODBC: not yet worked out

#   * amend setup.py: FOR UNIXODBC: works as is

#   * sudo python setup.py install

# Now, for unixodbc, set it up:

#   * edit /etc/odbcinst.ini to be e.g.:

#       [myodbc]

#       Description = MySQL ODBC 3.51 Driver (this can be an arbitrary name)

#       Driver = /usr/lib/odbc/libmyodbc.so

#       Setup = /usr/lib/odbc/libodbcmyS.so

#       FileUsage       = 1

#   * edit /etc/odbc.ini to be e.g.

#       [mysql-testdb]

#       Driver       = myodbc

#       Description  = mysql_egret_testdb NEEDS SSH TUNNEL

#       SERVER       = 127.0.0.1 # do not use "localhost" or the driver will look in /var/run/mysqld/mysqld.sock, instead of looking at PORT

#       PORT         = 3306

#       Database     = testdb

#       OPTION       = 3

# Now test:

#   * isql mysql-testdb USER PASSWORD

#   * python tests/dbapitests.py python tests/dbapitests.py "DSN=mysql-testdb;UID=xxx;PWD=xxx"

 

import pyodbc

def connect_to_database():

   print "--- Enter database details."

   dsn = ask_user("ODBC data source name (DSN)", "mysql-testdb")

   username = ask_user("Database username", "root")

   password = ask_user_password("Database password")

   print "Connecting to database."

   dbh = pyodbc.connect("DSN={0};UID={1};PWD={2}".format(dsn, username, password))

   # XXX print error message if it fails

   # XXX print success message it it succeeds

   # ... at present, it crashes with the error message.

   return dbh

 

def sql_insert_record(dbh, table, fields, values):

   if len(fields) != len(values):

       print "Field/value mismatch to sql_insert_record()"

       return

   # SQL e.g. INSERT INTO table (field1, field2, field3) VALUES (value1, value2, value3);

   # but we start with INSERT INTO table (field1, field2, field3) VALUES (?, ?, ?);

   sql = "INSERT INTO " + table + " ("

   for i in range(len(fields)):

       if i>0:

           sql += ", "

       sql += fields[i]

   sql += ") VALUES ("

   for i in range(len(values)):

       if i>0:

           sql += ", "

       sql += "?"

   sql += ");"

   if verbosedatabase:

       print "SQL so far (? will be bound to values later): " + sql

 

   cursor = dbh.cursor()

   cursor.execute(sql, values) # binds the ? to values in the process

   dbh.commit()

 

def sql_insert_multiple_records(dbh, table, fields, records):

   for record in records:

       sql_insert_record(dbh, table, fields, record)

 

# -----------------------------------------------------------

# --------------------------- Textfile results storage.

# -----------------------------------------------------------

 

# Trivial, but...

def produce_csv_output(filehandle, fields, values):

   """Produce CSV output, without using csv.writer, so the log can be used for lots of things."""

   output_csv(filehandle, fields)

   for row in values:

       output_csv(filehandle, row)

 

def output_csv(filehandle, values):

   line = ""

   for i in range(len(values)):

       if i>0:

           line += ","

       line += values[i]

   filehandle.write(line+"\n")