Exemplo n.º 1
0
def get_json_data(url):
    """
    Fetch JSON data from a URL and return the resulting dictionary.

    Displays a progress bar as it downloads.
    """

    requests = import_requests()
    req = requests.get(url, stream=True)

    totalLength = req.headers.get('content-length')
    if totalLength is None:
        compression = req.headers.get('content-encoding')
        compression = (compression + "'ed") if compression else "uncompressed"
        print("Downloading {}: {}...".format(compression, url))
        jsData = req.content
    else:
        totalLength = int(totalLength)
        progBar = pbar.Progress(totalLength, 25)
        jsData = bytes()
        for data in req.iter_content():
            jsData += data
            progBar.increment(
                len(data),
                postfix=lambda value, goal: \
                " {}/{}".format(
                    makeUnit(value),
                    makeUnit(goal),
            ))
        progBar.clear()

    return json.loads(jsData.decode())
Exemplo n.º 2
0
    def importListings(self, listings_file):
        """
        Updates the market data (AKA the StationItem table) using listings.csv
        Writes directly to database.
        """
        tdb, tdenv = self.tdb, self.tdenv
        
        tdenv.NOTE("Processing market data from {}: Start time = {}", listings_file, datetime.datetime.now())
        if not (self.dataPath / listings_file).exists():
            tdenv.NOTE("File not found, aborting: {}", (self.dataPath / listings_file))
            return
        
        progress = 0
        total = 1
        if listings_file == LISTINGS:
            from_live = 0
        else:
            from_live = 1
        
        def blocks(f, size = 65536):
            while True:
                b = f.read(size)
                if not b: break
                yield b
        
        with open(str(self.dataPath / listings_file), "r",encoding = "utf-8",errors = 'ignore') as f:
            total += (sum(bl.count("\n") for bl in blocks(f)))

        with open(str(self.dataPath / listings_file), "rU") as fh:
            if self.getOption("progbar"):
                prog = pbar.Progress(total, 50)
            listings = csv.DictReader(fh)
            
            cur_station = -1
            station_items = dict()
            
            for listing in listings:
                if self.getOption("progbar"):
                    prog.increment(1, postfix=lambda value, goal: " " + str(round(value / total * 100)) + "%")
                else:
                    progress += 1
                    print("\rProgress: (" + str(progress) + "/" + str(total) + ") " + str(round(progress / total * 100, 2)) + "%\t\t", end = "\r")        
                station_id = int(listing['station_id'])
                item_id = int(listing['commodity_id'])
                modified = datetime.datetime.utcfromtimestamp(int(listing['collected_at'])).strftime('%Y-%m-%d %H:%M:%S')
                demand_price = int(listing['sell_price'])
                demand_units = int(listing['demand'])
                demand_level = int(listing['demand_bracket']) if listing['demand_bracket'] != '' else -1
                supply_price = int(listing['buy_price'])
                supply_units = int(listing['supply'])
                supply_level = int(listing['supply_bracket']) if listing['supply_bracket'] != '' else -1
                
                if station_id != cur_station:
                    for item in station_items:
                        if not item:
                            self.execute("DELETE from StationItem WHERE station_id = ? and item_id = ?", (station_id, item))
                    del station_items, cur_station
                    cur_station = station_id
                    station_items = dict()
                    cursor = self.execute("SELECT item_id from StationItem WHERE station_id = ?", (station_id,))
                    for item in cursor:
                        station_items[item] = False
                    del cursor
                
                station_items[item_id] = True
                
                result = self.execute("SELECT modified FROM StationItem WHERE station_id = ? AND item_id = ?", (station_id, item_id)).fetchone()
                if result:
                    updated = timegm(datetime.datetime.strptime(result[0],'%Y-%m-%d %H:%M:%S').timetuple())
                    # When the dump file data matches the database, update to make from_live == 0.
                    if int(listing['collected_at']) == updated and listings_file == LISTINGS:
                        self.execute("""UPDATE StationItem
                                    SET from_live = 0
                                    WHERE station_id = ? AND item_id = ?""",
                                    (station_id, item_id))
                    if int(listing['collected_at']) > updated:
                        tdenv.DEBUG1("Updating:{}, {}, {}, {}, {}, {}, {}, {}, {}",
                             station_id, item_id, modified,
                             demand_price, demand_units, demand_level,
                             supply_price, supply_units, supply_level)
                        try:
                            self.execute("""UPDATE StationItem
                                    SET modified = ?,
                                     demand_price = ?, demand_units = ?, demand_level = ?,
                                     supply_price = ?, supply_units = ?, supply_level = ?,
                                     from_live = ?
                                    WHERE station_id = ? AND item_id = ?""",
                                    (modified, demand_price, demand_units, demand_level, supply_price, supply_units, supply_level, from_live,
                                     station_id, item_id))
                        except sqlite3.IntegrityError:
                            tdenv.DEBUG1("Error on update.")
                else:
                    tdenv.DEBUG1("Inserting:{}, {}, {}, {}, {}, {}, {}, {}, {}",
                             station_id, item_id, modified,
                             demand_price, demand_units, demand_level,
                             supply_price, supply_units, supply_level)
                    try:
                        self.execute("""INSERT INTO StationItem
                                (station_id, item_id, modified,
                                 demand_price, demand_units, demand_level,
                                 supply_price, supply_units, supply_level, from_live)
                                VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )""",
                                (station_id, item_id, modified,
                                 demand_price, demand_units, demand_level,
                                 supply_price, supply_units, supply_level, from_live))
                    except sqlite3.IntegrityError:
                        tdenv.DEBUG1("Error on insert.")
            if self.getOption("progbar"):
                while prog.value < prog.maxValue:
                    prog.increment(1, postfix=lambda value, goal: " " + str(round(value / total * 100)) + "%")
                prog.clear()
        
        del from_live
        self.updated['Listings'] = True
        tdenv.NOTE("Finished processing market data. End time = {}", datetime.datetime.now())
