#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # Copyright (C) 2016 Torsten Dreyer # # 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # terrasync.py - synchronize terrascenery data to your local disk # needs dnspython (pip install dnspython) # import os import hashlib import urllib.request from os import listdir from os.path import isfile, join dirindex = ".dirindex" DIRINDEXVERSION = 1 URL="http://flightgear.sourceforge.net/scenery" # User master repository for now #URL="automatic" TARGET="." QUICK=False REMOVE_ORPHAN=False ######################################################################## def fn_hash_of_file(fname): if not os.path.exists( fname ): return None hash = hashlib.sha1() try: with open(fname, "rb") as f: for chunk in iter(lambda: f.read(4096), b""): hash.update(chunk) except: pass return hash.hexdigest() ######################################################################## def do_download_file( _url, _path, _localfile, _hash, _force ): if os.path.exists( _localfile ) and not _force: h = fn_hash_of_file(_localfile) if h == _hash: #print("hash match for ", _localfile) return False r = urllib.request.urlopen( _url + _path ) with open(_localfile, 'wb') as f: f.write( r.read() ) #print("downloaded ", _localfile, " from ", _url + _path ) return True ######################################################################## def do_terrasync( _url, _path, _localdir, _dirIndexHash ): url = _url + _path print(url) if not os.path.exists( _localdir ): os.makedirs( _localdir ) # download and process .dirindex as temporary file # rename to .dirindex after successful processing of directory # in case of abort, .dirindex.tmp will be removed as orphan myDirIndexFile = os.path.join(_localdir, ".dirindex.tmp") try: if not do_download_file( url, "/.dirindex", myDirIndexFile, _dirIndexHash, QUICK == False ): # dirindex hash matches, file not downloaded, skip directory return except urllib.error.HTTPError as err: if err.code == 404 and _path == "": # HACK: only the master on SF provides .dirindex for root, fake it if it's missing print("Using static root hack.") for _sub in ("Models", "Terrain", "Objects", "Airports" ): do_terrasync( _url, "/" + _sub, os.path.join(_localdir,_sub), None ) return else: raise with open(myDirIndexFile, 'r') as myDirIndex: serverFiles = [] for line in myDirIndex: tokens = line.rstrip().split(':') if( len(tokens) == 0 ): continue # TODO: check version number, should be equal to DIRINDEXVERSION # otherwise complain and terminate if( tokens[0] == "version" ): continue if( tokens[0] == "path" ): continue if( tokens[0] == "d" ): do_terrasync( url, "/" + tokens[1], os.path.join(_localdir,tokens[1]), tokens[2] ) if( tokens[0] == "f" ): do_download_file( url, "/" + tokens[1], os.path.join(_localdir,tokens[1]), tokens[2], False ) serverFiles.append( tokens[1] ) os.rename( myDirIndexFile, os.path.join(_localdir, ".dirindex" ) ) localFiles = [f for f in listdir(_localdir) if isfile(join(_localdir, f))] for f in localFiles: if f != ".dirindex" and not f in serverFiles: if REMOVE_ORPHAN: os.remove( os.path.join(_localdir,f) ) #TODO: cleanup orphan files ######################################################################## import getopt, sys, random, re try: opts, args = getopt.getopt(sys.argv[1:], "u:t:qr", [ "url=", "target=", "quick", "remove-orphan" ]) except getopt.GetoptError: print("terrasync.py [--url=http://some.server.org/scenery] [--target=/some/path] [-q|--quick] [-r|--remove-orphan]") sys.exit(2) for opt, arg in opts: if opt in( "-u", "--url"): URL = arg elif opt in ( "-t", "--target"): TARGET= arg elif opt in ("-q", "--quick"): QUICK = True elif opt in ("-r", "--remove-orphan"): REMOVE_ORPHAN = True # automatic URL lookup from DNS NAPTR # - lookup terrasync.flightgear.org, type=NAPTR, service="ws20", flags="U" # - sort by order,preference ascending # - pick entries with lowest order and preference # - randomly pick one of those # - use regexp fields URL if URL == "automatic": import dns.resolver dnsResolver = dns.resolver.Resolver() order = -1 preference = -1 # find lowes preference/order for service 'ws20' and flags 'U' dnsAnswer = dnsResolver.query("terrasync.flightgear.org", "NAPTR" ) for naptr in dnsAnswer: if naptr.service != b'ws20' or naptr.flags != b'U': continue if order == -1 or naptr.order < order: order = naptr.order preference = naptr.preference if order == naptr.order: if naptr.preference < preference: preference = naptr.preference # grab candidats candidates = [] for naptr in dnsAnswer: if naptr.service != b'ws20' or naptr.flags != b'U' or naptr.preference != preference or naptr.order != order: continue candidates.append( naptr.regexp.decode('utf-8') ) if not candidates: print("sorry, no terrascenery URLs found. You may specify one with --url=http://some.url.org/foo") sys.exit(3) _url = random.choice(candidates) _subst = _url.split(_url[0]) # split string, first character is separator regexreplacement URL = re.sub(_subst[1], _subst[2], "" ) # apply regex substitude on empty string print( "terrasyncing from ", URL, "to ", TARGET ) do_terrasync( URL, "", TARGET, None ) ########################################################################