08ae38b6ce 2010-06-25 1: #!/usr/bin/env python3.1
08ae38b6ce 2010-06-25 2:
e7b837a681 2010-08-25 3: import datetime, http.cookiejar, os, sys, shelve, spacemap, re, urllib.request
e7b837a681 2010-08-25 4:
e7b837a681 2010-08-25 5: class Config:
e7b837a681 2010-08-25 6: __slots__ = frozenset(['_config', '_default', '_section', 'options', 'root'])
e7b837a681 2010-08-25 7: _default = {
e7b837a681 2010-08-25 8: 'general': {
e7b837a681 2010-08-25 9: 'port': '8008',
e7b837a681 2010-08-25 10: },
e7b837a681 2010-08-25 11: '_other': {
e7b837a681 2010-08-25 12: 'verbose': 'no',
e7b837a681 2010-08-25 13: 'noetag': 'no',
e7b837a681 2010-08-25 14: 'noparts': 'no',
cab908195f 2010-09-06 15: 'strip': '',
cab908195f 2010-09-06 16: 'sub': '',
e7b837a681 2010-08-25 17: },}
e7b837a681 2010-08-25 18:
e7b837a681 2010-08-25 19: # function to read in config file
e7b837a681 2010-08-25 20: def __init__(self):
e7b837a681 2010-08-25 21: import configparser, optparse
e7b837a681 2010-08-25 22:
e7b837a681 2010-08-25 23: parser = optparse.OptionParser()
e7b837a681 2010-08-25 24: parser.add_option('-c', '--config', dest = 'config', help = 'config file location', metavar = 'FILE', default = 'samesite.conf')
e7b837a681 2010-08-25 25: (self.options, args) = parser.parse_args()
e7b837a681 2010-08-25 26:
e7b837a681 2010-08-25 27: assert os.access(self.options.config, os.R_OK), "Fatal error: can't read {}".format(self.options.config)
e7b837a681 2010-08-25 28:
e7b837a681 2010-08-25 29: configDir = re.compile('^(.*)/[^/]+$').match(self.options.config)
e7b837a681 2010-08-25 30: if configDir:
e7b837a681 2010-08-25 31: self.root = configDir.group(1)
e7b837a681 2010-08-25 32: else:
e7b837a681 2010-08-25 33: self.root = os.getcwd()
e7b837a681 2010-08-25 34:
e7b837a681 2010-08-25 35: self._config = configparser.ConfigParser()
e7b837a681 2010-08-25 36: self._config.readfp(open(self.options.config))
e7b837a681 2010-08-25 37:
e7b837a681 2010-08-25 38: for section in self._config.sections():
e7b837a681 2010-08-25 39: if section != 'general':
e7b837a681 2010-08-25 40: if self._config.has_option(section, 'dir'):
e7b837a681 2010-08-25 41: if re.compile('^/$').match(self._config.get(section, 'dir')):
e7b837a681 2010-08-25 42: self._config.set(section, 'dir', self.root + os.sep + section)
e7b837a681 2010-08-25 43: thisDir = re.compile('^(.*)/$').match(self._config.get(section, 'dir'))
e7b837a681 2010-08-25 44: if thisDir:
e7b837a681 2010-08-25 45: self._config.set(section, 'dir', thisDir.group(1))
e7b837a681 2010-08-25 46: if not re.compile('^/(.*)$').match(self._config.get(section, 'dir')):
e7b837a681 2010-08-25 47: self._config.set(section, 'dir', self.root + os.sep + self._config.get(section, 'dir'))
e7b837a681 2010-08-25 48: else:
e7b837a681 2010-08-25 49: self._config.set(section, 'dir', self.root + os.sep + section)
e7b837a681 2010-08-25 50:
e7b837a681 2010-08-25 51: if not self._config.has_option(section, 'root'):
e7b837a681 2010-08-25 52: self._config.set(section, 'root', section)
e7b837a681 2010-08-25 53:
e7b837a681 2010-08-25 54: # function to select config file section or create one
e7b837a681 2010-08-25 55: def section(self, section):
e7b837a681 2010-08-25 56: if not self._config.has_section(section):
e7b837a681 2010-08-25 57: self._config.add_section(section)
e7b837a681 2010-08-25 58: self._section = section
e7b837a681 2010-08-25 59:
e7b837a681 2010-08-25 60: # function to get config parameter, if parameter doesn't exists the default
e7b837a681 2010-08-25 61: # value or None is substituted
e7b837a681 2010-08-25 62: def __getitem__(self, name):
e7b837a681 2010-08-25 63: if not self._config.has_option(self._section, name):
e7b837a681 2010-08-25 64: if self._section in self._default:
e7b837a681 2010-08-25 65: if name in self._default[self._section]:
e7b837a681 2010-08-25 66: self._config.set(self._section, name, self._default[self._section][name])
e7b837a681 2010-08-25 67: else:
e7b837a681 2010-08-25 68: self._config.set(self._section, name, None)
e7b837a681 2010-08-25 69: elif name in self._default['_other']:
e7b837a681 2010-08-25 70: self._config.set(self._section, name, self._default['_other'][name])
e7b837a681 2010-08-25 71: else:
e7b837a681 2010-08-25 72: self._config.set(self._section, name, None)
e7b837a681 2010-08-25 73: return(self._config.get(self._section, name))
e7b837a681 2010-08-25 74:
e7b837a681 2010-08-25 75: config = Config()
e7b837a681 2010-08-25 76:
e7b837a681 2010-08-25 77: #assert options.port or os.access(options.log, os.R_OK), 'Log file unreadable'
e7b837a681 2010-08-25 78:
cab908195f 2010-09-06 79: const_desc_fields = set(['Content-Length', 'Last-Modified', 'Pragma'])
cab908195f 2010-09-06 80: const_ignore_fields = set([
cab908195f 2010-09-06 81: 'Accept-Ranges', 'Age',
cab908195f 2010-09-06 82: 'Cache-Control', 'Connection', 'Content-Type',
cab908195f 2010-09-06 83: 'Date',
cab908195f 2010-09-06 84: 'Expires',
439e1753a4 2010-09-07 85: 'Referer',
cab908195f 2010-09-06 86: 'Server',
cab908195f 2010-09-06 87: 'Via',
cab908195f 2010-09-06 88: 'X-Cache', 'X-Cache-Lookup', 'X-Powered-By'
cab908195f 2010-09-06 89: ])
cab908195f 2010-09-06 90:
cab908195f 2010-09-06 91: block_size = 4096
cab908195f 2010-09-06 92:
90160dbf50 2011-03-06 93: import http.server
90160dbf50 2011-03-06 94:
90160dbf50 2011-03-06 95: class MyRequestHandler(http.server.BaseHTTPRequestHandler):
90160dbf50 2011-03-06 96: def __process(self):
90160dbf50 2011-03-06 97: # reload means file needs to be reloaded to serve request
90160dbf50 2011-03-06 98: reload = False
90160dbf50 2011-03-06 99: # recheck means file needs to be checked, this also means that if file hav been modified we can serve older copy
90160dbf50 2011-03-06 100: recheck = False
90160dbf50 2011-03-06 101: # file_stat means file definitely exists
90160dbf50 2011-03-06 102: file_stat = None
90160dbf50 2011-03-06 103: # requested_ranges holds data about any range requested
90160dbf50 2011-03-06 104: requested_ranges = None
90160dbf50 2011-03-06 105: # records holds data from index locally, should be written back upon successfull completion
90160dbf50 2011-03-06 106: record = None
90160dbf50 2011-03-06 107:
90160dbf50 2011-03-06 108: myPath = re.compile('^(.*?)(\?.*)$').match(self.path)
90160dbf50 2011-03-06 109: if myPath:
90160dbf50 2011-03-06 110: my_path = myPath.group(1)
90160dbf50 2011-03-06 111: else:
90160dbf50 2011-03-06 112: my_path = self.path
90160dbf50 2011-03-06 113:
90160dbf50 2011-03-06 114: config.section(self.headers['Host'])
90160dbf50 2011-03-06 115:
90160dbf50 2011-03-06 116: if config['sub'] != None and config['strip'] != None and len(config['strip']) > 0:
90160dbf50 2011-03-06 117: string = re.compile(config['strip']).sub(config['sub'], my_path)
90160dbf50 2011-03-06 118: my_path = string
90160dbf50 2011-03-06 119:
90160dbf50 2011-03-06 120: info = 'Checking file: ' + my_path
90160dbf50 2011-03-06 121:
90160dbf50 2011-03-06 122: if not os.access(config['dir'], os.X_OK):
90160dbf50 2011-03-06 123: os.mkdir(config['dir'])
90160dbf50 2011-03-06 124: # this is file index - everything is stored in this file
90160dbf50 2011-03-06 125: # _parts - list of stored parts of file
90160dbf50 2011-03-06 126: # _time - last time the file was checked
90160dbf50 2011-03-06 127: # everything else is just the headers
90160dbf50 2011-03-06 128: index = shelve.open(config['dir'] + os.sep + '.index')
90160dbf50 2011-03-06 129:
90160dbf50 2011-03-06 130: desc_fields = const_desc_fields.copy()
90160dbf50 2011-03-06 131: ignore_fields = const_ignore_fields.copy()
90160dbf50 2011-03-06 132: if config['noetag'] == 'no':
90160dbf50 2011-03-06 133: desc_fields.add('ETag')
90160dbf50 2011-03-06 134: else:
90160dbf50 2011-03-06 135: ignore_fields.add('ETag')
90160dbf50 2011-03-06 136:
90160dbf50 2011-03-06 137: proxy_ignored = set([
90160dbf50 2011-03-06 138: 'Accept', 'Accept-Charset', 'Accept-Encoding', 'Accept-Language',
90160dbf50 2011-03-06 139: 'Cache-Control', 'Connection', 'Content-Length', 'Cookie',
90160dbf50 2011-03-06 140: 'Host',
90160dbf50 2011-03-06 141: 'If-Modified-Since', 'If-Unmodified-Since',
90160dbf50 2011-03-06 142: 'Referer',
90160dbf50 2011-03-06 143: 'User-Agent',
90160dbf50 2011-03-06 144: 'Via',
90160dbf50 2011-03-06 145: 'X-Forwarded-For', 'X-REMOVED',
90160dbf50 2011-03-06 146: ])
90160dbf50 2011-03-06 147:
90160dbf50 2011-03-06 148: print('===============[ {} request ]==='.format(self.command))
90160dbf50 2011-03-06 149:
90160dbf50 2011-03-06 150: for header in self.headers:
90160dbf50 2011-03-06 151: if header in proxy_ignored:
90160dbf50 2011-03-06 152: pass
90160dbf50 2011-03-06 153: elif header in ('Range'):
90160dbf50 2011-03-06 154: isRange = re.compile('bytes=(\d+)-(\d+)').match(self.headers[header])
90160dbf50 2011-03-06 155: if isRange:
90160dbf50 2011-03-06 156: requested_ranges = spacemap.SpaceMap({int(isRange.group(1)): int(isRange.group(2)) + 1})
90160dbf50 2011-03-06 157: else:
90160dbf50 2011-03-06 158: return()
90160dbf50 2011-03-06 159: elif header in ('Pragma'):
90160dbf50 2011-03-06 160: if my_path in index:
90160dbf50 2011-03-06 161: index[my_path][header] = self.headers[header]
90160dbf50 2011-03-06 162: else:
90160dbf50 2011-03-06 163: print('Unknown header - ', header, ': ', self.headers[header], sep='')
90160dbf50 2011-03-06 164: return()
90160dbf50 2011-03-06 165: print(header, self.headers[header])
90160dbf50 2011-03-06 166:
90160dbf50 2011-03-06 167: # creating file name from my_path
90160dbf50 2011-03-06 168: file_name = config['dir'] + os.sep + re.compile('%20').sub(' ', my_path)
90160dbf50 2011-03-06 169: # partial file or unfinished download
90160dbf50 2011-03-06 170: temp_name = config['dir'] + os.sep + '.parts' + re.compile('%20').sub(' ', my_path)
90160dbf50 2011-03-06 171:
90160dbf50 2011-03-06 172: # creating empty placeholder in index
90160dbf50 2011-03-06 173: # if there's no space map and there's no file in real directory - we have no file
90160dbf50 2011-03-06 174: # if there's an empty space map - file is full
90160dbf50 2011-03-06 175: # space map generally covers every bit of file we don't posess currently
90160dbf50 2011-03-06 176: if not my_path in index:
90160dbf50 2011-03-06 177: info += '\nThis one is new.'
90160dbf50 2011-03-06 178: reload = True
90160dbf50 2011-03-06 179: record = {}
90160dbf50 2011-03-06 180: else:
90160dbf50 2011-03-06 181: # forcibly checking file if no file present
90160dbf50 2011-03-06 182: record = index[my_path]
90160dbf50 2011-03-06 183: if os.access(file_name, os.R_OK):
90160dbf50 2011-03-06 184: info += '\nFull file found.'
90160dbf50 2011-03-06 185: file_stat = os.stat(file_name)
90160dbf50 2011-03-06 186: elif '_parts' in index[my_path] and os.access(temp_name, os.R_OK):
90160dbf50 2011-03-06 187: info += '\nPartial file found.'
90160dbf50 2011-03-06 188: file_stat = os.stat(temp_name)
90160dbf50 2011-03-06 189: recheck = True
90160dbf50 2011-03-06 190: else:
90160dbf50 2011-03-06 191: info += '\nFile not found or inaccessible.'
90160dbf50 2011-03-06 192: record['_parts'] = None
90160dbf50 2011-03-06 193: reload = True
90160dbf50 2011-03-06 194:
90160dbf50 2011-03-06 195: if not '_parts' in record:
90160dbf50 2011-03-06 196: record['_parts'] = None
90160dbf50 2011-03-06 197:
90160dbf50 2011-03-06 198: if record['_parts'] == None:
90160dbf50 2011-03-06 199: recheck = True
90160dbf50 2011-03-06 200:
90160dbf50 2011-03-06 201: # forcibly checking file if file size doesn't match with index data
90160dbf50 2011-03-06 202: if not reload:
90160dbf50 2011-03-06 203: if '_parts' in record and record['_parts'] == spacemap.SpaceMap():
90160dbf50 2011-03-06 204: if 'Content-Length' in record and file_stat and file_stat.st_size != int(record['Content-Length']):
90160dbf50 2011-03-06 205: info += '\nFile size is {} and stored file size is {}.'.format(file_stat.st_size, record['Content-Length'])
90160dbf50 2011-03-06 206: record['_parts'] = None
90160dbf50 2011-03-06 207: reload = True
90160dbf50 2011-03-06 208:
90160dbf50 2011-03-06 209: # forcibly checking file if index holds Pragma header
90160dbf50 2011-03-06 210: if not reload and 'Pragma' in record and record['Pragma'] == 'no-cache':
90160dbf50 2011-03-06 211: info +='\nPragma on: recheck imminent.'
90160dbf50 2011-03-06 212: recheck = True
90160dbf50 2011-03-06 213:
90160dbf50 2011-03-06 214: # skipping file processing if there's no need to recheck it and we have checked it at least 4 hours ago
90160dbf50 2011-03-06 215: if not recheck and not reload and '_time' in record and (datetime.datetime.now() - datetime.timedelta(hours = 4) - record['_time']).days < 0:
90160dbf50 2011-03-06 216: recheck = True
90160dbf50 2011-03-06 217:
90160dbf50 2011-03-06 218: print(info)
90160dbf50 2011-03-06 219: if reload or recheck:
90160dbf50 2011-03-06 220:
90160dbf50 2011-03-06 221: try:
90160dbf50 2011-03-06 222: request = 'http://' + config['root'] + self.path
90160dbf50 2011-03-06 223: my_headers = {}
90160dbf50 2011-03-06 224: for header in ('Cache-Control', 'Cookie', 'Referer', 'User-Agent'):
90160dbf50 2011-03-06 225: if header in self.headers:
90160dbf50 2011-03-06 226: my_headers[header] = self.headers[header]
90160dbf50 2011-03-06 227:
90160dbf50 2011-03-06 228: needed = None
90160dbf50 2011-03-06 229: if '_parts' in record and record['_parts'] != None:
90160dbf50 2011-03-06 230: if config['noparts'] != 'no' or requested_ranges == None or requested_ranges == spacemap.SpaceMap():
90160dbf50 2011-03-06 231: needed = record['_parts']
90160dbf50 2011-03-06 232: else:
90160dbf50 2011-03-06 233: needed = record['_parts'] | requested_ranges
90160dbf50 2011-03-06 234: elif config['noparts'] =='no' and requested_ranges != None and requested_ranges != spacemap.SpaceMap():
90160dbf50 2011-03-06 235: needed = requested_ranges
90160dbf50 2011-03-06 236: ranges = ()
90160dbf50 2011-03-06 237: print('Missing ranges: {}, requested ranges: {}, needed ranges: {}.'.format(record['_parts'], requested_ranges, needed))
90160dbf50 2011-03-06 238: if needed != None and len(needed) > 0:
90160dbf50 2011-03-06 239: needed.rewind()
90160dbf50 2011-03-06 240: while True:
90160dbf50 2011-03-06 241: range = needed.pop()
90160dbf50 2011-03-06 242: if range[0] == None:
90160dbf50 2011-03-06 243: break
90160dbf50 2011-03-06 244: ranges += '{}-{}'.format(range[0], range[1] - 1),
90160dbf50 2011-03-06 245: my_headers['Range'] = 'bytes=' + ','.join(ranges)
90160dbf50 2011-03-06 246:
90160dbf50 2011-03-06 247: request = urllib.request.Request(request, headers = my_headers)
90160dbf50 2011-03-06 248:
90160dbf50 2011-03-06 249: with urllib.request.urlopen(request) as source:
90160dbf50 2011-03-06 250: new_record = {}
90160dbf50 2011-03-06 251: new_record['_parts'] = record['_parts']
90160dbf50 2011-03-06 252: headers = source.info()
90160dbf50 2011-03-06 253:
90160dbf50 2011-03-06 254: # stripping unneeded headers (XXX make this inplace?)
90160dbf50 2011-03-06 255: for header in headers:
90160dbf50 2011-03-06 256: if header in desc_fields:
90160dbf50 2011-03-06 257: #if header == 'Pragma' and headers[header] != 'no-cache':
90160dbf50 2011-03-06 258: if header == 'Content-Length':
90160dbf50 2011-03-06 259: if 'Content-Range' not in headers:
90160dbf50 2011-03-06 260: new_record[header] = int(headers[header])
90160dbf50 2011-03-06 261: else:
90160dbf50 2011-03-06 262: new_record[header] = headers[header]
90160dbf50 2011-03-06 263: elif header == 'Content-Range':
90160dbf50 2011-03-06 264: range = re.compile('^bytes (\d+)-(\d+)/(\d+)$').match(headers[header])
90160dbf50 2011-03-06 265: if range:
90160dbf50 2011-03-06 266: new_record['Content-Length'] = int(range.group(3))
90160dbf50 2011-03-06 267: else:
90160dbf50 2011-03-06 268: assert False, 'Content-Range unrecognized.'
80f8e3804a 2010-08-20 269: elif not header in ignore_fields:
80f8e3804a 2010-08-20 270: print('Undefined header "', header, '": ', headers[header], sep='')
80f8e3804a 2010-08-20 271:
80f8e3804a 2010-08-20 272: # comparing headers with data found in index
80f8e3804a 2010-08-20 273: # if any header has changed (except Pragma) file is fully downloaded
80f8e3804a 2010-08-20 274: # same if we get more or less headers
90160dbf50 2011-03-06 275: old_keys = set(record.keys())
80f8e3804a 2010-08-20 276: old_keys.discard('_time')
80f8e3804a 2010-08-20 277: old_keys.discard('Pragma')
90160dbf50 2011-03-06 278: more_keys = set(new_record.keys()) - old_keys
80f8e3804a 2010-08-20 279: more_keys.discard('Pragma')
90160dbf50 2011-03-06 280: less_keys = old_keys - set(new_record.keys())
80f8e3804a 2010-08-20 281: if len(more_keys) > 0:
80f8e3804a 2010-08-20 282: if not len(old_keys) == 0:
80f8e3804a 2010-08-20 283: print('More headers appear:', more_keys)
80f8e3804a 2010-08-20 284: reload = True
80f8e3804a 2010-08-20 285: elif len(less_keys) > 0:
80f8e3804a 2010-08-20 286: print('Less headers appear:', less_keys)
80f8e3804a 2010-08-20 287: else:
90160dbf50 2011-03-06 288: for key in record.keys():
90160dbf50 2011-03-06 289: if key[0] != '_' and key != 'Pragma' and not record[key] == new_record[key]:
90160dbf50 2011-03-06 290: print('Header "', key, '" changed from [', record[key], '] to [', new_record[key], ']', sep='')
90160dbf50 2011-03-06 291: print(type(record[key]), type(new_record[key]))
80f8e3804a 2010-08-20 292: reload = True
80f8e3804a 2010-08-20 293:
80f8e3804a 2010-08-20 294: if reload:
90160dbf50 2011-03-06 295: print('Reloading.')
90160dbf50 2011-03-06 296: if os.access(temp_name, os.R_OK):
90160dbf50 2011-03-06 297: os.unlink(temp_name)
90160dbf50 2011-03-06 298: if os.access(file_name, os.R_OK):
90160dbf50 2011-03-06 299: os.unlink(file_name)
90160dbf50 2011-03-06 300: new_record['_parts'] = spacemap.SpaceMap({0: int(new_record['Content-Length'])})
90160dbf50 2011-03-06 301: print(new_record)
90160dbf50 2011-03-06 302:
90160dbf50 2011-03-06 303: # downloading file or segment
90160dbf50 2011-03-06 304: if 'Content-Length' in new_record:
90160dbf50 2011-03-06 305: if needed == None:
90160dbf50 2011-03-06 306: needed = new_record['_parts']
90160dbf50 2011-03-06 307: else:
90160dbf50 2011-03-06 308: if len(needed) > 1:
90160dbf50 2011-03-06 309: print("Multipart requests currently not supported.")
90160dbf50 2011-03-06 310: assert False, 'Skip this one for now.'
90160dbf50 2011-03-06 311: else:
90160dbf50 2011-03-06 312: assert False, 'No Content-Length or Content-Range header.'
90160dbf50 2011-03-06 313:
90160dbf50 2011-03-06 314: new_record['_time'] = datetime.datetime.now()
90160dbf50 2011-03-06 315: if self.command not in ('HEAD'):
90160dbf50 2011-03-06 316: # file is created at temporary location and moved in place only when download completes
90160dbf50 2011-03-06 317: if not os.access(temp_name, os.R_OK):
90160dbf50 2011-03-06 318: empty_name = config['dir'] + os.sep + '.tmp'
90160dbf50 2011-03-06 319: with open(empty_name, 'w+b') as some_file:
90160dbf50 2011-03-06 320: pass
90160dbf50 2011-03-06 321: os.renames(empty_name, temp_name)
90160dbf50 2011-03-06 322: temp_file = open(temp_name, 'r+b')
90160dbf50 2011-03-06 323: if requested_ranges == None and needed == None:
90160dbf50 2011-03-06 324: needed = new_record['_parts']
90160dbf50 2011-03-06 325: needed.rewind()
90160dbf50 2011-03-06 326: while True:
90160dbf50 2011-03-06 327: (start, end) = needed.pop()
90160dbf50 2011-03-06 328: if start == None:
90160dbf50 2011-03-06 329: break
90160dbf50 2011-03-06 330: stream_last = start
90160dbf50 2011-03-06 331: old_record = new_record
90160dbf50 2011-03-06 332: if end - start < block_size:
90160dbf50 2011-03-06 333: req_block_size = end - start
90160dbf50 2011-03-06 334: else:
90160dbf50 2011-03-06 335: req_block_size = block_size
90160dbf50 2011-03-06 336: buffer = source.read(req_block_size)
90160dbf50 2011-03-06 337: length = len(buffer)
90160dbf50 2011-03-06 338: while length > 0 and stream_last < end:
90160dbf50 2011-03-06 339: stream_pos = stream_last + length
90160dbf50 2011-03-06 340: assert not stream_pos > end, 'Received more data then requested: pos:{} start:{} end:{}.'.format(stream_pos, start, end)
90160dbf50 2011-03-06 341: temp_file.seek(stream_last)
90160dbf50 2011-03-06 342: temp_file.write(buffer)
90160dbf50 2011-03-06 343: new_record['_parts'] = new_record['_parts'] - spacemap.SpaceMap({stream_last: stream_pos})
90160dbf50 2011-03-06 344: index[my_path] = old_record
90160dbf50 2011-03-06 345: index.sync()
90160dbf50 2011-03-06 346: old_record = new_record
90160dbf50 2011-03-06 347: stream_last = stream_pos
90160dbf50 2011-03-06 348: if end - stream_last < block_size:
90160dbf50 2011-03-06 349: req_block_size = end - stream_last
90160dbf50 2011-03-06 350: buffer = source.read(req_block_size)
90160dbf50 2011-03-06 351: length = len(buffer)
90160dbf50 2011-03-06 352: # moving downloaded data to real file
90160dbf50 2011-03-06 353: temp_file.close()
90160dbf50 2011-03-06 354:
90160dbf50 2011-03-06 355: index[my_path] = new_record
80f8e3804a 2010-08-20 356: index.sync()
80f8e3804a 2010-08-20 357:
80f8e3804a 2010-08-20 358: except urllib.error.HTTPError as error:
80f8e3804a 2010-08-20 359: # in case of error we don't need to do anything actually,
80f8e3804a 2010-08-20 360: # if file download stalls or fails the file would not be moved to it's location
80f8e3804a 2010-08-20 361: print(error)
80f8e3804a 2010-08-20 362:
90160dbf50 2011-03-06 363: print(index[my_path])
90160dbf50 2011-03-06 364:
90160dbf50 2011-03-06 365: if not os.access(file_name, os.R_OK) and os.access(temp_name, os.R_OK) and '_parts' in index[my_path] and index[my_path]['_parts'] == spacemap.SpaceMap():
90160dbf50 2011-03-06 366: # just moving
90160dbf50 2011-03-06 367: # drop old dirs XXX
90160dbf50 2011-03-06 368: print('Moving temporary file to new destination.')
90160dbf50 2011-03-06 369: os.renames(temp_name, file_name)
90160dbf50 2011-03-06 370:
90160dbf50 2011-03-06 371: if not my_path in index:
90160dbf50 2011-03-06 372: self.send_response(502)
90160dbf50 2011-03-06 373: self.end_headers()
90160dbf50 2011-03-06 374: return
90160dbf50 2011-03-06 375:
90160dbf50 2011-03-06 376: if self.command == 'HEAD':
90160dbf50 2011-03-06 377: self.send_response(200)
90160dbf50 2011-03-06 378: if 'Content-Length' in index[my_path]:
90160dbf50 2011-03-06 379: self.send_header('Content-Length', index[my_path]['Content-Length'])
90160dbf50 2011-03-06 380: self.send_header('Accept-Ranges', 'bytes')
90160dbf50 2011-03-06 381: self.send_header('Content-Type', 'application/octet-stream')
90160dbf50 2011-03-06 382: if 'Last-Modified' in index[my_path]:
90160dbf50 2011-03-06 383: self.send_header('Last-Modified', index[my_path]['Last-Modified'])
90160dbf50 2011-03-06 384: self.end_headers()
90160dbf50 2011-03-06 385: else:
90160dbf50 2011-03-06 386: if ('_parts' in index[my_path] and index[my_path]['_parts'] != spacemap.SpaceMap()) or not os.access(file_name, os.R_OK):
90160dbf50 2011-03-06 387: file_name = temp_name
90160dbf50 2011-03-06 388:
90160dbf50 2011-03-06 389: with open(file_name, 'rb') as real_file:
90160dbf50 2011-03-06 390: file_stat = os.stat(file_name)
90160dbf50 2011-03-06 391: if 'Range' in self.headers:
90160dbf50 2011-03-06 392: self.send_response(206)
90160dbf50 2011-03-06 393: ranges = ()
90160dbf50 2011-03-06 394: requested_ranges.rewind()
90160dbf50 2011-03-06 395: while True:
90160dbf50 2011-03-06 396: pair = requested_ranges.pop()
90160dbf50 2011-03-06 397: if pair[0] == None:
90160dbf50 2011-03-06 398: break
90160dbf50 2011-03-06 399: ranges += '{}-{}'.format(pair[0], str(pair[1] - 1)),
90160dbf50 2011-03-06 400: self.send_header('Content-Range', 'bytes {}/{}'.format(','.join(ranges), index[my_path]['Content-Length']))
90160dbf50 2011-03-06 401: else:
90160dbf50 2011-03-06 402: self.send_response(200)
90160dbf50 2011-03-06 403: self.send_header('Content-Length', str(file_stat.st_size))
90160dbf50 2011-03-06 404: requested_ranges = spacemap.SpaceMap({0: file_stat.st_size})
90160dbf50 2011-03-06 405: if 'Last-Modified' in index[my_path]:
90160dbf50 2011-03-06 406: self.send_header('Last-Modified', index[my_path]['Last-Modified'])
90160dbf50 2011-03-06 407: self.send_header('Content-Type', 'application/octet-stream')
90160dbf50 2011-03-06 408: self.end_headers()
90160dbf50 2011-03-06 409: if self.command in ('GET'):
90160dbf50 2011-03-06 410: if len(requested_ranges) > 0:
90160dbf50 2011-03-06 411: requested_ranges.rewind()
90160dbf50 2011-03-06 412: (start, end) = requested_ranges.pop()
90160dbf50 2011-03-06 413: else:
90160dbf50 2011-03-06 414: start = 0
90160dbf50 2011-03-06 415: end = index[my_path]['Content-Length']
90160dbf50 2011-03-06 416: real_file.seek(start)
90160dbf50 2011-03-06 417: if block_size > end - start:
90160dbf50 2011-03-06 418: req_block_size = end - start
90160dbf50 2011-03-06 419: else:
90160dbf50 2011-03-06 420: req_block_size = block_size
90160dbf50 2011-03-06 421: buffer = real_file.read(req_block_size)
90160dbf50 2011-03-06 422: length = len(buffer)
90160dbf50 2011-03-06 423: while length > 0:
90160dbf50 2011-03-06 424: self.wfile.write(buffer)
90160dbf50 2011-03-06 425: start += len(buffer)
90160dbf50 2011-03-06 426: if req_block_size > end - start:
90160dbf50 2011-03-06 427: req_block_size = end - start
90160dbf50 2011-03-06 428: if req_block_size == 0:
90160dbf50 2011-03-06 429: break
90160dbf50 2011-03-06 430: buffer = real_file.read(req_block_size)
90160dbf50 2011-03-06 431: length = len(buffer)
90160dbf50 2011-03-06 432:
90160dbf50 2011-03-06 433: def do_HEAD(self):
90160dbf50 2011-03-06 434: return self.__process()
90160dbf50 2011-03-06 435: def do_GET(self):
90160dbf50 2011-03-06 436: return self.__process()
80f8e3804a 2010-08-20 437:
90160dbf50 2011-03-06 438: config.section('general')
90160dbf50 2011-03-06 439: server = http.server.HTTPServer(('127.0.0.1', int(config['port'])), MyRequestHandler)
90160dbf50 2011-03-06 440: server.serve_forever()