Exemplo n.º 3
0
    def importStations(self):
        """
        Populate the Station table using stations.jsonl
        Also populates the ShipVendor table if the option is set.
        Writes directly to database.
        """
        tdb, tdenv = self.tdb, self.tdenv
        
        tdenv.NOTE("Processing Stations, this may take a bit: Start time = {}", datetime.datetime.now())
        if self.getOption('shipvend'):
            tdenv.NOTE("Simultaneously processing ShipVendors.")

        if self.getOption('upvend'):
            tdenv.NOTE("Simultaneously processing UpgradeVendors, this will take quite a while.")


        progress = 0
        total = 1
        def blocks(f, size = 65536):
            while True:
                b = f.read(size)
                if not b: break
                yield b

        with open(str(self.dataPath / self.stationsPath), "r",encoding = "utf-8",errors = 'ignore') as f:
            total += (sum(bl.count("\n") for bl in blocks(f)))
        
        with open(str(self.dataPath / self.stationsPath), "rU") as fh:
            if self.getOption("progbar"):
                prog = pbar.Progress(total, 50)
            for line in fh:
                if self.getOption("progbar"):
                    prog.increment(1, postfix=lambda value, goal: " " + str(round(value / total * 100)) + "%")
                else:
                    progress += 1
                    print("\rProgress: (" + str(progress) + "/" + str(total) + ") " + str(round(progress / total * 100, 2)) + "%\t\t", end = "\r")        
                station = json.loads(line)
                
                # Import Stations
                station_id = station['id']
                name = station['name']
                system_id = station['system_id']
                ls_from_star = station['distance_to_star'] if station['distance_to_star'] else 0
                blackmarket = 'Y' if station['has_blackmarket'] else 'N'
                max_pad_size = station['max_landing_pad_size'] if station['max_landing_pad_size'] and station['max_landing_pad_size'] != 'None' else '?'
                market = 'Y' if station['has_market'] else 'N'
                shipyard = 'Y' if station['has_shipyard'] else 'N'
                modified = datetime.datetime.utcfromtimestamp(station['updated_at']).strftime('%Y-%m-%d %H:%M:%S')
                outfitting = 'Y' if station['has_outfitting'] else 'N'
                rearm = 'Y' if station['has_rearm'] else 'N'
                refuel = 'Y' if station['has_refuel'] else 'N'
                repair = 'Y' if station['has_repair'] else 'N'
                planetary = 'Y' if station['is_planetary'] else 'N'
                type_id = station['type_id'] if station['type_id'] else 0
                
                system = self.execute("SELECT System.name FROM System WHERE System.system_id = ?", (system_id,)).fetchone()[0].upper()
                
                result = self.execute("SELECT modified FROM Station WHERE station_id = ?", (station_id,)).fetchone()
                if result:
                    updated = timegm(datetime.datetime.strptime(result[0],'%Y-%m-%d %H:%M:%S').timetuple())
                    if station['updated_at'] > updated:
                        tdenv.DEBUG0("{}/{} has been updated: {} vs {}", 
                                    system ,name, modified, result[0])
                        tdenv.DEBUG1("Updating: {}, {}, {}, {}, {}, {}, {},"
                                              " {}, {}, {}, {}, {}, {}, {}, {}",
                                    station_id, name, system_id, ls_from_star, blackmarket,
                                    max_pad_size, market, shipyard, modified, outfitting,
                                    rearm, refuel, repair, planetary, type_id)
                        self.execute("""UPDATE Station
                                    SET name = ?, system_id = ?, ls_from_star = ?, blackmarket = ?,
                                    max_pad_size = ?, market = ?, shipyard = ?, modified = ?,
                                    outfitting = ?, rearm = ?, refuel = ?, repair = ?, planetary = ?, type_id = ?
                                    WHERE station_id = ?""", 
                                    (name, system_id, ls_from_star, blackmarket,
                                     max_pad_size, market, shipyard, modified,
                                     outfitting, rearm, refuel, repair, planetary, type_id,
                                     station_id))
                        self.updated['Station'] = True
                else:
                    tdenv.DEBUG0("{}/{} has been added:", system ,name)
                    tdenv.DEBUG1("Inserting: {}, {}, {}, {}, {}, {}, {},"
                                              " {}, {}, {}, {}, {}, {}, {}, {}",
                        station_id, name, system_id, ls_from_star, blackmarket,
                        max_pad_size, market, shipyard, modified, outfitting,
                        rearm, refuel, repair, planetary, type_id)
                    self.execute("""INSERT INTO Station (
                                station_id,name,system_id,ls_from_star,
                                blackmarket,max_pad_size,market,shipyard,
                                modified,outfitting,rearm,refuel,
                                repair,planetary,type_id ) VALUES
                                ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) """,
                                (station_id,name,system_id,ls_from_star,
                                 blackmarket,max_pad_size,market,shipyard,
                                 modified,outfitting,rearm,refuel,
                                 repair,planetary,type_id))
                    self.updated['Station'] = True
                
                #Import shipyards into ShipVendors if shipvend is set.
                if station['has_shipyard'] and self.getOption('shipvend'):
                    if not station['shipyard_updated_at']:
                        station['shipyard_updated_at'] = station['updated_at']
                    modified = datetime.datetime.utcfromtimestamp(station['shipyard_updated_at']).strftime('%Y-%m-%d %H:%M:%S')
                    result = self.execute("SELECT modified FROM ShipVendor WHERE station_id = ?", (station_id,)).fetchone()
                    if result:
                        updated = timegm(datetime.datetime.strptime(result[0],'%Y-%m-%d %H:%M:%S').timetuple())
                    else:
                        updated = 0
                    if station['shipyard_updated_at'] > updated:
                        self.execute("DELETE FROM ShipVendor WHERE station_id = ?", (station_id,))
                        tdenv.DEBUG1("{}/{} has shipyard, updating ships sold.", system, name)
                        for ship in station['selling_ships']:
                            # Make sure all the 'Mark N' ship names abbreviate 'Mark' as '<Name> Mk. <Number>'.
                            # Fix capitalization.
                            ship = ship.replace('MK', 'Mk').replace('mk','Mk').replace('mK','Mk')
                            # Fix no '.' in abbreviation.
                            if "Mk" in ship and "Mk." not in ship:
                                ship = ship.replace('Mk', 'Mk.')
                            # Fix no trailing space.
                            if "Mk." in ship and "Mk. " not in ship:
                                ship = ship.replace("Mk.", "Mk. ")
                            # Fix no leading space.
                            if "Mk." in ship and " Mk." not in ship:
                                ship = ship.replace("Mk.", " Mk.")
                            
                            tdenv.DEBUG2("ship_id:{},station_id:{},modified:{}",
                                 ship,
                                 station_id,
                                 modified)
                            try:
                                self.execute("""INSERT INTO ShipVendor
                                        ( ship_id,station_id,modified ) VALUES
                                        ( (SELECT Ship.ship_id FROM Ship WHERE Ship.name = ?), ?, ? ) """,
                                        (ship,
                                         station_id,
                                         modified))
                            except sqlite3.IntegrityError:
                                continue
                        self.updated['ShipVendor'] = True
                        
                #Import Outfitters into UpgradeVendors if upvend is set.
                if station['has_outfitting'] and self.getOption('upvend'):
                    if not station['outfitting_updated_at']:
                        station['outfitting_updated_at'] = station['updated_at']
                    modified = datetime.datetime.utcfromtimestamp(station['outfitting_updated_at']).strftime('%Y-%m-%d %H:%M:%S')
                    result = self.execute("SELECT modified FROM UpgradeVendor WHERE station_id = ?", (station_id,)).fetchone()
                    if result:
                        updated = timegm(datetime.datetime.strptime(result[0],'%Y-%m-%d %H:%M:%S').timetuple())
                    else:
                        updated = 0
                    if station['outfitting_updated_at'] > updated:
                        self.execute("DELETE FROM UpgradeVendor WHERE station_id = ?", (station_id,))
                        tdenv.DEBUG1("{}/{} has outfitting, updating modules sold.", system, name)
                        for upgrade in station['selling_modules']:
                            tdenv.DEBUG2("upgrade_id:{},station_id:{},modified:{}",
                                 upgrade,
                                 station['id'],
                                 modified)
                            try:
                                self.execute("""INSERT INTO UpgradeVendor
                                        ( upgrade_id,station_id,cost,modified ) VALUES
                                        ( ?, ?, (SELECT Upgrade.cost FROM Upgrade WHERE Upgrade.upgrade_id = ?), ? ) """,
                                        (upgrade,
                                         station_id,
                                         upgrade,
                                         modified))
                            except sqlite3.IntegrityError:
                                continue
                        self.updated['UpgradeVendor'] = True
            if self.getOption("progbar"):
                while prog.value < prog.maxValue:
                    prog.increment(1, postfix=lambda value, goal: " " + str(round(value / total * 100)) + "%")
                prog.clear()

        tdenv.NOTE("Finished processing Stations. End time = {}", datetime.datetime.now())
