progress
Commander cleanup First impl of ship logs Ship display improved Store debugged
This commit is contained in:
parent
2181583843
commit
237dcc8c14
9
main.py
9
main.py
@ -2,18 +2,21 @@
|
||||
import argparse
|
||||
from nullptr.commander import Commander
|
||||
import os
|
||||
from nullptr.store_analyzer import StoreAnalyzer
|
||||
from nullptr.models.base import Base
|
||||
def main(args):
|
||||
if not os.path.isdir(args.data_dir):
|
||||
os.makedirs(args.data_dir )
|
||||
if args.analyze:
|
||||
a = StoreAnalyzer(verbose=True)
|
||||
a.run(args.analyze)
|
||||
else:
|
||||
c = Commander(args.data_dir)
|
||||
c.run()
|
||||
|
||||
# X1-AG74-41076A
|
||||
# X1-KS52-51429E
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-d', '--data-dir', default='data')
|
||||
parser.add_argument('-a', '--analyze', type=argparse.FileType('rb'))
|
||||
args = parser.parse_args()
|
||||
main(args)
|
||||
|
@ -8,6 +8,14 @@ from copy import copy
|
||||
class AnalyzerException(Exception):
|
||||
pass
|
||||
|
||||
def path_dist(m):
|
||||
t = 0
|
||||
o = Point(0,0)
|
||||
for w in m:
|
||||
t +=w.distance(o)
|
||||
o = w
|
||||
return t
|
||||
|
||||
@dataclass
|
||||
class Point:
|
||||
x: int
|
||||
@ -70,7 +78,6 @@ class Analyzer:
|
||||
possibles = sorted(candidates, key=lambda m: m[2])
|
||||
possibles = possibles[:10]
|
||||
results = []
|
||||
print(len(possibles))
|
||||
for typ,m,d in possibles:
|
||||
system = m.waypoint.system
|
||||
p = self.find_path(origin, system)
|
||||
@ -101,7 +108,8 @@ class Analyzer:
|
||||
path = []
|
||||
mkts = [m.waypoint for m in self.store.all_members(orig.system, Marketplace)]
|
||||
cur = orig
|
||||
|
||||
if orig == to:
|
||||
return []
|
||||
while cur != to:
|
||||
best = cur
|
||||
bestdist = cur.distance(to)
|
||||
@ -156,30 +164,25 @@ class Analyzer:
|
||||
|
||||
def find_trade(self, system):
|
||||
prices = self.prices(system)
|
||||
occupied_resources = set()
|
||||
for s in self.store.all('Ship'):
|
||||
if s.mission != 'haul':
|
||||
continue
|
||||
occupied_resources.add(s.mission_state['resource'])
|
||||
best = None
|
||||
for resource, markets in prices.items():
|
||||
if resource in occupied_resources:
|
||||
continue
|
||||
source = sorted(markets, key=lambda x: x['buy'])[0]
|
||||
dest = sorted(markets, key=lambda x: x['sell'])[-1]
|
||||
margin = dest['sell'] -source['buy']
|
||||
if margin < 0:
|
||||
continue
|
||||
|
||||
dist = source['wp'].distance(dest['wp'])
|
||||
dist = max(dist, 0.0001)
|
||||
score = margin / dist
|
||||
if margin < 0:
|
||||
continue
|
||||
o = TradeOption(resource, source['wp'], dest['wp'], margin, dist, score)
|
||||
if best is None or best.score < o.score:
|
||||
best = o
|
||||
return best
|
||||
|
||||
def market_scan(self, system):
|
||||
m = [w.waypoint for w in self.store.all_members(system, Marketplace) if not w.is_fuel()]
|
||||
ms = len(m)
|
||||
path = self.solve_tsp(m)
|
||||
cur = Point(0,0)
|
||||
for w in path:
|
||||
print(w, w.distance(cur))
|
||||
cur = w
|
||||
far = self.store.get(Waypoint, 'X1-NN7-B7')
|
||||
for w in path:
|
||||
print(w, w.distance(far))
|
||||
|
@ -178,6 +178,7 @@ class Api:
|
||||
def navigate(self, ship, wp):
|
||||
data = {'waypointSymbol': str(wp)}
|
||||
response = self.request('post', f'my/ships/{ship}/navigate', data)
|
||||
ship.log(f'nav to {wp}')
|
||||
ship.update(response)
|
||||
|
||||
def dock(self, ship):
|
||||
@ -193,6 +194,7 @@ class Api:
|
||||
def flight_mode(self, ship, mode):
|
||||
data = {'flightMode': mode}
|
||||
data = self.request('patch', f'my/ships/{ship}/nav', data)
|
||||
ship.update(data)
|
||||
return data
|
||||
|
||||
def jump(self, ship, waypoint):
|
||||
@ -243,6 +245,7 @@ class Api:
|
||||
'units': units
|
||||
}
|
||||
data = self.request('post', f'my/ships/{ship}/sell', data)
|
||||
ship.log(f'sell {units} of {typ}')
|
||||
if 'cargo' in data:
|
||||
ship.update(data)
|
||||
if 'agent' in data:
|
||||
@ -255,6 +258,7 @@ class Api:
|
||||
'units': amt
|
||||
}
|
||||
data = self.request('post', f'my/ships/{ship}/purchase', data)
|
||||
ship.log(f'buy {amt} of {typ} at {ship.location}')
|
||||
if 'cargo' in data:
|
||||
ship.update(data)
|
||||
if 'agent' in data:
|
||||
@ -271,6 +275,7 @@ class Api:
|
||||
'units': units
|
||||
}
|
||||
data = self.request('post', f'my/ships/{ship.symbol}/jettison', data)
|
||||
ship.log(f'drop {units} of {typ}')
|
||||
if 'cargo' in data:
|
||||
ship.update(data)
|
||||
if 'agent' in data:
|
||||
|
@ -1,6 +1,6 @@
|
||||
from nullptr.command_line import CommandLine
|
||||
from nullptr.store import Store
|
||||
from nullptr.analyzer import Analyzer, Point
|
||||
from nullptr.analyzer import Analyzer, Point, path_dist
|
||||
import argparse
|
||||
from nullptr.models import *
|
||||
from nullptr.api import Api
|
||||
@ -33,23 +33,27 @@ class Commander(CommandLine):
|
||||
self.stop_auto= False
|
||||
super().__init__()
|
||||
|
||||
######## INFRA #########
|
||||
def handle_eof(self):
|
||||
self.store.close()
|
||||
readline.write_history_file(self.hist_file)
|
||||
print("Goodbye!")
|
||||
|
||||
def do_pp(self):
|
||||
pprint(self.api.last_result)
|
||||
|
||||
def prompt(self):
|
||||
if self.ship:
|
||||
return f'{self.ship.symbol}> '
|
||||
else:
|
||||
return '> '
|
||||
|
||||
def has_ship(self):
|
||||
if self.ship is not None:
|
||||
return True
|
||||
else:
|
||||
print('set a ship')
|
||||
def after_cmd(self):
|
||||
self.store.flush()
|
||||
|
||||
def do_auto(self):
|
||||
self.centcom.run_interactive()
|
||||
######## Resolvers #########
|
||||
def ask_obj(self, typ, prompt):
|
||||
obj = None
|
||||
while obj is None:
|
||||
@ -59,6 +63,12 @@ class Commander(CommandLine):
|
||||
print('not found')
|
||||
return obj
|
||||
|
||||
def has_ship(self):
|
||||
if self.ship is not None:
|
||||
return True
|
||||
else:
|
||||
print('set a ship')
|
||||
|
||||
def select_agent(self):
|
||||
agents = self.store.all(Agent)
|
||||
agent = next(agents, None)
|
||||
@ -66,6 +76,27 @@ class Commander(CommandLine):
|
||||
agent = self.agent_setup()
|
||||
return agent
|
||||
|
||||
def resolve(self, typ, arg):
|
||||
arg = arg.upper()
|
||||
matches = [c for c in self.store.all(typ) if c.symbol.startswith(arg)]
|
||||
if len(matches) == 1:
|
||||
return matches[0]
|
||||
elif len(matches) > 1:
|
||||
raise CommandError('multiple matches')
|
||||
else:
|
||||
raise CommandError('not found')
|
||||
|
||||
def resolve_system(self, system_str):
|
||||
if type(system_str) == System:
|
||||
return system_str
|
||||
if system_str == '':
|
||||
if not self.has_ship(): return
|
||||
system = self.ship.location.system
|
||||
else:
|
||||
system = self.store.get(System, system_str)
|
||||
return system
|
||||
|
||||
######## First run #########
|
||||
def agent_setup(self):
|
||||
symbol = input('agent name: ')
|
||||
agent = self.store.get(Agent, symbol, create=True)
|
||||
@ -90,38 +121,186 @@ class Commander(CommandLine):
|
||||
self.store.flush()
|
||||
return agent
|
||||
|
||||
def resolve(self, typ, arg):
|
||||
arg = arg.upper()
|
||||
matches = [c for c in self.store.all(typ) if c.symbol.startswith(arg)]
|
||||
if len(matches) == 1:
|
||||
return matches[0]
|
||||
elif len(matches) > 1:
|
||||
raise CommandError('multiple matches')
|
||||
else:
|
||||
raise CommandError('not found')
|
||||
def do_token(self):
|
||||
print(self.agent.token)
|
||||
|
||||
def resolve_system(self, system_str):
|
||||
if type(system_str) == System:
|
||||
return system_str
|
||||
if system_str == '':
|
||||
if not self.has_ship(): return
|
||||
system = self.ship.location.system
|
||||
else:
|
||||
system = self.store.get(System, system_str)
|
||||
return system
|
||||
|
||||
def after_cmd(self):
|
||||
self.store.flush()
|
||||
def do_register(self, faction):
|
||||
self.api.register(faction.upper())
|
||||
pprint(self.api.agent)
|
||||
|
||||
######## Fleet #########
|
||||
def do_info(self, arg=''):
|
||||
if arg.startswith('r'):
|
||||
self.api.info()
|
||||
|
||||
pprint(self.agent, 100)
|
||||
|
||||
def do_auto(self):
|
||||
self.centcom.run_interactive()
|
||||
def do_ships(self, arg=''):
|
||||
if arg.startswith('r'):
|
||||
r = self.api.list_ships()
|
||||
else:
|
||||
r = sorted(list(self.store.all('Ship')))
|
||||
pprint(r)
|
||||
|
||||
def do_ship(self, arg=''):
|
||||
if arg != '':
|
||||
symbol = f'{self.agent.symbol}-{arg}'
|
||||
ship = self.store.get('Ship', symbol)
|
||||
if ship is None:
|
||||
print('not found')
|
||||
return
|
||||
else:
|
||||
self.ship = ship
|
||||
pprint(self.ship, 5)
|
||||
|
||||
######## Atlas #########
|
||||
def do_systems(self, page=1):
|
||||
r = self.api.list_systems(int(page))
|
||||
pprint(self.api.last_meta)
|
||||
|
||||
def do_catalog(self, system_str=''):
|
||||
system = self.resolve_system(system_str)
|
||||
r = self.api.list_waypoints(system)
|
||||
for w in r:
|
||||
if 'MARKETPLACE' in w.traits:
|
||||
self.api.marketplace(w)
|
||||
if w.type == 'JUMP_GATE':
|
||||
self.api.jumps(w)
|
||||
|
||||
def do_system(self, system_str):
|
||||
system = self.store.get(System, system_str)
|
||||
r = self.api.list_waypoints(system)
|
||||
pprint(r)
|
||||
|
||||
def do_waypoints(self, system_str=''):
|
||||
loc = None
|
||||
if system_str == '':
|
||||
if not self.has_ship(): return
|
||||
loc = self.ship.location
|
||||
system = loc.system
|
||||
else:
|
||||
system = self.store.get(System, system_str)
|
||||
print(f'=== waypoints in {system}')
|
||||
r = self.store.all_members(system, 'Waypoint')
|
||||
for w in r:
|
||||
|
||||
wname = w.symbol.split('-')[2]
|
||||
traits = ", ".join(w.traits())
|
||||
typ = w.type[0]
|
||||
if typ not in ['F','J'] and len(traits) == 0:
|
||||
continue
|
||||
if loc:
|
||||
dist = loc.distance(w)
|
||||
print(f'{wname:4} {typ} {dist:6} {traits}')
|
||||
else:
|
||||
print(f'{wname:4} {typ} {traits}')
|
||||
|
||||
def do_members(self):
|
||||
if not self.has_ship(): return
|
||||
system = self.ship.location.system
|
||||
pprint(list(self.store.all_members(system)))
|
||||
|
||||
def do_wp(self, s=''):
|
||||
self.do_waypoints(s)
|
||||
|
||||
######## Specials #########
|
||||
def do_market(self, arg=''):
|
||||
if arg == '':
|
||||
if not self.has_ship(): return
|
||||
waypoint = self.ship.location
|
||||
else:
|
||||
waypoint = self.resolve('Waypoint', arg)
|
||||
r = self.api.marketplace(waypoint)
|
||||
pprint(r, 3)
|
||||
|
||||
def do_atlas(self, state=None):
|
||||
atlas = self.store.get(Atlas, 'ATLAS')
|
||||
if state is not None:
|
||||
atlas.enabled = True if state == 'on' else 'off'
|
||||
pprint(atlas, 5)
|
||||
|
||||
def do_jumps(self, waypoint_str=None):
|
||||
if waypoint_str is None:
|
||||
if not self.has_ship(): return
|
||||
waypoint = self.ship.location
|
||||
else:
|
||||
waypoint = self.store.get(Waypoint, waypoint_str.upper())
|
||||
r = self.api.jumps(waypoint)
|
||||
pprint(r)
|
||||
|
||||
def do_shipyard(self):
|
||||
if not self.has_ship(): return
|
||||
location = self.ship.location
|
||||
data = self.api.shipyard(location)
|
||||
for s in must_get(data, 'ships'):
|
||||
print(s['type'], s['purchasePrice'])
|
||||
|
||||
######## Commerce #########
|
||||
def do_refuel(self, source='market'):
|
||||
if not self.has_ship(): return
|
||||
from_cargo = source != 'market'
|
||||
r = self.api.refuel(self.ship, from_cargo=from_cargo)
|
||||
pprint(r)
|
||||
|
||||
def do_cargo(self):
|
||||
if not self.has_ship(): return
|
||||
print(f'== Cargo {self.ship.cargo_units}/{self.ship.cargo_capacity} ==')
|
||||
for c, units in self.ship.cargo.items():
|
||||
print(f'{units:4d} {c}')
|
||||
|
||||
def do_buy(self, resource, amt=None):
|
||||
if not self.has_ship(): return
|
||||
if amt is None:
|
||||
amt = self.ship.cargo_capacity - self.ship.cargo_units
|
||||
self.api.buy(self.ship, resource.upper(), amt)
|
||||
self.do_cargo()
|
||||
|
||||
def do_sell(self, resource, amt=None):
|
||||
if not self.has_ship(): return
|
||||
self.api.sell(self.ship, resource.upper(), amt)
|
||||
self.do_cargo()
|
||||
|
||||
def do_dump(self, resource):
|
||||
if not self.has_ship(): return
|
||||
self.api.jettison(self.ship, resource.upper())
|
||||
self.do_cargo()
|
||||
|
||||
|
||||
def do_purchase(self, ship_type):
|
||||
if not self.has_ship(): return
|
||||
location = self.ship.location
|
||||
ship_type = ship_type.upper()
|
||||
if not ship_type.startswith('SHIP'):
|
||||
ship_type = 'SHIP_' + ship_type
|
||||
s = self.api.purchase(ship_type, location)
|
||||
pprint(s)
|
||||
|
||||
######## Mining #########
|
||||
def do_siphon(self):
|
||||
if not self.has_ship(): return
|
||||
data = self.api.siphon(self.ship)
|
||||
pprint(data)
|
||||
|
||||
def do_survey(self):
|
||||
if not self.has_ship(): return
|
||||
r = self.api.survey(self.ship)
|
||||
pprint(r)
|
||||
|
||||
def do_surveys(self):
|
||||
pprint(list(self.store.all('Survey')))
|
||||
|
||||
def do_extract(self, survey_str=''):
|
||||
if not self.has_ship(): return
|
||||
survey = None
|
||||
if survey_str != '':
|
||||
survey = self.resolve('Survey', survey_str)
|
||||
result = self.api.extract(self.ship, survey)
|
||||
|
||||
symbol = mg(result,'extraction.yield.symbol')
|
||||
units = mg(result,'extraction.yield.units')
|
||||
print(units, symbol)
|
||||
|
||||
|
||||
######## Missions #########
|
||||
def print_mission(self):
|
||||
print(f'mission: {self.ship.mission} ({self.ship.mission_status})')
|
||||
pprint(self.ship.mission_state)
|
||||
@ -155,11 +334,45 @@ class Commander(CommandLine):
|
||||
if not self.has_ship(): return
|
||||
self.centcom.set_mission_param(self.ship, nm, val)
|
||||
|
||||
######## Contracts #########
|
||||
def active_contract(self):
|
||||
for c in self.store.all('Contract'):
|
||||
if c.accepted and not c.fulfilled: return c
|
||||
raise CommandError('no active contract')
|
||||
|
||||
def do_contracts(self, arg=''):
|
||||
if arg.startswith('r'):
|
||||
r = self.api.list_contracts()
|
||||
else:
|
||||
r = list(self.store.all('Contract'))
|
||||
pprint(r)
|
||||
|
||||
def do_negotiate(self):
|
||||
if not self.has_ship(): return
|
||||
r = self.api.negotiate(self.ship)
|
||||
pprint(r)
|
||||
|
||||
def do_accept(self, c):
|
||||
contract = self.resolve('Contract', c)
|
||||
r = self.api.accept_contract(contract)
|
||||
pprint(r)
|
||||
|
||||
def do_deliver(self):
|
||||
if not self.has_ship(): return
|
||||
site = self.ship.location
|
||||
contract = self.active_contract()
|
||||
delivery = contract.unfinished_delivery()
|
||||
if delivery is None:
|
||||
raise CommandError('no delivery')
|
||||
resource = delivery['trade_symbol']
|
||||
self.api.deliver(self.ship, resource, contract)
|
||||
pprint(contract)
|
||||
|
||||
def do_fulfill(self):
|
||||
contract = self.active_contract()
|
||||
self.api.fulfill(contract)
|
||||
|
||||
######## Automissions #########
|
||||
def do_cmine(self):
|
||||
if not self.has_ship(): return
|
||||
site = self.ship.location
|
||||
@ -219,204 +432,25 @@ class Commander(CommandLine):
|
||||
self.centcom.set_mission_param(self.ship, 'hops', markets)
|
||||
self.print_mission()
|
||||
|
||||
def totaldist(self, m):
|
||||
t = 0
|
||||
o = Point(0,0)
|
||||
for w in m:
|
||||
t +=w.distance(o)
|
||||
o = w
|
||||
return t
|
||||
|
||||
def do_sprobe(self):
|
||||
if not self.has_ship(): return
|
||||
system = self.ship.location.system
|
||||
m = [m.waypoint for m in self.store.all_members(system, 'Marketplace')]
|
||||
print("pre", self.totaldist(m))
|
||||
print("pre", path_dist(m))
|
||||
m = self.analyzer.solve_tsp(m)
|
||||
print("post", self.totaldist(m))
|
||||
print("post", path_dist(m))
|
||||
hops = [w.symbol for w in m]
|
||||
self.centcom.init_mission(self.ship, 'probe')
|
||||
self.centcom.set_mission_param(self.ship, 'hops', hops)
|
||||
self.print_mission()
|
||||
|
||||
######## Travel #########
|
||||
def do_travel(self, dest):
|
||||
dest = self.resolve('Waypoint', dest)
|
||||
self.centcom.init_mission(self.ship, 'travel')
|
||||
self.centcom.set_mission_param(self.ship, 'dest', dest)
|
||||
self.print_mission()
|
||||
|
||||
def do_register(self, faction):
|
||||
self.api.register(faction.upper())
|
||||
pprint(self.api.agent)
|
||||
|
||||
def do_systems(self, page=1):
|
||||
r = self.api.list_systems(int(page))
|
||||
pprint(self.api.last_meta)
|
||||
|
||||
def do_stats(self):
|
||||
total = 0
|
||||
for t in self.store.data:
|
||||
num = len(self.store.data[t])
|
||||
nam = t.__name__
|
||||
total += num
|
||||
print(f'{num:5d} {nam}')
|
||||
print(f'{total:5d} total')
|
||||
|
||||
def do_defrag(self):
|
||||
self.store.defrag()
|
||||
|
||||
def do_catalog(self, system_str=''):
|
||||
system = self.resolve_system(system_str)
|
||||
r = self.api.list_waypoints(system)
|
||||
for w in r:
|
||||
if 'MARKETPLACE' in w.traits:
|
||||
self.api.marketplace(w)
|
||||
if w.type == 'JUMP_GATE':
|
||||
self.api.jumps(w)
|
||||
|
||||
def do_market_scan(self):
|
||||
if not self.has_ship(): return
|
||||
loc = self.ship.location.system
|
||||
pprint(self.analyzer.market_scan(loc))
|
||||
|
||||
def do_system(self, system_str):
|
||||
system = self.store.get(System, system_str)
|
||||
r = self.api.list_waypoints(system)
|
||||
pprint(r)
|
||||
|
||||
def do_waypoints(self, system_str=''):
|
||||
loc = None
|
||||
if system_str == '':
|
||||
if not self.has_ship(): return
|
||||
loc = self.ship.location
|
||||
system = loc.system
|
||||
else:
|
||||
system = self.store.get(System, system_str)
|
||||
print(f'=== waypoints in {system}')
|
||||
r = self.store.all_members(system, 'Waypoint')
|
||||
for w in r:
|
||||
traits = []
|
||||
if w.type == 'JUMP_GATE':
|
||||
traits.append('JUMP')
|
||||
if w.type == 'GAS_GIANT':
|
||||
traits.append('GAS')
|
||||
if 'SHIPYARD' in w.traits:
|
||||
traits.append('SHIPYARD')
|
||||
if 'MARKETPLACE' in w.traits:
|
||||
traits.append('MARKET')
|
||||
|
||||
|
||||
if w.type == 'ASTEROID':
|
||||
if 'COMMON_METAL_DEPOSITS' in w.traits:
|
||||
traits.append('METAL')
|
||||
if 'PRECIOUS_METAL_DEPOSITS' in w.traits:
|
||||
traits.append('GOLD')
|
||||
if 'MINERAL_DEPOSITS' in w.traits:
|
||||
traits.append('MINS')
|
||||
if 'STRIPPED' in w.traits:
|
||||
traits.append('STRIPPED')
|
||||
|
||||
wname = w.symbol.split('-')[2]
|
||||
traits = ', '.join(traits)
|
||||
typ = w.type[0]
|
||||
if typ not in ['F','J'] and len(traits) == 0:
|
||||
continue
|
||||
|
||||
if loc:
|
||||
dist = loc.distance(w)
|
||||
print(f'{wname:4} {typ} {dist:6} {traits}')
|
||||
else:
|
||||
print(f'{wname:4} {typ} {traits}')
|
||||
|
||||
def do_members(self):
|
||||
if not self.has_ship(): return
|
||||
system = self.ship.location.system
|
||||
pprint(list(self.store.all_members(system)))
|
||||
|
||||
def do_wp(self, s=''):
|
||||
self.do_waypoints(s)
|
||||
|
||||
def do_marketplace(self, waypoint_str):
|
||||
waypoint = self.store.get(Waypoint, waypoint_str.upper())
|
||||
r = self.api.marketplace(waypoint)
|
||||
|
||||
def do_atlas(self, state=None):
|
||||
atlas = self.store.get(Atlas, 'ATLAS')
|
||||
if state is not None:
|
||||
atlas.enabled = True if state == 'on' else 'off'
|
||||
pprint(atlas, 5)
|
||||
|
||||
def do_jumps(self, waypoint_str=None):
|
||||
if waypoint_str is None:
|
||||
if not self.has_ship(): return
|
||||
waypoint = self.ship.location
|
||||
else:
|
||||
waypoint = self.store.get(Waypoint, waypoint_str.upper())
|
||||
r = self.api.jumps(waypoint)
|
||||
pprint(r)
|
||||
|
||||
def do_query(self, resource):
|
||||
if not self.has_ship(): return
|
||||
location = self.ship.location
|
||||
resource = resource.upper()
|
||||
print('Found markets:')
|
||||
for typ, m, d, plen in self.analyzer.find_closest_markets(resource, 'buy,exchange',location):
|
||||
price = '?'
|
||||
if resource in m.prices:
|
||||
price = m.prices[resource]['buy']
|
||||
print(m, typ[0], f'{plen-1:3} hops {price}')
|
||||
|
||||
def do_path(self):
|
||||
orig = self.ask_obj(System, 'from: ')
|
||||
dest = self.ask_obj(System, 'to: ')
|
||||
# orig = self.store.get(System, 'X1-KS52')
|
||||
# dest = self.store.get(System, 'X1-DA90')
|
||||
path = self.analyzer.find_path(orig, dest)
|
||||
pprint(path)
|
||||
|
||||
def do_ships(self, arg=''):
|
||||
if arg.startswith('r'):
|
||||
r = self.api.list_ships()
|
||||
else:
|
||||
r = sorted(list(self.store.all('Ship')))
|
||||
pprint(r)
|
||||
|
||||
def do_contracts(self, arg=''):
|
||||
if arg.startswith('r'):
|
||||
r = self.api.list_contracts()
|
||||
else:
|
||||
r = list(self.store.all('Contract'))
|
||||
pprint(r)
|
||||
|
||||
def do_deliver(self):
|
||||
if not self.has_ship(): return
|
||||
site = self.ship.location
|
||||
contract = self.active_contract()
|
||||
delivery = contract.unfinished_delivery()
|
||||
if delivery is None:
|
||||
raise CommandError('no delivery')
|
||||
resource = delivery['trade_symbol']
|
||||
self.api.deliver(self.ship, resource, contract)
|
||||
pprint(contract)
|
||||
|
||||
def do_fulfill(self):
|
||||
contract = self.active_contract()
|
||||
self.api.fulfill(contract)
|
||||
|
||||
def do_ship(self, arg=''):
|
||||
if arg != '':
|
||||
symbol = f'{self.agent.symbol}-{arg}'
|
||||
ship = self.store.get('Ship', symbol)
|
||||
if ship is None:
|
||||
print('not found')
|
||||
return
|
||||
else:
|
||||
self.ship = ship
|
||||
pprint(self.ship)
|
||||
|
||||
def do_pp(self):
|
||||
pprint(self.api.last_result)
|
||||
|
||||
def do_go(self, arg):
|
||||
if not self.has_ship(): return
|
||||
system = self.ship.location.system
|
||||
@ -435,19 +469,6 @@ class Commander(CommandLine):
|
||||
self.api.orbit(self.ship)
|
||||
pprint(self.ship)
|
||||
|
||||
def do_siphon(self):
|
||||
if not self.has_ship(): return
|
||||
data = self.api.siphon(self.ship)
|
||||
pprint(data)
|
||||
|
||||
def do_negotiate(self):
|
||||
if not self.has_ship(): return
|
||||
r = self.api.negotiate(self.ship)
|
||||
pprint(r)
|
||||
|
||||
def do_token(self):
|
||||
print(self.agent.token)
|
||||
|
||||
def do_speed(self, speed):
|
||||
if not self.has_ship(): return
|
||||
speed = speed.upper()
|
||||
@ -456,25 +477,42 @@ class Commander(CommandLine):
|
||||
print('please choose from:', speeds)
|
||||
self.api.flight_mode(self.ship, speed)
|
||||
|
||||
def do_refuel(self, source='market'):
|
||||
def do_jump(self, waypoint_str):
|
||||
if not self.has_ship(): return
|
||||
from_cargo = source != 'market'
|
||||
r = self.api.refuel(self.ship, from_cargo=from_cargo)
|
||||
pprint(r)
|
||||
w = self.resolve('Waypoint', waypoint_str)
|
||||
self.api.jump(self.ship, w)
|
||||
pprint(self.ship)
|
||||
|
||||
def do_accept(self, c):
|
||||
contract = self.resolve('Contract', c)
|
||||
r = self.api.accept_contract(contract)
|
||||
pprint(r)
|
||||
######## Analysis #########
|
||||
def do_stats(self):
|
||||
total = 0
|
||||
for t in self.store.data:
|
||||
num = len(self.store.data[t])
|
||||
nam = t.__name__
|
||||
total += num
|
||||
print(f'{num:5d} {nam}')
|
||||
print(f'{total:5d} total')
|
||||
|
||||
def do_market(self, arg=''):
|
||||
if arg == '':
|
||||
def do_defrag(self):
|
||||
self.store.defrag()
|
||||
|
||||
|
||||
def do_query(self, resource):
|
||||
if not self.has_ship(): return
|
||||
waypoint = self.ship.location
|
||||
else:
|
||||
waypoint = self.resolve('Waypoint', arg)
|
||||
r = self.api.marketplace(waypoint)
|
||||
pprint(r, 3)
|
||||
location = self.ship.location
|
||||
resource = resource.upper()
|
||||
print('Found markets:')
|
||||
for typ, m, d, plen in self.analyzer.find_closest_markets(resource, 'buy,exchange',location):
|
||||
price = '?'
|
||||
if resource in m.prices:
|
||||
price = m.prices[resource]['buy']
|
||||
print(m, typ[0], f'{plen-1:3} hops {price}')
|
||||
|
||||
def do_findtrade(self):
|
||||
if not self.has_ship(): return
|
||||
system = self.ship.location.system
|
||||
t = self.analyzer.find_trade(system)
|
||||
pprint(t)
|
||||
|
||||
def do_prices(self, resource=None):
|
||||
if not self.has_ship(): return
|
||||
@ -484,68 +522,3 @@ class Commander(CommandLine):
|
||||
pprint(prices[resource.upper()])
|
||||
else:
|
||||
pprint(prices)
|
||||
|
||||
def do_cargo(self):
|
||||
if not self.has_ship(): return
|
||||
print(f'== Cargo {self.ship.cargo_units}/{self.ship.cargo_capacity} ==')
|
||||
for c, units in self.ship.cargo.items():
|
||||
print(f'{units:4d} {c}')
|
||||
|
||||
def do_buy(self, resource, amt=None):
|
||||
if not self.has_ship(): return
|
||||
if amt is None:
|
||||
amt = self.ship.cargo_capacity - self.ship.cargo_units
|
||||
self.api.buy(self.ship, resource.upper(), amt)
|
||||
self.do_cargo()
|
||||
|
||||
def do_sell(self, resource, amt=None):
|
||||
if not self.has_ship(): return
|
||||
self.api.sell(self.ship, resource.upper(), amt)
|
||||
self.do_cargo()
|
||||
|
||||
def do_dump(self, resource):
|
||||
if not self.has_ship(): return
|
||||
self.api.jettison(self.ship, resource.upper())
|
||||
self.do_cargo()
|
||||
|
||||
def do_shipyard(self):
|
||||
if not self.has_ship(): return
|
||||
location = self.ship.location
|
||||
data = self.api.shipyard(location)
|
||||
for s in must_get(data, 'ships'):
|
||||
print(s['type'], s['purchasePrice'])
|
||||
|
||||
def do_jump(self, waypoint_str):
|
||||
if not self.has_ship(): return
|
||||
|
||||
w = self.resolve('Waypoint', waypoint_str)
|
||||
self.api.jump(self.ship, w)
|
||||
pprint(self.ship)
|
||||
|
||||
def do_purchase(self, ship_type):
|
||||
if not self.has_ship(): return
|
||||
location = self.ship.location
|
||||
ship_type = ship_type.upper()
|
||||
if not ship_type.startswith('SHIP'):
|
||||
ship_type = 'SHIP_' + ship_type
|
||||
s = self.api.purchase(ship_type, location)
|
||||
pprint(s)
|
||||
|
||||
def do_survey(self):
|
||||
if not self.has_ship(): return
|
||||
r = self.api.survey(self.ship)
|
||||
pprint(r)
|
||||
|
||||
def do_surveys(self):
|
||||
pprint(list(self.store.all('Survey')))
|
||||
|
||||
def do_extract(self, survey_str=''):
|
||||
if not self.has_ship(): return
|
||||
survey = None
|
||||
if survey_str != '':
|
||||
survey = self.resolve('Survey', survey_str)
|
||||
result = self.api.extract(self.ship, survey)
|
||||
|
||||
symbol = mg(result,'extraction.yield.symbol')
|
||||
units = mg(result,'extraction.yield.units')
|
||||
print(units, symbol)
|
||||
|
@ -22,12 +22,19 @@ class Base:
|
||||
identifier = 'symbol'
|
||||
|
||||
def __init__(self, symbol, store):
|
||||
self.disable_dirty = True
|
||||
self.file_offset = None
|
||||
self._disable_dirty = True
|
||||
self._file_offset = None
|
||||
self.store = store
|
||||
self.symbol = symbol
|
||||
self.define()
|
||||
self.disable_dirty = False
|
||||
self._disable_dirty = False
|
||||
|
||||
def __setstate__(self, d):
|
||||
self.__init__(d['symbol'], d['store'])
|
||||
self.__dict__.update(d)
|
||||
|
||||
def __getstate__(self):
|
||||
return {k:v for k,v in self.__dict__.items() if not k.startswith('_')}
|
||||
|
||||
@classmethod
|
||||
def ext(cls):
|
||||
@ -73,7 +80,7 @@ class Base:
|
||||
setattr(self, attr, lst)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if name not in ['symbol','store','disable_dirty', 'file_offset'] and not self.disable_dirty:
|
||||
if not name.startswith('_') and not self._disable_dirty:
|
||||
self.store.dirty(self)
|
||||
if issubclass(type(value), Base):
|
||||
value = Reference.create(value)
|
||||
@ -91,11 +98,6 @@ class Base:
|
||||
def is_expired(self):
|
||||
return False
|
||||
|
||||
def load(self, d):
|
||||
self.disable_dirty = True
|
||||
self.__dict__.update(d)
|
||||
self.disable_dirty = False
|
||||
|
||||
def type(self):
|
||||
return self.__class__.__name__
|
||||
|
||||
|
@ -2,6 +2,7 @@ from .base import Base
|
||||
from time import time
|
||||
from nullptr.util import *
|
||||
from nullptr.models import Waypoint
|
||||
import os
|
||||
|
||||
class Ship(Base):
|
||||
def define(self):
|
||||
@ -18,6 +19,18 @@ class Ship(Base):
|
||||
self.mission:str = None
|
||||
self.mission_status:str = 'init'
|
||||
self.role = None
|
||||
self.frame = ''
|
||||
self.speed = "CRUISE"
|
||||
self._log_file = None
|
||||
|
||||
def log(self, m):
|
||||
if self._log_file is None:
|
||||
fn = os.path.join(self.store.data_dir, f'{self.symbol}.{self.ext()}.log')
|
||||
self._log_file = open(fn, 'a')
|
||||
ts = int(time())
|
||||
m = m.strip()
|
||||
self._log_file.write(f'{ts} {m}\n')
|
||||
self._log_file.flush()
|
||||
|
||||
@classmethod
|
||||
def ext(self):
|
||||
@ -30,6 +43,8 @@ class Ship(Base):
|
||||
|
||||
def update(self, d):
|
||||
self.seta('status', d, 'nav.status')
|
||||
self.seta('speed', d, "nav.flightMode")
|
||||
self.seta('frame', d, 'frame.name')
|
||||
getter = self.store.getter(Waypoint, create=True)
|
||||
self.seta('location', d, 'nav.waypointSymbol', interp=getter)
|
||||
self.seta('cargo_capacity', d, 'cargo.capacity')
|
||||
@ -104,16 +119,45 @@ class Ship(Base):
|
||||
self.update_timers()
|
||||
arrival = int(self.arrival - time())
|
||||
cooldown = int(self.cooldown - time())
|
||||
|
||||
role = self.role
|
||||
if role is None:
|
||||
role = 'none'
|
||||
mstatus = self.mission_status
|
||||
if mstatus == 'error':
|
||||
mstatus = mstatus.upper()
|
||||
status = self.status.lower()
|
||||
if status.startswith('in_'):
|
||||
status = status[3:]
|
||||
|
||||
if detail < 2:
|
||||
r = self.symbol
|
||||
if detail > 1:
|
||||
if self.role is not None:
|
||||
r += f' {self.role}'
|
||||
r += ' ' + self.status
|
||||
r += f' [{self.fuel_current}/{self.fuel_capacity}]'
|
||||
r += ' ' + str(self.location)
|
||||
elif detail == 2:
|
||||
symbol = self.symbol.split('-')[1]
|
||||
|
||||
r = f'{symbol:<2} {role:7} {mstatus:8} {str(self.location):11}'
|
||||
if self.is_travelling():
|
||||
r += f' [A: {arrival}]'
|
||||
if self.is_cooldown():
|
||||
r += f' [C: {cooldown}]'
|
||||
else:
|
||||
r = f'== {self.symbol} {self.frame} ==\n'
|
||||
r += f'Role: {role}\n'
|
||||
r += f'Mission: {self.mission} ({mstatus})\n'
|
||||
for k, v in self.mission_state.items():
|
||||
r += f' {k}: {v}\n'
|
||||
adj = 'to' if self.status == 'IN_TRANSIT' else 'at'
|
||||
r += f'Status {self.status} {adj} {self.location}\n'
|
||||
|
||||
r += f'Fuel: {self.fuel_current}/{self.fuel_capacity}\n'
|
||||
r += f'Speed: {self.speed}\n'
|
||||
r += f'Cargo: {self.cargo_units}/{self.cargo_capacity}\n'
|
||||
for res, u in self.cargo.items():
|
||||
r += f' {res}: {u}\n'
|
||||
if self.is_travelling():
|
||||
r += f'Arrival: {arrival} seconds\n'
|
||||
if self.is_cooldown():
|
||||
r += f'Cooldown: {cooldown} seconds \n'
|
||||
|
||||
return r
|
||||
|
||||
|
@ -32,3 +32,26 @@ class Waypoint(Base):
|
||||
def ext(self):
|
||||
return 'way'
|
||||
|
||||
def traits(self):
|
||||
traits = []
|
||||
if self.type == 'JUMP_GATE':
|
||||
traits.append('JUMP')
|
||||
if self.type == 'GAS_GIANT':
|
||||
traits.append('GAS')
|
||||
if 'SHIPYARD' in self.traits:
|
||||
traits.append('SHIPYARD')
|
||||
if 'MARKETPLACE' in self.traits:
|
||||
traits.append('MARKET')
|
||||
|
||||
|
||||
if self.type == 'ASTEROID':
|
||||
if 'COMMON_METAL_DEPOSITS' in self.traits:
|
||||
traits.append('METAL')
|
||||
if 'PRECIOUS_METAL_DEPOSITS' in self.traits:
|
||||
traits.append('GOLD')
|
||||
if 'MINERAL_DEPOSITS' in self.traits:
|
||||
traits.append('MINS')
|
||||
if 'STRIPPED' in self.traits:
|
||||
traits.append('STRIPPED')
|
||||
return traits
|
||||
|
@ -63,6 +63,7 @@ class ChunkHeader:
|
||||
class Store:
|
||||
def __init__(self, data_file, verbose=False):
|
||||
self.init_models()
|
||||
self.data_dir = os.path.dirname(data_file)
|
||||
self.fil = open_file(data_file)
|
||||
self.data = {m: {} for m in self.models}
|
||||
self.system_members = {}
|
||||
@ -102,8 +103,7 @@ class Store:
|
||||
buf = BytesIO(data)
|
||||
p = StoreUnpickler(buf, self)
|
||||
obj = p.load()
|
||||
obj.file_offset = offset
|
||||
obj.disable_dirty = False
|
||||
obj._file_offset = offset
|
||||
self.hold(obj)
|
||||
|
||||
def load(self):
|
||||
@ -143,27 +143,31 @@ class Store:
|
||||
h = ChunkHeader()
|
||||
h.size = sz
|
||||
h.used = used
|
||||
h.offset = self.fil.tell()
|
||||
h.offset = offset
|
||||
h.write(self.fil)
|
||||
return offset, h
|
||||
|
||||
def purge(self, obj):
|
||||
if obj.file_offset is None:
|
||||
if obj._file_offset is None:
|
||||
return
|
||||
self.fil.seek(obj.file_offset)
|
||||
self.fil.seek(obj._file_offset)
|
||||
hdr = ChunkHeader.parse(self.fil)
|
||||
hdr.in_use = False
|
||||
self.fil.seek(obj.file_offset)
|
||||
self.fil.seek(obj._file_offset)
|
||||
hdr.write(self.fil)
|
||||
obj.file_offset = None
|
||||
if type(obj) in self.data and obj.symbol in self.data[type(obj)]:
|
||||
del self.data[type(obj)][obj.symbol]
|
||||
if obj in self.dirty_objects:
|
||||
self.dirty_objects.remove(obj)
|
||||
obj._file_offset = None
|
||||
|
||||
def store(self, obj):
|
||||
data = self.dump_object(obj)
|
||||
osize = len(data)
|
||||
# is there an existing chunk for this obj?
|
||||
if obj.file_offset is not None:
|
||||
if obj._file_offset is not None:
|
||||
# read chunk hdr
|
||||
self.fil.seek(obj.file_offset)
|
||||
self.fil.seek(obj._file_offset)
|
||||
hdr = ChunkHeader.parse(self.fil)
|
||||
csize = hdr.size
|
||||
# if the chunk is too small
|
||||
@ -171,15 +175,15 @@ class Store:
|
||||
# free the chunk
|
||||
hdr.in_use = False
|
||||
# force a new chunk
|
||||
obj.file_offset = None
|
||||
obj._file_offset = None
|
||||
else:
|
||||
# if it is big enough, update the used field
|
||||
hdr.used = osize
|
||||
self.fil.seek(hdr.offset)
|
||||
hdr.write(self.fil)
|
||||
|
||||
if obj.file_offset is None:
|
||||
obj.file_offset, hdr = self.allocate_chunk(osize)
|
||||
if obj._file_offset is None:
|
||||
obj._file_offset, hdr = self.allocate_chunk(osize)
|
||||
# print(type(obj).__name__, hdr)
|
||||
self.fil.write(data)
|
||||
slack = b'\x00' * (hdr.size - hdr.used)
|
||||
@ -294,14 +298,17 @@ class Store:
|
||||
self.fil.flush()
|
||||
self.dirty_objects = set()
|
||||
dur = time() - start_time
|
||||
# print(f'flush done {it} items {dur:.2f}')
|
||||
self.p(f'flush done {it} items {dur:.2f}')
|
||||
|
||||
def defrag(self):
|
||||
nm = self.fil.name
|
||||
self.fil.close()
|
||||
bakfile = nm+'.bak'
|
||||
if os.path.isfile(bakfile):
|
||||
os.remove(bakfile)
|
||||
os.rename(nm, nm + '.bak')
|
||||
self.fil = open(nm, 'ab+')
|
||||
self.fil = open_file(nm)
|
||||
for t in self.data:
|
||||
for o in self.all(t):
|
||||
o.file_offset = None
|
||||
o._file_offset = None
|
||||
self.store(o)
|
||||
|
58
nullptr/store_analyzer.py
Normal file
58
nullptr/store_analyzer.py
Normal file
@ -0,0 +1,58 @@
|
||||
from nullptr.store import CHUNK_MAGIC, ChunkHeader, StoreUnpickler
|
||||
from hexdump import hexdump
|
||||
from io import BytesIO
|
||||
class FakeStore:
|
||||
def get(self, typ, sym, create=False):
|
||||
return None
|
||||
|
||||
class StoreAnalyzer:
|
||||
def __init__(self, verbose=False):
|
||||
self.verbose = verbose
|
||||
|
||||
def load_obj(self, f, sz):
|
||||
buf = BytesIO(f.read(sz))
|
||||
p = StoreUnpickler(buf, FakeStore())
|
||||
obj = p.load()
|
||||
return obj
|
||||
print(obj.symbol, type(obj).__name__)
|
||||
|
||||
def run(self, f):
|
||||
lastpos = 0
|
||||
pos = 0
|
||||
objs = {}
|
||||
result = True
|
||||
f.seek(0)
|
||||
while True:
|
||||
lastpos = pos
|
||||
pos = f.tell()
|
||||
m = f.read(8)
|
||||
if len(m) < 8:
|
||||
break
|
||||
if m != CHUNK_MAGIC:
|
||||
print(f'missing magic at {pos}')
|
||||
result = False
|
||||
self.investigate(f, lastpos)
|
||||
break
|
||||
f.seek(-8, 1)
|
||||
h = ChunkHeader.parse(f)
|
||||
if self.verbose:
|
||||
print(h, pos)
|
||||
if h.in_use:
|
||||
obj = self.load_obj(f, h.used)
|
||||
kobj = obj.symbol, type(obj).__name__
|
||||
if kobj in objs:
|
||||
print(f'Double object {kobj} prev {objs[kobj]} latest {h}')
|
||||
result = False
|
||||
objs[kobj] = h
|
||||
else:
|
||||
f.seek(h.used, 1)
|
||||
f.seek(h.size - h.used, 1)
|
||||
return result
|
||||
|
||||
def investigate(self, f, lastpos):
|
||||
print(f'dumping 1024 bytes from {lastpos}')
|
||||
f.seek(lastpos, 0)
|
||||
d = f.read(1024)
|
||||
|
||||
hexdump(d)
|
||||
print(d.index(CHUNK_MAGIC))
|
@ -1,7 +1,10 @@
|
||||
import unittest
|
||||
import tempfile
|
||||
from nullptr.store import Store
|
||||
from nullptr.store import Store, ChunkHeader
|
||||
from nullptr.models import Base
|
||||
from io import BytesIO
|
||||
import os
|
||||
from nullptr.store_analyzer import StoreAnalyzer
|
||||
|
||||
class Dummy(Base):
|
||||
def define(self):
|
||||
@ -16,7 +19,7 @@ class Dummy(Base):
|
||||
return 'dum'
|
||||
|
||||
def f(self, detail=1):
|
||||
r = super().f(detail)
|
||||
r = super().f(detail) + '.' + self.ext()
|
||||
if detail >2:
|
||||
r += f' c:{self.count}'
|
||||
return r
|
||||
@ -50,10 +53,19 @@ class TestStore(unittest.TestCase):
|
||||
dum2 = self.s.get(Dummy, "7",create=True)
|
||||
self.reopen()
|
||||
dum = self.s.get(Dummy, "5")
|
||||
old_off = dum._file_offset
|
||||
self.assertTrue(old_off is not None)
|
||||
dum.data = "A" * 1000
|
||||
dum.count = 1337
|
||||
self.s.flush()
|
||||
new_off = dum._file_offset
|
||||
self.assertTrue(new_off is not None)
|
||||
self.assertNotEqual(old_off, new_off)
|
||||
self.reopen()
|
||||
dum = self.s.get(Dummy, "5")
|
||||
newer_off = dum._file_offset
|
||||
self.assertTrue(newer_off is not None)
|
||||
self.assertEqual(new_off, newer_off)
|
||||
self.assertEqual(1337, dum.count)
|
||||
|
||||
def test_purge(self):
|
||||
@ -64,6 +76,8 @@ class TestStore(unittest.TestCase):
|
||||
self.s.flush()
|
||||
self.s.purge(dum)
|
||||
self.reopen()
|
||||
dum = self.s.get(Dummy, "5")
|
||||
self.assertIsNone(dum)
|
||||
dum2 = self.s.get(Dummy, "7")
|
||||
self.assertEqual(1337, dum2.count)
|
||||
|
||||
@ -98,4 +112,59 @@ class TestStore(unittest.TestCase):
|
||||
dum3 = self.s.get(Dummy, "9")
|
||||
self.assertEqual(1338, dum3.count)
|
||||
|
||||
def test_dont_relocate(self):
|
||||
dum = self.s.get(Dummy, "5", create=True)
|
||||
dum.data = "A"
|
||||
self.s.flush()
|
||||
old_off = dum._file_offset
|
||||
self.reopen()
|
||||
dum2 = self.s.get(Dummy, "5")
|
||||
dum2.data = "BCDE"
|
||||
self.s.flush()
|
||||
new_off = dum._file_offset
|
||||
self.assertEqual(old_off, new_off)
|
||||
|
||||
def test_chunk_header(self):
|
||||
a = ChunkHeader()
|
||||
a.size = 123
|
||||
a.used = 122
|
||||
a.in_use = True
|
||||
b = BytesIO()
|
||||
a.write(b)
|
||||
b.seek(0)
|
||||
c = ChunkHeader.parse(b)
|
||||
self.assertEqual(c.size, a.size)
|
||||
self.assertEqual(c.used, a.used)
|
||||
self.assertEqual(c.in_use, True)
|
||||
c.in_use = False
|
||||
b.seek(0)
|
||||
c.write(b)
|
||||
b.seek(0)
|
||||
d = ChunkHeader.parse(b)
|
||||
self.assertEqual(d.size, a.size)
|
||||
self.assertEqual(d.used, a.used)
|
||||
self.assertEqual(d.in_use, False)
|
||||
|
||||
def test_mass(self):
|
||||
num = 50
|
||||
for i in range(num):
|
||||
dum = self.s.get(Dummy, str(i), create=True)
|
||||
dum.data = str(i)
|
||||
dum.count = 0
|
||||
self.reopen()
|
||||
sz = os.stat(self.store_file.name).st_size
|
||||
for j in range(50):
|
||||
for i in range(num):
|
||||
dum = self.s.get(Dummy, str(i))
|
||||
# this works because j is max 49, and the slack is 64
|
||||
# so no growing is needed
|
||||
self.assertEqual(dum.data, "B" * j + str(i))
|
||||
self.assertEqual(dum.count, j)
|
||||
dum.data = "B" * (j+1) + str(i)
|
||||
dum.count += 1
|
||||
self.reopen()
|
||||
sz2 = os.stat(self.store_file.name).st_size
|
||||
self.assertEqual(sz, sz2)
|
||||
an = StoreAnalyzer().run(self.store_file)
|
||||
self.assertTrue(an)
|
||||
|
@ -1,2 +1,3 @@
|
||||
requests
|
||||
readline
|
||||
hexdump
|
||||
|
Loading…
Reference in New Issue
Block a user