1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
|
# 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
|