Exemplo n.º 4
0
    def importSystems(self):
        """
        Populate the System table using systems_populated.jsonl
        Writes directly to database.
        """
        tdb, tdenv = self.tdb, self.tdenv
        
        tdenv.NOTE("Processing Systems: Start time = {}", datetime.datetime.now())

        progress = 0
        total = 1
        def blocks(f, size = 65536):
            while True:
                b = f.read(size)
                if not b: break
                yield b

        with open(str(self.dataPath / self.systemsPath), "r",encoding = "utf-8",errors = 'ignore') as f:
            total += (sum(bl.count("\n") for bl in blocks(f)))

        with open(str(self.dataPath / self.systemsPath), "rU") as fh:
            if self.getOption("progbar"):
                prog = pbar.Progress(total, 50)
            for line in fh:
                if self.getOption("progbar"):
                    prog.increment(1, postfix=lambda value, goal: " " + str(round(value / total * 100)) + "%")
                else:
                    progress += 1
                    print("\rProgress: (" + str(progress) + "/" + str(total) + ") " + str(round(progress / total * 100, 2)) + "%\t\t", end = "\r")        
                system = json.loads(line)
                system_id = system['id']
                name = system['name']
                pos_x = system['x']
                pos_y = system['y']
                pos_z = system['z']
                modified = datetime.datetime.utcfromtimestamp(system['updated_at']).strftime('%Y-%m-%d %H:%M:%S')
                
                result = self.execute("SELECT modified FROM System WHERE system_id = ?", (system_id,)).fetchone()
                if result:
                    updated = timegm(datetime.datetime.strptime(result[0],'%Y-%m-%d %H:%M:%S').timetuple())
                    if system['updated_at'] > updated:
                        tdenv.DEBUG0("System '{}' has been updated: '{}' vs '{}'", name, modified, result[0])
                        tdenv.DEBUG1("Updating: {}, {}, {}, {}, {}, {}", system_id, name, pos_x, pos_y, pos_z, modified)
                        self.execute("""UPDATE System
                                    SET name = ?,pos_x = ?,pos_y = ?,pos_z = ?,modified = ?
                                    WHERE system_id = ?""", 
                                    (name, pos_x, pos_y, pos_z, modified,
                                     system_id))
                        self.updated['System'] = True
                else:
                    tdenv.DEBUG0("System '{}' has been added.", name)
                    tdenv.DEBUG1("Inserting: {}, {}, {}, {}, {}, {}", system_id, name, pos_x, pos_y, pos_z, modified)
                    self.execute("""INSERT INTO System
                                ( system_id,name,pos_x,pos_y,pos_z,modified ) VALUES
                                ( ?, ?, ?, ?, ?, ? ) """,
                                (system_id, name, pos_x, pos_y, pos_z, modified))
                    self.updated['System'] = True
            if self.getOption("progbar"):
                while prog.value < prog.maxValue:
                    prog.increment(1, postfix=lambda value, goal: " " + str(round(value / total * 100)) + "%")
                prog.clear()
        
        tdenv.NOTE("Finished processing Systems. End time = {}", datetime.datetime.now())
