# Connection.py
# $Id: Connection.py,v 1.18 2001/09/02 17:02:23 s2mdalle Exp $
# Written by David Allen <mda@idatar.com>
#
# Base class for socket connections.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#############################################################################
import socket
import Options
from string import *
import utils
import errno
ConnectionException = "foo"
class Connection:
def __init__(self, *args):
self._d = None
self.bytes_transferred = {'sent' : 0,
'received' : 0 }
return None
def received(self, bytes):
"""Keep track of the number of bytes received on this socket object"""
oldval = self.bytes_transferred['received']
self.bytes_transferred['received'] = oldval + bytes
return self.bytes_transferred['received']
def sent(self, bytes):
"""Keep track of the number of bytes sent on this socket object"""
self.bytes_transferred['sent'] = self.bytes_transferred['sent'] + bytes
return self.bytes_transferred['sent']
def getBytesSent(self):
return self.bytes_transferred['sent']
def getBytesRead(self):
return self.bytes_transferred['received']
def readloop(self, sock, bytesToRead, msgBar=None):
"""Reads bytesToRead data off of sock or until EOF if
bytesToRead < 0. Optionally uses msgBar to log information to the
user."""
timesRead = 0
data = ''
CHUNKSIZE = 1024 # Default read block size.
# This may get overridden depending on how much data
# we have to read. Optimally we want to read all of
# the data that's going to come in 100 steps.
if bytesToRead < 0:
numKBtoRead = "" # Don't report total size since we don't know
else:
# Report the total size so the user has an idea of how long it's
# going to take.
val = float(bytesToRead) / float(1024)
numKBtoRead = "of %0.2f kilobytes total size" % val
if bytesToRead > (1024 * 100): # 100 Kb
CHUNKSIZE = bytesToRead / 100
chunk = 'foobar'
while len(chunk) > 0:
self.checkStopped(msgBar) # Constantly make sure we should
chunk = sock.recv(CHUNKSIZE)
self.received(CHUNKSIZE)
self.checkStopped(msgBar) # continue...
timesRead = timesRead + 1
# print "Read %s: %s" % (CHUNKSIZE, timesRead * CHUNKSIZE)
data = data + chunk
if bytesToRead > 0 and len(data) >= bytesToRead:
# print "BTR=%s, len(data)=%s, breaking" % (bytesToRead,
# len(data))
# Disregard content length for broken gopher+ servers.
# break
pass
if msgBar:
# Report statistics on how far along we are...
bytesRead = timesRead * CHUNKSIZE
kbRead = (float(timesRead) * float(CHUNKSIZE)) / float(1024)
if bytesToRead > 0:
pct = (float(bytesRead) / float(bytesToRead))*float(100)
# This happens sometimes when the server tells us to read
# fewer bytes than there are in the file. In this case,
# we need to only display 100% read even though it's
# actually more. Becase reading 120% of a file doesn't
# make sense.
if pct >= float(100):
pct = float(100)
pctDone = ("%0.2f" % pct) + "%"
else:
pctDone = ""
msgBar.message('state',
"Read %d bytes (%0.2f Kb) %s %s" %
(bytesRead,
kbRead,
numKBtoRead,
pctDone))
# Break standards-compliance because of all those terrible gopher
# servers out there. Return all of the data that we read, not just
# the first bytesToRead characters of it. This will produce the user
# expected behavior, but it disregards content lenghts in gopher+
if bytesToRead > 0:
# return data[0:bytesToRead]
return data
else:
return data
def checkStopped(self, msgBar):
"""Issue a message to the user and jump out if greenlight
isn't true."""
if not Options.program_options.GREEN_LIGHT:
raise ConnectionException, "Connection stopped"
def requestToData(self, resource, request,
msgBar=None, grokLine=None):
"""Sends request to the host/port stored in resource, and returns
any data returned by the server. This may throw
ConnectionException. msgBar is optional.
May throw ConnectionException if green_light ever becomes false.
This is provided so that the user can immediately stop the connection
if it exists."""
utils.msg(msgBar, "Creating socket...")
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if not self.socket:
raise GopherConnectionException, "Cannot create socket."
self.checkStopped(msgBar)
utils.msg(msgBar, "Looking up hostname...")
try:
# Try to get a cached copy of the IP address rather than
# looking it up again which takes longer...
ipaddr = Options.program_options.getIP(resource.getHost())
except KeyError:
# OK, it wasn't in the cache. Figure out what it is,
# and then put it in the cache.
try:
self.checkStopped(msgBar)
ipaddr = socket.gethostbyname(resource.getHost())
except socket.error, err:
host = resource.getHost()
estr = "Cannot lookup\n%s:\n%s" % (host, err)
raise ConnectionException, estr
Options.program_options.setIP(resource.getHost(), ipaddr)
# At this point, ipaddr holds the actual network address of the
# host we're contacting.
utils.msg(msgBar,
"Connecting to %s:%s..." % (ipaddr, resource.getPort()))
try:
retval = self.socket.connect((ipaddr, int(resource.getPort())))
#if retval != 0:
# errortype = errno.errorcode[retval]
# raise socket.error, errortype
except socket.error, err:
newestr = "Cannot connect to\n%s:%s:\n%s" % (resource.getHost(),
resource.getPort(),
err)
raise ConnectionException, newestr
data = ""
self.checkStopped(msgBar)
self.socket.send(request) # Send full request - usually quite short
self.checkStopped(msgBar)
self.sent(len(request)) # We've sent this many bytes so far...
if grokLine: # Read the first line...this is for Gopher+ retrievals
line = "" # and usually tells us how many bytes to read later
byte = ""
while byte != "\n":
self.checkStopped(msgBar)
byte = self.socket.recv(1)
if len(byte) <= 0:
print "****************BROKE out of byte loop"
break
line = line + byte
bytesread = len(line)
line = strip(line)
try:
if line[0] == '+':
bytecount = int(line[1:]) # Skip first char: "+-1" => "-1"
resource.setLen(bytecount)
# Read all of the data into the 'data' variable.
data = self.readloop(self.socket, bytecount, msgBar)
else:
data = self.readloop(self.socket, -1, msgBar)
except:
print "*** Couldn't read bytecount: skipping."
data = self.readloop(self.socket, -1, msgBar)
else:
data = self.readloop(self.socket, -1, msgBar)
utils.msg(msgBar, "Closing socket.")
self.socket.close()
# FIXME: 'data' may be huge. Buffering? Write to cache file here
# and return a cache file name?
return data
syntax highlighted by Code2HTML, v. 0.9.1