Exemplo n.º 5
0
    def getBestHops(self, routes, restrictTo=None):
        """
        Given a list of routes, try all available next hops from each
        route.

        Store the results by destination so that we pick the
        best route-to-point for each destination at each step.

        If we have two routes: A->B->D, A->C->D and A->B->D produces
        more profit, there's no point continuing the A->C->D path.
        """

        tdb = self.tdb
        tdenv = self.tdenv
        avoidPlaces = getattr(tdenv, 'avoidPlaces', None) or ()
        assert not restrictTo or isinstance(restrictTo, set)
        maxJumpsPer = tdenv.maxJumpsPer
        maxLyPer = tdenv.maxLyPer
        maxPadSize = tdenv.padSize
        planetary = tdenv.planetary
        noPlanet = tdenv.noPlanet
        maxLsFromStar = tdenv.maxLs or float('inf')
        reqBlackMarket = getattr(tdenv, 'blackMarket', False) or False
        maxAge = getattr(tdenv, 'maxAge') or 0
        credits = tdenv.credits - (getattr(tdenv, 'insurance', 0) or 0)
        fitFunction = self.defaultFit
        capacity = tdenv.capacity
        maxUnits = getattr(tdenv, 'limit') or capacity

        bestToDest = {}
        safetyMargin = 1.0 - tdenv.margin
        unique = tdenv.unique
        loopInt = getattr(tdenv, 'loopInt', 0) or None

        # Penalty is expressed as percentage, reduce it to a multiplier
        if tdenv.lsPenalty:
            lsPenalty = tdenv.lsPenalty / 100
        else:
            lsPenalty = 0

        goalSystem = tdenv.goalSystem
        uniquePath = None

        restrictStations = set()
        if restrictTo:
            for place in restrictTo:
                if isinstance(place, Station):
                    restrictStations.add(place)
                elif isinstance(place, System) and place.stations:
                    restrictStations.update(place.stations)

        # Are we doing direct routes?
        if tdenv.direct:
            if goalSystem and not restrictTo:
                restrictTo = (goalSystem, )
                restrictStations = set(goalSystem.stations)
            if avoidPlaces:
                restrictStations = set(
                    stn for stn in restrictStations
                    if stn not in avoidPlaces and \
                        stn.system not in avoidPlaces
                )

            def station_iterator(srcStation):
                srcSys = srcStation.system
                srcDist = srcSys.distanceTo
                for stn in restrictStations:
                    stnSys = stn.system
                    yield Destination(stnSys, stn, (srcSys, stnSys),
                                      srcDist(stnSys))
        else:
            getDestinations = tdb.getDestinations

            def station_iterator(srcStation):
                yield from getDestinations(
                    srcStation,
                    maxJumps=maxJumpsPer,
                    maxLyPer=maxLyPer,
                    avoidPlaces=avoidPlaces,
                    maxPadSize=maxPadSize,
                    maxLsFromStar=maxLsFromStar,
                    noPlanet=noPlanet,
                    planetary=planetary,
                )

        prog = pbar.Progress(len(routes), 25)
        connections = 0
        getSelling = self.stationsSelling.get
        for route in routes:
            if tdenv.progress:
                prog.increment(1)
            tdenv.DEBUG1("Route = {}", route.str())

            srcStation = route.lastStation
            startCr = credits + int(route.gainCr * safetyMargin)
            routeJumps = len(route.jumps)

            srcSelling = getSelling(srcStation.ID, None)
            srcSelling = tuple(values for values in srcSelling
                               if values[1] <= startCr)
            if not srcSelling:
                tdenv.DEBUG1("Nothing sold/affordable - next.")
                continue

            if goalSystem:
                origSystem = route.firstSystem
                srcSystem = srcStation.system
                srcDistTo = srcSystem.distanceTo
                goalDistTo = goalSystem.distanceTo
                origDistTo = origSystem.distanceTo
                srcGoalDist = srcDistTo(goalSystem)
                srcOrigDist = srcDistTo(origSystem)
                origGoalDist = origDistTo(goalSystem)

            if unique:
                uniquePath = route.route
            elif loopInt:
                uniquePath = route.route[-loopInt:-1]

            stations = (dest for dest in station_iterator(srcStation)
                        if dest.station != srcStation)
            if reqBlackMarket:
                stations = (d for d in stations
                            if d.station.blackMarket == 'Y')
            if uniquePath:
                stations = (d for d in stations if d.station not in uniquePath)
            if restrictStations:
                stations = (d for d in stations
                            if d.station in restrictStations)
            if maxAge:
                stations = (d for d in stations if d.station.dataAge)
                stations = (d for d in stations if d.station.dataAge <= maxAge)
            if goalSystem:
                if bool(tdenv.unique):
                    stations = (d for d in stations
                                if d.system is not srcSystem)
                stations = (
                    d for d in stations
                    if d.system is goalSystem or d.distLy < srcGoalDist)

            if tdenv.debug >= 1:

                def annotate(dest):
                    tdenv.DEBUG1("destSys {}, destStn {}, jumps {}, distLy {}",
                                 dest.system.dbname, dest.station.dbname,
                                 "->".join(jump.str()
                                           for jump in dest.via), dest.distLy)
                    return True

                stations = (d for d in stations if annotate(d))

            for dest in stations:
                dstStation = dest.station

                connections += 1
                items = self.getTrades(srcStation, dstStation, srcSelling)
                if not items:
                    continue
                trade = fitFunction(items, startCr, capacity, maxUnits)

                multiplier = 1.0
                # Calculate total K-lightseconds supercruise time.
                # This will amortize for the start/end stations
                dstSys = dest.system
                if goalSystem and dstSys is not goalSystem:
                    dstGoalDist = goalDistTo(dstSys)
                    # Biggest reward for shortening distance to goal
                    score = 5000 * origGoalDist / dstGoalDist
                    # bias towards bigger reductions
                    score += 50 * srcGoalDist / dstGoalDist
                    # discourage moving back towards origin
                    if dstSys is not origSystem:
                        score += 10 * (origDistTo(dstSys) - srcOrigDist)
                    # Gain per unit pays a small part
                    score += (trade.gainCr / trade.units) / 25
                else:
                    score = trade.gainCr
                if lsPenalty:
                    # Only want 1dp
                    cruiseKls = int(dstStation.lsFromStar / 100) / 10
                    # Produce a curve that favors distances under 1kls
                    # positively, starts to penalize distances over 1k,
                    # and after 4kls starts to penalize aggresively
                    # http://goo.gl/Otj2XP
                    penalty = ((cruiseKls**2) - cruiseKls) / 3
                    penalty *= lsPenalty
                    multiplier *= (1 - penalty)

                score *= multiplier

                dstID = dstStation.ID
                try:
                    # See if there is already a candidate for this destination
                    btd = bestToDest[dstID]
                except KeyError:
                    # No existing candidate, we win by default
                    pass
                else:
                    bestRoute = btd[1]
                    bestScore = btd[5]
                    # Check if it is a better option than we just produced
                    bestTradeScore = bestRoute.score + bestScore
                    newTradeScore = route.score + score
                    if bestTradeScore > newTradeScore:
                        continue
                    if bestTradeScore == newTradeScore:
                        bestLy = btd[4]
                        if bestLy <= dest.distLy:
                            continue

                bestToDest[dstID] = (dstStation, route, trade, dest.via,
                                     dest.distLy, score)

        prog.clear()

        if connections == 0:
            raise NoHopsError(
                "No destinations could be reached within the constraints.")

        result = []
        for (dst, route, trade, jumps, ly, score) in bestToDest.values():
            result.append(route.plus(dst, trade, jumps, score))

        return result
Exemplo n.º 6
0
def download(
    tdenv,
    url,
    localFile,
    headers=None,
    backup=False,
    shebang=None,
    chunkSize=4096,
):
    """
    Fetch data from a URL and save the output
    to a local file. Returns the response headers.

    tdenv:
        TradeEnv we're working under

    url:
        URL we're fetching (http, https or ftp)

    localFile:
        Name of the local file to open.

    headers:
        dict() of additional HTTP headers to send

    shebang:
        function to call on the first line
    """

    requests = import_requests()
    tdenv.NOTE("Requesting {}".format(url))
    req = requests.get(url, headers=headers or None, stream=True)
    req.raise_for_status()

    encoding = req.headers.get('content-encoding', 'uncompress')
    length = req.headers.get('content-length', None)
    transfer = req.headers.get('transfer-encoding', None)
    if transfer != 'chunked':
        # chunked transfer-encoding doesn't need a content-length
        if length is None:
            raise Exception(
                "Remote server replied with invalid content-length.")
        length = int(length)
        if length <= 0:
            raise TradeException(
                "Remote server gave an empty response. Please try again later."
            )

    if tdenv.detail > 1:
        if length:
            tdenv.NOTE("Downloading {} {}ed data", makeUnit(length), encoding)
        else:
            tdenv.NOTE("Downloading {} {}ed data", transfer, encoding)
    tdenv.DEBUG0(str(req.headers).replace("{", "{{").replace("}", "}}"))

    # Figure out how much data we have
    if length and not tdenv.quiet:
        progBar = pbar.Progress(length, 20)
    else:
        progBar = None

    actPath = Path(localFile)
    tmpPath = Path("tmp/{}.dl".format(actPath.name))

    histogram = deque()

    fetched = 0
    lastTime = started = time.time()
    spinner, spinners = 0, [
        '.    ', '..   ', '...  ', ' ... ', '  ...', '   ..', '    .'
    ]
    with tmpPath.open("wb") as fh:
        for data in req.iter_content(chunk_size=chunkSize):
            fh.write(data)
            fetched += len(data)
            if shebang:
                bangLine = data.decode().partition("\n")[0]
                tdenv.DEBUG0("Checking shebang of {}", bangLine)
                shebang(bangLine)
                shebang = None
            if progBar:
                now = time.time()
                deltaT = max(now - lastTime, 0.001)
                lastTime = now
                if len(histogram) >= 15:
                    histogram.popleft()
                histogram.append(len(data) / deltaT)
                progBar.increment(
                    len(data),
                    postfix=lambda value, goal: \
                            " {:>7s} [{:>7s}/s] {:>3.0f}% {:1s}".format(
                            makeUnit(value),
                            makeUnit(sum(histogram) / len(histogram)),
                            (fetched * 100. / length),
                            spinners[spinner]
                        )
                )
                if deltaT > 0.200:
                    spinner = (spinner + 1) % len(spinners)
        tdenv.DEBUG0("End of data")
    if not tdenv.quiet:
        if progBar:
            progBar.clear()
        elapsed = (time.time() - started) or 1
        tdenv.NOTE("Downloaded {} of {}ed data {}/s", makeUnit(fetched),
                   encoding, makeUnit(fetched / elapsed))

    # Swap the file into place
    if backup:
        bakPath = Path(localFile + ".bak")
        if bakPath.exists():
            bakPath.unlink()
        if actPath.exists():
            actPath.rename(localFile + ".bak")
    if actPath.exists():
        actPath.unlink()
    tmpPath.rename(actPath)

    req.close()
    return req.headers
Exemplo n.º 7
0
    def getBestHops(self, routes, restrictTo=None):
        """
        Given a list of routes, try all available next hops from each
        route.

        Store the results by destination so that we pick the
        best route-to-point for each destination at each step.

        If we have two routes: A->B->D, A->C->D and A->B->D produces
        more profit, there's no point continuing the A->C->D path.
        """

        tdb = self.tdb
        tdenv = self.tdenv
        avoidPlaces = getattr(tdenv, 'avoidPlaces', None) or ()
        assert not restrictTo or isinstance(restrictTo, set)
        maxJumpsPer = tdenv.maxJumpsPer
        maxLyPer = tdenv.maxLyPer
        maxPadSize = tdenv.padSize
        planetary = tdenv.planetary
        noPlanet = tdenv.noPlanet
        maxLsFromStar = tdenv.maxLs or float('inf')
        reqBlackMarket = getattr(tdenv, 'blackMarket', False) or False
        maxAge = getattr(tdenv, 'maxAge') or 0
        credits = tdenv.credits - (getattr(tdenv, 'insurance', 0) or 0)
        fitFunction = self.defaultFit
        capacity = tdenv.capacity
        maxUnits = getattr(tdenv, 'limit') or capacity

        bestToDest = {}
        safetyMargin = 1.0 - tdenv.margin
        unique = tdenv.unique
        loopInt = getattr(tdenv, 'loopInt', 0) or None

        # Penalty is expressed as percentage, reduce it to a multiplier
        if tdenv.lsPenalty:
            lsPenalty = max(min(tdenv.lsPenalty / 100, 1), 0)
        else:
            lsPenalty = 0

        goalSystem = tdenv.goalSystem
        uniquePath = None

        restrictStations = set()
        if restrictTo:
            for place in restrictTo:
                if isinstance(place, Station):
                    restrictStations.add(place)
                elif isinstance(place, System) and place.stations:
                    restrictStations.update(place.stations)

        # Are we doing direct routes?
        if tdenv.direct:
            if goalSystem and not restrictTo:
                restrictTo = (goalSystem, )
                restrictStations = set(goalSystem.stations)
            if avoidPlaces:
                restrictStations = set(
                    stn for stn in restrictStations
                    if stn not in avoidPlaces and \
                        stn.system not in avoidPlaces
                )

            def station_iterator(srcStation):
                srcSys = srcStation.system
                srcDist = srcSys.distanceTo
                for stn in restrictStations:
                    stnSys = stn.system
                    yield Destination(stnSys, stn, (srcSys, stnSys),
                                      srcDist(stnSys))
        else:
            getDestinations = tdb.getDestinations

            def station_iterator(srcStation):
                yield from getDestinations(
                    srcStation,
                    maxJumps=maxJumpsPer,
                    maxLyPer=maxLyPer,
                    avoidPlaces=avoidPlaces,
                    maxPadSize=maxPadSize,
                    maxLsFromStar=maxLsFromStar,
                    noPlanet=noPlanet,
                    planetary=planetary,
                )

        prog = pbar.Progress(len(routes), 25)
        connections = 0
        getSelling = self.stationsSelling.get
        for route in routes:
            if tdenv.progress:
                prog.increment(1)
            tdenv.DEBUG1("Route = {}", route.str())

            srcStation = route.lastStation
            startCr = credits + int(route.gainCr * safetyMargin)
            routeJumps = len(route.jumps)

            srcSelling = getSelling(srcStation.ID, None)
            srcSelling = tuple(values for values in srcSelling
                               if values[1] <= startCr)
            if not srcSelling:
                tdenv.DEBUG1("Nothing sold/affordable - next.")
                continue

            if goalSystem:
                origSystem = route.firstSystem
                srcSystem = srcStation.system
                srcDistTo = srcSystem.distanceTo
                goalDistTo = goalSystem.distanceTo
                origDistTo = origSystem.distanceTo
                srcGoalDist = srcDistTo(goalSystem)
                srcOrigDist = srcDistTo(origSystem)
                origGoalDist = origDistTo(goalSystem)

            if unique:
                uniquePath = route.route
            elif loopInt:
                uniquePath = route.route[-loopInt:-1]

            stations = (dest for dest in station_iterator(srcStation)
                        if dest.station != srcStation)
            if reqBlackMarket:
                stations = (d for d in stations
                            if d.station.blackMarket == 'Y')
            if uniquePath:
                stations = (d for d in stations if d.station not in uniquePath)
            if restrictStations:
                stations = (d for d in stations
                            if d.station in restrictStations)
            if maxAge:
                stations = (d for d in stations if d.station.dataAge)
                stations = (d for d in stations if d.station.dataAge <= maxAge)
            if goalSystem:
                if bool(tdenv.unique):
                    stations = (d for d in stations
                                if d.system is not srcSystem)
                stations = (
                    d for d in stations
                    if d.system is goalSystem or d.distLy < srcGoalDist)

            if tdenv.debug >= 1:

                def annotate(dest):
                    tdenv.DEBUG1("destSys {}, destStn {}, jumps {}, distLy {}",
                                 dest.system.dbname, dest.station.dbname,
                                 "->".join(jump.str()
                                           for jump in dest.via), dest.distLy)
                    return True

                stations = (d for d in stations if annotate(d))

            for dest in stations:
                dstStation = dest.station

                connections += 1
                items = self.getTrades(srcStation, dstStation, srcSelling)
                if not items:
                    continue
                trade = fitFunction(items, startCr, capacity, maxUnits)

                multiplier = 1.0
                # Calculate total K-lightseconds supercruise time.
                # This will amortize for the start/end stations
                dstSys = dest.system
                if goalSystem and dstSys is not goalSystem:
                    dstGoalDist = goalDistTo(dstSys)
                    # Biggest reward for shortening distance to goal
                    score = 5000 * origGoalDist / dstGoalDist
                    # bias towards bigger reductions
                    score += 50 * srcGoalDist / dstGoalDist
                    # discourage moving back towards origin
                    if dstSys is not origSystem:
                        score += 10 * (origDistTo(dstSys) - srcOrigDist)
                    # Gain per unit pays a small part
                    score += (trade.gainCr / trade.units) / 25
                else:
                    score = trade.gainCr
                if lsPenalty:
                    # [kfsone] Only want 1dp

                    cruiseKls = int(dstStation.lsFromStar / 100) / 10

                    # Produce a curve that favors distances under 1kls
                    # positively, starts to penalize distances over 1k,
                    # and after 4kls starts to penalize aggresively
                    # http://goo.gl/Otj2XP

                    # [eyeonus] As aadler pointed out, this goes into negative
                    # numbers, which causes problems.
                    #penalty = ((cruiseKls ** 2) - cruiseKls) / 3
                    #penalty *= lsPenalty
                    #multiplier *= (1 - penalty)

                    # [eyeonus]:
                    # (Keep in mind all this ignores values of x<0.)
                    # The sigmoid: (1-(25(x-1))/(1+abs(25(x-1))))/4
                    # ranges between 0.5 and 0 with a drop around x=1,
                    # which makes it great for giving a boost to distances < 1Kls.
                    #
                    # The sigmoid: (-1-(50(x-4))/(1+abs(50(x-4))))/4
                    # ranges between 0 and -0.5 with a drop around x=4,
                    # making it great for penalizing distances > 4Kls.
                    #
                    # The curve: (-1+1/(x+1)^((x+1)/4))/2
                    # ranges between 0 and -0.5 in a smooth arc,
                    # which will be used for making distances
                    # closer to 4Kls get a slightly higher penalty
                    # then distances closer to 1Kls.
                    #
                    # Adding the three together creates a doubly-kinked curve
                    # that ranges from ~0.5 to -1.0, with drops around x=1 and x=4,
                    # which closely matches ksfone's intention without going into
                    # negative numbers and causing problems when we add it to
                    # the multiplier variable. ( 1 + -1 = 0 )
                    #
                    # You can see a graph of the formula here:
                    # https://goo.gl/sn1PqQ
                    # NOTE: The black curve is at a penalty of 0%,
                    # the red curve at a penalty of 100%, with intermediates at
                    # 25%, 50%, and 75%.
                    # The other colored lines show the penalty curves individually
                    # and the teal composite of all three.

                    def sigmoid(x):
                        return x / (1 + abs(x))

                    boost = (1 - sigmoid(25 * (cruiseKls - 1))) / 4
                    drop = (-1 - sigmoid(50 * (cruiseKls - 4))) / 4
                    try:
                        penalty = (-1 + 1 /
                                   (cruiseKls + 1)**((cruiseKls + 1) / 4)) / 2
                    except OverflowError:
                        penalty = -0.5

                    multiplier += (penalty + boost + drop) * lsPenalty

                score *= multiplier

                dstID = dstStation.ID
                try:
                    # See if there is already a candidate for this destination
                    btd = bestToDest[dstID]
                except KeyError:
                    # No existing candidate, we win by default
                    pass
                else:
                    bestRoute = btd[1]
                    bestScore = btd[5]
                    # Check if it is a better option than we just produced
                    bestTradeScore = bestRoute.score + bestScore
                    newTradeScore = route.score + score
                    if bestTradeScore > newTradeScore:
                        continue
                    if bestTradeScore == newTradeScore:
                        bestLy = btd[4]
                        if bestLy <= dest.distLy:
                            continue

                bestToDest[dstID] = (dstStation, route, trade, dest.via,
                                     dest.distLy, score)

        prog.clear()

        if connections == 0:
            raise NoHopsError(
                "No destinations could be reached within the constraints.")

        result = []
        for (dst, route, trade, jumps, ly, score) in bestToDest.values():
            result.append(route.plus(dst, trade, jumps, score))

        return result