diff --git a/packages/cli/bin/construction.dart b/packages/cli/bin/construction.dart index e6879d04..cd246b7b 100644 --- a/packages/cli/bin/construction.dart +++ b/packages/cli/bin/construction.dart @@ -3,16 +3,11 @@ import 'package:cli/cli.dart'; import 'package:cli/printing.dart'; Future command(FileSystem fs, ArgResults argResults) async { - final SystemSymbol startSystemSymbol; - if (argResults.rest.isNotEmpty) { - startSystemSymbol = SystemSymbol.fromString(argResults.rest.first); - } else { - final agentCache = AgentCache.load(fs)!; - startSystemSymbol = agentCache.headquartersSystemSymbol; - } - final db = await defaultDatabase(); + final startSystemSymbol = + await startSystemFromArg(db, argResults.rest.firstOrNull); + final systemsCache = SystemsCache.load(fs)!; final jumpGateSymbol = systemsCache .waypointsInSystem(startSystemSymbol) diff --git a/packages/cli/bin/deals_nearby.dart b/packages/cli/bin/deals_nearby.dart index 7d16b5b7..e8f7505a 100644 --- a/packages/cli/bin/deals_nearby.dart +++ b/packages/cli/bin/deals_nearby.dart @@ -28,13 +28,13 @@ Future cliMain(FileSystem fs, ArgResults argResults) async { final behaviorCache = BehaviorCache.load(fs); final shipCache = ShipCache.load(fs)!; - final agentCache = AgentCache.load(fs)!; + final agentCache = await AgentCache.load(db); final contractCache = ContractCache.load(fs)!; final centralCommand = CentralCommand(behaviorCache: behaviorCache, shipCache: shipCache); final start = startArg == null - ? agentCache.headquarters(systemsCache) + ? agentCache!.headquarters(systemsCache) : systemsCache.waypointFromString(startArg)!; final jumpGate = systemsCache.jumpGateWaypointForSystem(start.system)!; @@ -55,7 +55,7 @@ Future cliMain(FileSystem fs, ArgResults argResults) async { final extraSellOpps = []; if (centralCommand.isContractTradingEnabled) { extraSellOpps - .addAll(centralCommand.contractSellOpps(agentCache, contractCache)); + .addAll(centralCommand.contractSellOpps(agentCache!, contractCache)); } if (centralCommand.isConstructionTradingEnabled) { extraSellOpps.addAll(centralCommand.constructionSellOpps()); diff --git a/packages/cli/bin/exports_supply_chain.dart b/packages/cli/bin/exports_supply_chain.dart index 38964d60..f475583e 100644 --- a/packages/cli/bin/exports_supply_chain.dart +++ b/packages/cli/bin/exports_supply_chain.dart @@ -244,11 +244,11 @@ Future command(FileSystem fs, ArgResults argResults) async { final systemsCache = SystemsCache.load(fs)!; final marketListings = MarketListingCache.load(fs); final marketPrices = MarketPrices.load(fs); - final agentCache = AgentCache.load(fs)!; + final agent = await myAgent(db); final constructionCache = ConstructionCache(db); - final jumpgate = systemsCache - .jumpGateWaypointForSystem(agentCache.headquartersSystemSymbol)!; + final jumpgate = + systemsCache.jumpGateWaypointForSystem(agent.headquarters.system)!; final waypointSymbol = jumpgate.symbol; final construction = await constructionCache.getConstruction(waypointSymbol); diff --git a/packages/cli/bin/extract_scores.dart b/packages/cli/bin/extract_scores.dart index c0dc1da8..2a0449e2 100644 --- a/packages/cli/bin/extract_scores.dart +++ b/packages/cli/bin/extract_scores.dart @@ -1,4 +1,3 @@ -import 'package:cli/cache/agent_cache.dart'; import 'package:cli/cache/charting_cache.dart'; import 'package:cli/cache/market_cache.dart'; import 'package:cli/cache/market_prices.dart'; @@ -64,8 +63,7 @@ Future command(FileSystem fs, ArgResults argResults) async { final systems = await SystemsCache.loadOrFetch(fs); final charting = ChartingCache(db); - final agentCache = AgentCache.load(fs)!; - final hqSystem = agentCache.headquartersSystemSymbol; + final hqSystem = await myHqSystemSymbol(db); final marketListings = MarketListingCache.load(fs); final marketPrices = MarketPrices.load(fs); diff --git a/packages/cli/bin/jumpgate.dart b/packages/cli/bin/jumpgate.dart index 8c158bfb..dff2a5e8 100644 --- a/packages/cli/bin/jumpgate.dart +++ b/packages/cli/bin/jumpgate.dart @@ -4,15 +4,11 @@ import 'package:cli/net/auth.dart'; import 'package:cli/printing.dart'; Future command(FileSystem fs, ArgResults argResults) async { - final SystemSymbol startSystemSymbol; - if (argResults.rest.isNotEmpty) { - startSystemSymbol = SystemSymbol.fromString(argResults.rest.first); - } else { - final agentCache = AgentCache.load(fs)!; - startSystemSymbol = agentCache.headquartersSystemSymbol; - } - final db = await defaultDatabase(); + + final startSystemSymbol = + await startSystemFromArg(db, argResults.rest.firstOrNull); + final api = defaultApi(fs, db, getPriority: () => networkPriorityLow); final systemsCache = SystemsCache.load(fs)!; diff --git a/packages/cli/bin/jumpgate_construction.dart b/packages/cli/bin/jumpgate_construction.dart index abb0e037..81400955 100644 --- a/packages/cli/bin/jumpgate_construction.dart +++ b/packages/cli/bin/jumpgate_construction.dart @@ -4,13 +4,8 @@ import 'package:cli/cli.dart'; Future command(FileSystem fs, ArgResults argResults) async { final db = await defaultDatabase(); - final SystemSymbol startSystemSymbol; - if (argResults.rest.isNotEmpty) { - startSystemSymbol = SystemSymbol.fromString(argResults.rest.first); - } else { - final agentCache = AgentCache.load(fs)!; - startSystemSymbol = agentCache.headquartersSystemSymbol; - } + final startSystemSymbol = + await startSystemFromArg(db, argResults.rest.firstOrNull); final systemsCache = SystemsCache.load(fs)!; diff --git a/packages/cli/bin/jumpgates_to_scan.dart b/packages/cli/bin/jumpgates_to_scan.dart index 5d0fa735..df4fba24 100644 --- a/packages/cli/bin/jumpgates_to_scan.dart +++ b/packages/cli/bin/jumpgates_to_scan.dart @@ -6,8 +6,7 @@ Future command(FileSystem fs, ArgResults argResults) async { // Start at the agent's headquarters system. // Walk the web of jump gates to find endpoints we should scan. final db = await defaultDatabase(); - final agentCache = AgentCache.load(fs)!; - final systemSymbol = agentCache.headquartersSystemSymbol; + final systemSymbol = await myHqSystemSymbol(db); final systemsCache = SystemsCache.load(fs)!; final jumpGateCache = JumpGateCache.load(fs); final constructionSnapshot = await ConstructionSnapshot.load(db); diff --git a/packages/cli/bin/market_feeder.dart b/packages/cli/bin/market_feeder.dart index 712fde25..d321af6e 100644 --- a/packages/cli/bin/market_feeder.dart +++ b/packages/cli/bin/market_feeder.dart @@ -22,13 +22,12 @@ Future command(FileSystem fs, ArgResults argResults) async { sellsFuel: defaultSellsFuel(marketListings), ); - final agentCache = AgentCache.load(fs)!; final behaviorCache = BehaviorCache.load(fs); const shipType = ShipType.LIGHT_HAULER; final ship = staticCaches.shipyardShips[shipType]!; final shipSpec = ship.shipSpec; - final credits = agentCache.agent.credits; + final credits = await myCredits(db); // Given a desired export. Find a market to feed. final export = TradeSymbol.fromJson(argResults['export'] as String)!; diff --git a/packages/cli/bin/market_listings.dart b/packages/cli/bin/market_listings.dart index 4d3e431a..77fd57b8 100644 --- a/packages/cli/bin/market_listings.dart +++ b/packages/cli/bin/market_listings.dart @@ -61,9 +61,10 @@ void addSymbols( } Future command(FileSystem fs, ArgResults argResults) async { + final db = await defaultDatabase(); + final systemsCache = SystemsCache.load(fs)!; - final agentCache = AgentCache.load(fs)!; - final hqSystem = agentCache.headquartersSystemSymbol; + final hqSystem = await myHqSystemSymbol(db); final marketListings = MarketListingCache.load(fs); final waypoints = systemsCache.waypointsInSystem(hqSystem); @@ -98,6 +99,8 @@ Future command(FileSystem fs, ArgResults argResults) async { addSymbols(table, 'exchange', listing.exchange, marketSymbol, marketPrices); logger.info(table.toString()); } + + await db.close(); } void main(List args) async { diff --git a/packages/cli/bin/market_nearby_sells.dart b/packages/cli/bin/market_nearby_sells.dart index 99b0f918..45484887 100644 --- a/packages/cli/bin/market_nearby_sells.dart +++ b/packages/cli/bin/market_nearby_sells.dart @@ -16,14 +16,13 @@ Future command(FileSystem fs, ArgResults argResults) async { systemConnectivity, sellsFuel: (_) => false, ); - final agentCache = AgentCache.load(fs)!; final shipCache = ShipCache.load(fs)!; const tradeSymbol = TradeSymbol.DIAMONDS; - final hq = agentCache.headquarters(systemsCache); + final hqSystem = await myHqSystemSymbol(db); final hqMine = systemsCache - .waypointsInSystem(hq.system) + .waypointsInSystem(hqSystem) .firstWhere((w) => w.isAsteroid) .symbol; diff --git a/packages/cli/bin/market_pairs.dart b/packages/cli/bin/market_pairs.dart index 17a90091..639426c0 100644 --- a/packages/cli/bin/market_pairs.dart +++ b/packages/cli/bin/market_pairs.dart @@ -24,9 +24,10 @@ int? distanceBetween( } Future command(FileSystem fs, ArgResults argResults) async { + final db = await defaultDatabase(); + final systemsCache = SystemsCache.load(fs)!; - final agentCache = AgentCache.load(fs)!; - final hqSystem = agentCache.headquartersSystemSymbol; + final hqSystem = await myHqSystemSymbol(db); final marketListings = MarketListingCache.load(fs); final marketPrices = MarketPrices.load(fs); @@ -122,6 +123,8 @@ Future command(FileSystem fs, ArgResults argResults) async { ]); } logger.info(table.toString()); + + await db.close(); } void main(List args) async { diff --git a/packages/cli/bin/simulate.dart b/packages/cli/bin/simulate.dart index 2437904b..1a315452 100644 --- a/packages/cli/bin/simulate.dart +++ b/packages/cli/bin/simulate.dart @@ -161,14 +161,13 @@ Future command(FileSystem fs, ArgResults argResults) async { systemConnectivity, sellsFuel: (_) => false, ); - final agentCache = AgentCache.load(fs)!; final shipyardPrices = ShipyardPrices.load(fs); final shipyardShips = ShipyardShipCache.load(fs); final shipMounts = ShipMountCache.load(fs); - final hq = agentCache.headquarters(systemsCache); + final hqSystem = await myHqSystemSymbol(db); final hqMine = - systemsCache.waypointsInSystem(hq.system).firstWhere((w) => w.isAsteroid); + systemsCache.waypointsInSystem(hqSystem).firstWhere((w) => w.isAsteroid); const tradeSymbol = TradeSymbol.DIAMONDS; const haulerType = ShipType.LIGHT_HAULER; final unitsPerHaulerCycle = shipyardShips.capacityForShipType(haulerType)!; diff --git a/packages/cli/bin/system_routing.dart b/packages/cli/bin/system_routing.dart index f4b6f0e4..fe5b77cc 100644 --- a/packages/cli/bin/system_routing.dart +++ b/packages/cli/bin/system_routing.dart @@ -12,8 +12,7 @@ Future command(FileSystem fs, ArgResults argResults) async { final db = await defaultDatabase(); final systems = SystemsCache.load(fs)!; - final agentCache = AgentCache.load(fs)!; - final hqSystemSymbol = agentCache.headquartersSystemSymbol; + final hqSystemSymbol = await myHqSystemSymbol(db); final marketListings = MarketListingCache.load(fs); final shipyardListings = ShipyardListingCache.load(fs); final jumpGateCache = JumpGateCache.load(fs); diff --git a/packages/cli/bin/system_stats.dart b/packages/cli/bin/system_stats.dart index 5b59cabf..c2b5a334 100644 --- a/packages/cli/bin/system_stats.dart +++ b/packages/cli/bin/system_stats.dart @@ -3,13 +3,8 @@ import 'package:cli/cli.dart'; Future command(FileSystem fs, ArgResults argResults) async { final db = await defaultDatabase(); - final SystemSymbol startSystemSymbol; - if (argResults.rest.isNotEmpty) { - startSystemSymbol = SystemSymbol.fromString(argResults.rest.first); - } else { - final agentCache = AgentCache.load(fs)!; - startSystemSymbol = agentCache.headquartersSystemSymbol; - } + final startSystemSymbol = + await startSystemFromArg(db, argResults.rest.firstOrNull); logger.info('Starting from $startSystemSymbol, known reachable:'); final systemsCache = SystemsCache.load(fs)!; diff --git a/packages/cli/bin/system_to_chart_next.dart b/packages/cli/bin/system_to_chart_next.dart index 2c552b20..28eba88a 100644 --- a/packages/cli/bin/system_to_chart_next.dart +++ b/packages/cli/bin/system_to_chart_next.dart @@ -5,15 +5,8 @@ import 'package:cli/ships.dart'; Future command(FileSystem fs, ArgResults argResults) async { final db = await defaultDatabase(); - - final WaypointSymbol startSymbol; - if (argResults.rest.isNotEmpty) { - startSymbol = WaypointSymbol.fromString(argResults.rest.first); - } else { - final agentCache = AgentCache.load(fs)!; - startSymbol = agentCache.headquartersSymbol; - } - + final startSymbol = + await startWaypointFromArg(db, argResults.rest.firstOrNull); final staticCaches = StaticCaches.load(fs); final systemsCache = SystemsCache.load(fs)!; final charting = ChartingCache(db); diff --git a/packages/cli/bin/systems_explored.dart b/packages/cli/bin/systems_explored.dart index 6503ad7a..3d36c4fb 100644 --- a/packages/cli/bin/systems_explored.dart +++ b/packages/cli/bin/systems_explored.dart @@ -86,10 +86,9 @@ Future command(FileSystem fs, ArgResults argResults) async { final constructionSnapshot = await ConstructionSnapshot.load(db); final systemConnectivity = SystemConnectivity.fromJumpGates(jumpGateCache, constructionSnapshot); - final agentCache = AgentCache.load(fs)!; - final headquartersSystemSymbol = agentCache.headquartersSystemSymbol; + final hqSystemSymbol = await myHqSystemSymbol(db); final reachableSystems = - systemConnectivity.systemsReachableFrom(headquartersSystemSymbol); + systemConnectivity.systemsReachableFrom(hqSystemSymbol); final systemsWithCharts = reachableSystems .where((s) => (chartedWaypointsBySystem[s] ?? []).isNotEmpty); diff --git a/packages/cli/bin/systems_interesting.dart b/packages/cli/bin/systems_interesting.dart index 557a0e5d..6ad9c644 100644 --- a/packages/cli/bin/systems_interesting.dart +++ b/packages/cli/bin/systems_interesting.dart @@ -23,8 +23,7 @@ Future command(FileSystem fs, ArgResults argResults) async { final jumpGateCache = JumpGateCache.load(fs); final systemConnectivity = SystemConnectivity.fromJumpGates(jumpGateCache, constructionSnapshot); - final agentCache = AgentCache.load(fs)!; - final hqSystemSymbol = agentCache.headquartersSystemSymbol; + final hqSystemSymbol = await myHqSystemSymbol(db); final reachableSystems = systemConnectivity.systemsReachableFrom(hqSystemSymbol).toSet(); diff --git a/packages/cli/bin/systems_nearby.dart b/packages/cli/bin/systems_nearby.dart index d6dd3e1e..e6105766 100644 --- a/packages/cli/bin/systems_nearby.dart +++ b/packages/cli/bin/systems_nearby.dart @@ -3,13 +3,8 @@ import 'package:cli/cli.dart'; Future command(FileSystem fs, ArgResults argResults) async { final db = await defaultDatabase(); - final SystemSymbol startSystemSymbol; - if (argResults.rest.isNotEmpty) { - startSystemSymbol = SystemSymbol.fromString(argResults.rest.first); - } else { - final agentCache = AgentCache.load(fs)!; - startSystemSymbol = agentCache.headquartersSystemSymbol; - } + final startSystemSymbol = + await startSystemFromArg(db, argResults.rest.firstOrNull); final marketListings = MarketListingCache.load(fs); final jumpGateCache = JumpGateCache.load(fs); diff --git a/packages/cli/bin/systems_to_chart.dart b/packages/cli/bin/systems_to_chart.dart index d27c866d..78cb8148 100644 --- a/packages/cli/bin/systems_to_chart.dart +++ b/packages/cli/bin/systems_to_chart.dart @@ -4,15 +4,10 @@ import 'package:cli/cli.dart'; /// Walks our known system graph, starting from HQ and prints systems needing /// exploration. Future command(FileSystem fs, ArgResults argResults) async { - final SystemSymbol startSystemSymbol; - if (argResults.rest.isNotEmpty) { - startSystemSymbol = SystemSymbol.fromString(argResults.rest.first); - } else { - final agentCache = AgentCache.load(fs)!; - startSystemSymbol = agentCache.headquartersSystemSymbol; - } - final db = await defaultDatabase(); + final startSystemSymbol = + await startSystemFromArg(db, argResults.rest.firstOrNull); + final jumpGateCache = JumpGateCache.load(fs); final constructionSnapshot = await ConstructionSnapshot.load(db); final systemConnectivity = diff --git a/packages/cli/bin/systems_to_warp_to.dart b/packages/cli/bin/systems_to_warp_to.dart index 464a3fc0..c6dc63ca 100644 --- a/packages/cli/bin/systems_to_warp_to.dart +++ b/packages/cli/bin/systems_to_warp_to.dart @@ -12,8 +12,7 @@ Future command(FileSystem fs, ArgResults argResults) async { final db = await defaultDatabase(); final api = defaultApi(fs, db, getPriority: () => networkPriorityLow); - final agentCache = AgentCache.load(fs)!; - final startSystemSymbol = agentCache.headquartersSystemSymbol; + final startSystemSymbol = await myHqSystemSymbol(db); final staticCaches = StaticCaches.load(fs); final jumpGateCache = JumpGateCache.load(fs); diff --git a/packages/cli/bin/waypoint_cache_ages.dart b/packages/cli/bin/waypoint_cache_ages.dart index fe658bfb..35465c3e 100644 --- a/packages/cli/bin/waypoint_cache_ages.dart +++ b/packages/cli/bin/waypoint_cache_ages.dart @@ -4,15 +4,10 @@ import 'package:cli/printing.dart'; import 'package:cli_table/cli_table.dart'; Future command(FileSystem fs, ArgResults argResults) async { - final SystemSymbol startSystemSymbol; - if (argResults.rest.isNotEmpty) { - startSystemSymbol = SystemSymbol.fromString(argResults.rest.first); - } else { - final agentCache = AgentCache.load(fs)!; - startSystemSymbol = agentCache.headquartersSystemSymbol; - } - final db = await defaultDatabase(); + final startSystemSymbol = + await startSystemFromArg(db, argResults.rest.firstOrNull); + final systemsCache = SystemsCache.load(fs)!; final chartingSnapshot = await ChartingSnapshot.load(db); final constructionSnapshot = await ConstructionSnapshot.load(db); diff --git a/packages/cli/bin/waypoint_reachability.dart b/packages/cli/bin/waypoint_reachability.dart index dd13a4fb..4de56a40 100644 --- a/packages/cli/bin/waypoint_reachability.dart +++ b/packages/cli/bin/waypoint_reachability.dart @@ -3,10 +3,10 @@ import 'package:cli/cli.dart'; import 'package:cli/nav/waypoint_connectivity.dart'; Future command(FileSystem fs, ArgResults argResults) async { + final db = await defaultDatabase(); final staticCaches = StaticCaches.load(fs); final systemsCache = SystemsCache.load(fs)!; - final agentCache = AgentCache.load(fs)!; - final hqSystem = agentCache.headquartersSystemSymbol; + final hqSystem = await myHqSystemSymbol(db); final fuelCapacity = staticCaches.shipyardShips[ShipType.COMMAND_FRIGATE]!.frame.fuelCapacity; @@ -31,6 +31,8 @@ Future command(FileSystem fs, ArgResults argResults) async { ); } } + + await db.close(); } void main(List args) async { diff --git a/packages/cli/lib/behavior/trader.dart b/packages/cli/lib/behavior/trader.dart index 6502ac93..c1658bf7 100644 --- a/packages/cli/lib/behavior/trader.dart +++ b/packages/cli/lib/behavior/trader.dart @@ -284,7 +284,7 @@ Future _completeContract( ) async { final response = await api.contracts.fulfillContract(contract.id); final data = response!.data; - caches.agent.agent = data.agent; + await caches.agent.updateAgent(Agent.fromOpenApi(data.agent)); caches.contracts.updateContract(data.contract); final contactTransaction = ContractTransaction.fulfillment( diff --git a/packages/cli/lib/cache/agent_cache.dart b/packages/cli/lib/cache/agent_cache.dart index 749bb674..b2a5423c 100644 --- a/packages/cli/lib/cache/agent_cache.dart +++ b/packages/cli/lib/cache/agent_cache.dart @@ -1,62 +1,54 @@ import 'package:cli/api.dart'; -import 'package:cli/cache/json_store.dart'; import 'package:cli/cache/systems_cache.dart'; -import 'package:cli/compare.dart'; -import 'package:cli/logger.dart'; +import 'package:cli/cli.dart'; +import 'package:cli/config.dart'; import 'package:cli/net/queries.dart'; -import 'package:file/file.dart'; -import 'package:types/types.dart'; /// Holds the Agent object between requests. /// The "Agent" api object doesn't have a way to be updated, so this /// is a holder for that object. -class AgentCache extends JsonStore { +class AgentCache { /// Creates a new ship cache. - AgentCache( - super.agent, { - required super.fs, - super.path = defaultPath, - this.requestsBetweenChecks = 100, - }) : super(recordToJson: (a) => a.toJson()); + AgentCache(Agent agent, Database db, {this.requestsBetweenChecks = 100}) + : _agent = agent, + _db = db; - /// Creates a new AgentCache from a file. - static AgentCache? load(FileSystem fs, {String path = defaultPath}) { - final agent = JsonStore.loadRecord( - fs, - path, - (j) => Agent.fromJson(j)!, - ); + Agent _agent; + final Database _db; + + /// Loads the agent from the cache. + // TODO(eseidel): Do callers need an AgentCache or just an Agent? + static Future load(Database db) async { + final agent = await db.getAgent(symbol: config.agentSymbol); if (agent == null) { return null; } - return AgentCache(agent, fs: fs, path: path); + return AgentCache(agent, db); } /// Creates a new AgentCache from the API. - static Future loadOrFetch( - Api api, { - required FileSystem fs, - String path = defaultPath, - }) async { + static Future loadOrFetch(Database db, Api api) async { + final cached = await db.getAgent(symbol: config.agentSymbol); + if (cached != null) { + return AgentCache(cached, db); + } final agent = await getMyAgent(api); - return AgentCache(agent, fs: fs, path: path); + return AgentCache(agent, db); } - /// Default location of the cache file. - static const String defaultPath = 'data/agent.json'; - /// Agent object held in the cache. - Agent get agent => record; - set agent(Agent newAgent) { - record = newAgent; - save(); + Agent get agent => _agent; + + /// Sets the agent in the cache. + Future updateAgent(Agent agent) async { + _agent = agent; + await _db.upsertAgent(agent); } /// Adjust the credits of the agent. // All callers of this are probably wrong. - void adjustCredits(int adjustment) { - agent.credits += adjustment; - save(); + Future adjustCredits(int adjustment) async { + await updateAgent(agent.copyWith(credits: agent.credits + adjustment)); } /// Number of requests between checks to ensure ships are up to date. @@ -66,15 +58,16 @@ class AgentCache extends JsonStore { /// The headquarters of the agent. SystemWaypoint headquarters(SystemsCache systems) => - systems.waypoint(agent.headquartersSymbol); + systems.waypoint(agent.headquarters); /// The symbol of the agent's headquarters. - WaypointSymbol get headquartersSymbol => agent.headquartersSymbol; + WaypointSymbol get headquartersSymbol => agent.headquarters; /// The symbol of the system of the agent's headquarters. - SystemSymbol get headquartersSystemSymbol => agent.headquartersSymbol.system; + SystemSymbol get headquartersSystemSymbol => agent.headquarters.system; /// Ensures the agent in the cache is up to date. + // TODO(eseidel): Move this out of this class. Future ensureAgentUpToDate(Api api) async { _requestsSinceLastCheck++; if (_requestsSinceLastCheck < requestsBetweenChecks) { @@ -82,10 +75,10 @@ class AgentCache extends JsonStore { } final newAgent = await getMyAgent(api); _requestsSinceLastCheck = 0; - if (jsonMatches(agent, newAgent)) { + if (newAgent == agent) { return; } logger.warn('Agent changed, updating cache.'); - agent = newAgent; + await updateAgent(newAgent); } } diff --git a/packages/cli/lib/cache/caches.dart b/packages/cli/lib/cache/caches.dart index 5d6dbc1d..8738efc0 100644 --- a/packages/cli/lib/cache/caches.dart +++ b/packages/cli/lib/cache/caches.dart @@ -131,7 +131,7 @@ class Caches { Database db, { Future Function(Uri uri) httpGet = defaultHttpGet, }) async { - final agent = await AgentCache.loadOrFetch(api, fs: fs); + final agent = await AgentCache.loadOrFetch(db, api); final prices = MarketPrices.load(fs); // Intentionally do not load ships from disk (they change too often). final ships = await ShipCache.loadOrFetch(api, fs: fs, forceRefresh: true); diff --git a/packages/cli/lib/cli.dart b/packages/cli/lib/cli.dart index 424dba24..1e21ea54 100644 --- a/packages/cli/lib/cli.dart +++ b/packages/cli/lib/cli.dart @@ -1,6 +1,8 @@ import 'package:args/args.dart'; import 'package:cli/cache/caches.dart'; +import 'package:cli/config.dart'; import 'package:cli/logger.dart'; +import 'package:db/db.dart'; import 'package:file/local.dart'; import 'package:meta/meta.dart'; import 'package:scoped/scoped.dart'; @@ -68,3 +70,45 @@ ShipType shipTypeFromArg(String arg) { String argFromShipType(ShipType shipType) { return shipType.value.substring('SHIP_'.length); } + +/// Common lookups which CLIs might need. + +/// Get the agent from the database. +Future myAgent(Database db) async { + final agent = await db.getAgent(symbol: config.agentSymbol); + return agent!; +} + +/// Get the symbol of the agent's headquarters. +Future myHqSymbol(Database db) async { + final agent = await myAgent(db); + return agent.headquarters; +} + +/// Get the system symbol of the agent's headquarters. +Future myHqSystemSymbol(Database db) async { + final hq = await myHqSymbol(db); + return hq.system; +} + +/// Get the agent's credits. +Future myCredits(Database db) async { + final agent = await myAgent(db); + return agent.credits; +} + +/// Get the start symbol from the command line argument. +Future startWaypointFromArg(Database db, String? arg) async { + if (arg == null) { + return myHqSymbol(db); + } + return WaypointSymbol.fromString(arg); +} + +/// Get the start system from the command line argument. +Future startSystemFromArg(Database db, String? arg) async { + if (arg == null) { + return myHqSystemSymbol(db); + } + return SystemSymbol.fromString(arg); +} diff --git a/packages/cli/lib/config.dart b/packages/cli/lib/config.dart index db014f5d..1b8b2b41 100644 --- a/packages/cli/lib/config.dart +++ b/packages/cli/lib/config.dart @@ -6,6 +6,10 @@ const defaultMaxAge = Duration(days: 3); /// Class for holding our hard-coded configuration values. class Config { + // TODO(eseidel): This should be configured at runtime. + /// The symbol of the agent we are controlling. + final String agentSymbol = 'ESEIDEL'; + /// Whether or not we should enable the idle queue. final bool serviceIdleQueue = true; diff --git a/packages/cli/lib/net/actions.dart b/packages/cli/lib/net/actions.dart index 5d65ea39..bc164dc8 100644 --- a/packages/cli/lib/net/actions.dart +++ b/packages/cli/lib/net/actions.dart @@ -136,7 +136,7 @@ Future> sellAllCargoAndLog( await for (final response in sellAllCargo(api, agentCache, market, shipCache, ship, where: where)) { final marketTransaction = response.transaction; - final agent = response.agent; + final agent = Agent.fromOpenApi(response.agent); logMarketTransaction(ship, marketPrices, agent, marketTransaction); final transaction = Transaction.fromMarketTransaction( marketTransaction, @@ -194,7 +194,7 @@ Future purchaseCargoAndLog( // Trade good REACTOR_FUSION_I has a limit of 10 units per transaction.", // "code":4604,"data":{"waypointSymbol":"X1-UC8-13100A","tradeSymbol": // "REACTOR_FUSION_I","units":60,"tradeVolume":10}}} - final agent = data.agent; + final agent = Agent.fromOpenApi(data.agent); final marketTransaction = data.transaction; logMarketTransaction(ship, marketPrices, agent, marketTransaction); final transaction = Transaction.fromMarketTransaction( @@ -228,7 +228,8 @@ Future purchaseShipAndLog( ) async { final result = await purchaseShip(api, shipCache, agentCache, shipyardSymbol, shipType); - logShipyardTransaction(ship, result.agent, result.transaction); + final agent = Agent.fromOpenApi(result.agent); + logShipyardTransaction(ship, agent, result.transaction); shipErr(ship, 'Bought ship: ${result.ship.symbol}'); final transaction = Transaction.fromShipyardTransaction( result.transaction, @@ -511,7 +512,7 @@ Future useJumpGateAndLog( // TODO(eseidel): JumpShip200Response does not include the agent. // so we modify the agent ourselves. // agentCache.agent = response.data.agent; - agentCache.adjustCredits(-marketTransaction.totalPrice); + await agentCache.adjustCredits(-marketTransaction.totalPrice); final agent = agentCache.agent; logMarketTransaction( @@ -559,7 +560,7 @@ Future acceptContractAndLog( ) async { final response = await api.contracts.acceptContract(contract.id); final data = response!.data; - agentCache.agent = data.agent; + await agentCache.updateAgent(Agent.fromOpenApi(data.agent)); contractCache.updateContract(data.contract); shipInfo(ship, 'Accepted: ${contractDescription(contract)}.'); shipInfo( @@ -596,7 +597,7 @@ Future installMountAndLog( installMountRequest: InstallMountRequest(symbol: tradeSymbol.value), ); final data = response!.data; - agentCache.agent = data.agent; + await agentCache.updateAgent(Agent.fromOpenApi(data.agent)); ship ..cargo = data.cargo ..mounts = data.mounts; @@ -624,7 +625,7 @@ Future removeMountAndLog( removeMountRequest: RemoveMountRequest(symbol: tradeSymbol.value), ); final data = response!.data; - agentCache.agent = data.agent; + await agentCache.updateAgent(Agent.fromOpenApi(data.agent)); ship ..cargo = data.cargo ..mounts = data.mounts; diff --git a/packages/cli/lib/net/direct.dart b/packages/cli/lib/net/direct.dart index 7c1f8bc1..c55d9319 100644 --- a/packages/cli/lib/net/direct.dart +++ b/packages/cli/lib/net/direct.dart @@ -28,7 +28,7 @@ Future purchaseShip( final data = purchaseResponse!.data; // Add the new ship to our cache. shipCache.updateShip(data.ship); - agentCache.agent = data.agent; + await agentCache.updateAgent(Agent.fromOpenApi(data.agent)); return data; } @@ -180,10 +180,11 @@ Future sellCargo( ); final response = await api.fleet.sellCargo(ship.symbol, sellCargoRequest: request); - ship.cargo = response!.data.cargo; + final data = response!.data; + ship.cargo = data.cargo; shipCache.updateShip(ship); - agentCache.agent = response.data.agent; - return response.data; + await agentCache.updateAgent(Agent.fromOpenApi(data.agent)); + return data; } /// Purchase [units] of [tradeSymbol] from market. @@ -206,7 +207,7 @@ Future purchaseCargo( final data = response!.data; ship.cargo = data.cargo; shipCache.updateShip(ship); - agentCache.agent = data.agent; + await agentCache.updateAgent(Agent.fromOpenApi(data.agent)); return data; } @@ -224,7 +225,7 @@ Future refuelShip( // refill to full. final responseWrapper = await api.fleet.refuelShip(ship.symbol); final data = responseWrapper!.data; - agentCache.agent = data.agent; + await agentCache.updateAgent(Agent.fromOpenApi(data.agent)); ship.fuel = data.fuel; shipCache.updateShip(ship); return data; diff --git a/packages/cli/lib/net/queries.dart b/packages/cli/lib/net/queries.dart index 54ee84c2..c09d6a66 100644 --- a/packages/cli/lib/net/queries.dart +++ b/packages/cli/lib/net/queries.dart @@ -73,7 +73,7 @@ Stream getAllFactions(Api api) { /// Fetch user's [Agent] object. Future getMyAgent(Api api) async { final response = await api.agents.getMyAgent(); - return response!.data; + return Agent.fromOpenApi(response!.data); } /// Fetch shipyard for a given waypoint, will throw if the waypoint does not diff --git a/packages/cli/test/behavior/advance_test.dart b/packages/cli/test/behavior/advance_test.dart index 030b652b..54860ee2 100644 --- a/packages/cli/test/behavior/advance_test.dart +++ b/packages/cli/test/behavior/advance_test.dart @@ -11,8 +11,6 @@ import '../cache/caches_mock.dart'; class _MockApi extends Mock implements Api {} -class _MockAgent extends Mock implements Agent {} - class _MockCentralCommand extends Mock implements CentralCommand {} class _MockDatabase extends Mock implements Database {} @@ -38,9 +36,8 @@ void main() { when(() => ship.symbol).thenReturn(shipSymbol.symbol); when(() => ship.nav).thenReturn(shipNav); when(() => shipNav.status).thenReturn(ShipNavStatus.DOCKED); - final agent = _MockAgent(); + final agent = Agent.test(); when(() => caches.agent.agent).thenReturn(agent); - when(() => agent.credits).thenReturn(1000000); final behaviorState = BehaviorState(shipSymbol, Behavior.idle); final centralCommand = _MockCentralCommand(); @@ -69,9 +66,8 @@ void main() { final shipNav = _MockShipNav(); final shipNavRoute = _MockShipNavRoute(); final caches = mockCaches(); - final agent = _MockAgent(); + final agent = Agent.test(); when(() => caches.agent.agent).thenReturn(agent); - when(() => agent.credits).thenReturn(1000000); final now = DateTime(2021); final arrivalTime = now.add(const Duration(seconds: 1)); diff --git a/packages/cli/test/behavior/buy_ship_test.dart b/packages/cli/test/behavior/buy_ship_test.dart index 6b2e8291..b10ec9c8 100644 --- a/packages/cli/test/behavior/buy_ship_test.dart +++ b/packages/cli/test/behavior/buy_ship_test.dart @@ -9,8 +9,6 @@ import 'package:types/types.dart'; import '../cache/caches_mock.dart'; -class _MockAgent extends Mock implements Agent {} - class _MockApi extends Mock implements Api {} class _MockCentralCommand extends Mock implements CentralCommand {} @@ -76,9 +74,11 @@ void main() { when(() => shipNav.waypointSymbol).thenReturn(symbol.waypoint); when(() => shipNav.systemSymbol).thenReturn(symbol.systemString); - final agent = _MockAgent(); + final agent = Agent.test(); when(() => caches.agent.agent).thenReturn(agent); - when(() => agent.credits).thenReturn(1000000); + registerFallbackValue(agent); + when(() => caches.agent.updateAgent(any())) + .thenAnswer((_) => Future.value()); when(() => caches.waypoints.waypointsInSystem(symbol.system)) .thenAnswer((_) => Future.value([])); @@ -128,7 +128,7 @@ void main() { (_) => Future.value( PurchaseShip201Response( data: PurchaseShip201ResponseData( - agent: agent, + agent: agent.toOpenApi(), transaction: transaction, ship: ship, // Supposed to be the new ship, cheating for the mock. ), diff --git a/packages/cli/test/behavior/central_command_test.dart b/packages/cli/test/behavior/central_command_test.dart index e1b7bb78..86f767cd 100644 --- a/packages/cli/test/behavior/central_command_test.dart +++ b/packages/cli/test/behavior/central_command_test.dart @@ -2,6 +2,7 @@ import 'package:cli/behavior/central_command.dart'; import 'package:cli/cache/caches.dart'; import 'package:cli/config.dart'; import 'package:cli/logger.dart'; +import 'package:db/db.dart'; import 'package:file/memory.dart'; import 'package:mocktail/mocktail.dart'; import 'package:test/test.dart'; @@ -9,8 +10,6 @@ import 'package:types/types.dart'; import '../cache/caches_mock.dart'; -class _MockAgent extends Mock implements Agent {} - class _MockAgentCache extends Mock implements AgentCache {} class _MockApi extends Mock implements Api {} @@ -25,6 +24,8 @@ class _MockContractTerms extends Mock implements ContractTerms {} class _MockCostedDeal extends Mock implements CostedDeal {} +class _MockDatabase extends Mock implements Database {} + class _MockDeal extends Mock implements Deal {} class _MockLogger extends Mock implements Logger {} @@ -144,11 +145,11 @@ void main() { test('CentralCommand.affordableContracts', () { final ship = _MockShip(); - final agent = _MockAgent(); // TODO(eseidel): Contracts are disabled under 100000 credits. - when(() => agent.credits).thenReturn(100000); + final agent = Agent.test(credits: 100000); final fs = MemoryFileSystem.test(); - final agentCache = AgentCache(agent, fs: fs); + final db = _MockDatabase(); + final agentCache = AgentCache(agent, db); when(() => ship.symbol).thenReturn('S'); // Unless we change Contract.isExpired to take a getNow, we need to use // a real DateTime here. @@ -489,7 +490,7 @@ void main() { final caches = mockCaches(); final ship = _MockShip(); final shipNav = _MockShipNav(); - final faction = FactionSymbol.AEGIS.value; + const faction = FactionSymbol.AEGIS; when(() => shipNav.systemSymbol).thenReturn('W-A'); when(() => ship.nav).thenReturn(shipNav); final shipSymbol = ShipSymbol.fromString('X-A'); @@ -502,7 +503,7 @@ void main() { when(() => ship.registration).thenReturn( ShipRegistration( name: shipSymbol.symbol, - factionSymbol: faction, + factionSymbol: faction.value, role: ShipRole.COMMAND, ), ); @@ -541,7 +542,7 @@ void main() { when(() => caches.agent.agent).thenReturn( Agent( symbol: shipSymbol.agentName, - headquarters: hqSymbol.waypoint, + headquarters: hqSymbol, credits: 100000, shipCount: 1, startingFaction: faction, @@ -600,8 +601,7 @@ void main() { when(() => contractCache.activeContracts).thenReturn([contract]); final agentCache = _MockAgentCache(); - final agent = _MockAgent(); - when(() => agent.credits).thenReturn(100000); + final agent = Agent.test(credits: 100000); when(() => agentCache.agent).thenReturn(agent); int remainingUnitsNeededForContract( diff --git a/packages/cli/test/behavior/miner_test.dart b/packages/cli/test/behavior/miner_test.dart index 64c32da2..6deb4882 100644 --- a/packages/cli/test/behavior/miner_test.dart +++ b/packages/cli/test/behavior/miner_test.dart @@ -12,8 +12,6 @@ import 'package:types/types.dart'; import '../cache/caches_mock.dart'; -class _MockAgent extends Mock implements Agent {} - class _MockApi extends Mock implements Api {} class _MockCentralCommand extends Mock implements CentralCommand {} @@ -596,8 +594,11 @@ void main() { (_) => Future.value(market), ); final fleetApi = _MockFleetApi(); - final agent = _MockAgent(); - when(() => agent.credits).thenReturn(1000000); + final agent = Agent.test(); + registerFallbackValue(agent); + when(() => caches.agent.updateAgent(any())) + .thenAnswer((_) => Future.value()); + final transaction = MarketTransaction( tradeSymbol: tradeSymbol.value, units: cargoCapacity, @@ -622,7 +623,7 @@ void main() { (_) => Future.value( SellCargo201Response( data: SellCargo201ResponseData( - agent: agent, + agent: agent.toOpenApi(), cargo: ShipCargo(capacity: cargoCapacity, units: 0), transaction: transaction, ), diff --git a/packages/cli/test/behavior/mount_from_buy_test.dart b/packages/cli/test/behavior/mount_from_buy_test.dart index 0f081cff..c74603e7 100644 --- a/packages/cli/test/behavior/mount_from_buy_test.dart +++ b/packages/cli/test/behavior/mount_from_buy_test.dart @@ -9,8 +9,6 @@ import 'package:types/types.dart'; import '../cache/caches_mock.dart'; -class _MockAgent extends Mock implements Agent {} - class _MockApi extends Mock implements Api {} class _MockCentralCommand extends Mock implements CentralCommand {} @@ -40,9 +38,12 @@ void main() { final fleetApi = _MockFleetApi(); when(() => api.fleet).thenReturn(fleetApi); final caches = mockCaches(); - final agent = _MockAgent(); + final agent = Agent.test(); when(() => caches.agent.agent).thenReturn(agent); - when(() => agent.credits).thenReturn(1000000); + registerFallbackValue(agent); + when(() => caches.agent.updateAgent(any())) + .thenAnswer((_) => Future.value()); + final ship = _MockShip(); final shipNav = _MockShipNav(); final centralCommand = _MockCentralCommand(); @@ -80,7 +81,6 @@ void main() { requirements: ShipRequirements(), ), ); - when(() => caches.agent.headquartersSymbol).thenReturn(waypointSymbol); when(() => ship.fuel).thenReturn(ShipFuel(current: 100, capacity: 100)); final shipEngine = _MockShipEngine(); when(() => shipEngine.speed).thenReturn(10); @@ -182,7 +182,7 @@ void main() { (_) => Future.value( InstallMount201Response( data: InstallMount201ResponseData( - agent: agent, + agent: agent.toOpenApi(), cargo: shipCargo, transaction: ShipModificationTransaction( waypointSymbol: waypointSymbol.waypoint, @@ -206,7 +206,7 @@ void main() { (_) => Future.value( RemoveMount201Response( data: RemoveMount201ResponseData( - agent: agent, + agent: agent.toOpenApi(), cargo: shipCargo, transaction: ShipModificationTransaction( waypointSymbol: waypointSymbol.waypoint, diff --git a/packages/cli/test/behavior/trader_test.dart b/packages/cli/test/behavior/trader_test.dart index 008ee3cc..5f04d6db 100644 --- a/packages/cli/test/behavior/trader_test.dart +++ b/packages/cli/test/behavior/trader_test.dart @@ -9,8 +9,6 @@ import 'package:types/types.dart'; import '../cache/caches_mock.dart'; -class _MockAgent extends Mock implements Agent {} - class _MockApi extends Mock implements Api {} class _MockCentralCommand extends Mock implements CentralCommand {} @@ -154,9 +152,12 @@ void main() { when(() => shipCargo.capacity).thenReturn(10); when(() => shipCargo.inventory).thenReturn([]); - final agent = _MockAgent(); + final agent = Agent.test(); when(() => caches.agent.agent).thenReturn(agent); - when(() => agent.credits).thenReturn(1000000); + registerFallbackValue(agent); + when(() => caches.agent.updateAgent(any())) + .thenAnswer((_) => Future.value()); + final state = BehaviorState(shipSymbol, Behavior.trader); when( @@ -171,7 +172,7 @@ void main() { (invocation) => Future.value( PurchaseCargo201Response( data: SellCargo201ResponseData( - agent: agent, + agent: agent.toOpenApi(), cargo: shipCargo, transaction: MarketTransaction( waypointSymbol: start.waypoint, @@ -359,9 +360,11 @@ void main() { ), ]); - final agent = _MockAgent(); + final agent = Agent.test(); when(() => caches.agent.agent).thenReturn(agent); - when(() => agent.credits).thenReturn(1000000); + registerFallbackValue(agent); + when(() => caches.agent.updateAgent(any())) + .thenAnswer((_) => Future.value()); final transaction = MarketTransaction( pricePerUnit: 100, @@ -382,7 +385,7 @@ void main() { (_) => Future.value( RefuelShip200Response( data: RefuelShip200ResponseData( - agent: agent, + agent: agent.toOpenApi(), fuel: ShipFuel(current: fuelCapacity, capacity: fuelCapacity), transaction: transaction, ), @@ -408,7 +411,7 @@ void main() { (_) => Future.value( SellCargo201Response( data: SellCargo201ResponseData( - agent: agent, + agent: agent.toOpenApi(), cargo: shipCargo, transaction: MarketTransaction( waypointSymbol: start.waypoint, @@ -529,9 +532,8 @@ void main() { ), ); - final agent = _MockAgent(); + final agent = Agent.test(); when(() => caches.agent.agent).thenReturn(agent); - when(() => agent.credits).thenReturn(1000000); final contractsApi = _MockContractsApi(); when(() => api.contracts).thenReturn(contractsApi); @@ -1028,6 +1030,8 @@ void main() { final start = WaypointSymbol.fromString('S-A-B'); final end = WaypointSymbol.fromString('S-A-C'); + final agent = Agent.test(); + final ship = _MockShip(); final shipNav = _MockShipNav(); when(() => ship.nav).thenReturn(shipNav); @@ -1180,15 +1184,16 @@ void main() { AcceptContract200Response( data: AcceptContract200ResponseData( contract: contract, - agent: _MockAgent(), + agent: agent.toOpenApi(), ), ), ), ); - final agent = _MockAgent(); when(() => caches.agent.agent).thenReturn(agent); - when(() => agent.credits).thenReturn(1000000); + registerFallbackValue(agent); + when(() => caches.agent.updateAgent(any())) + .thenAnswer((_) => Future.value()); when(() => db.insertTransaction(any())).thenAnswer((_) => Future.value()); @@ -1373,9 +1378,8 @@ void main() { ), ); - final agent = _MockAgent(); + final agent = Agent.test(); when(() => caches.agent.agent).thenReturn(agent); - when(() => agent.credits).thenReturn(1000000); when(() => db.insertTransaction(any())).thenAnswer((_) => Future.value()); diff --git a/packages/cli/test/cache/agent_cache_test.dart b/packages/cli/test/cache/agent_cache_test.dart index c32859a1..361e63e2 100644 --- a/packages/cli/test/cache/agent_cache_test.dart +++ b/packages/cli/test/cache/agent_cache_test.dart @@ -1,33 +1,33 @@ import 'package:cli/api.dart'; import 'package:cli/cache/agent_cache.dart'; import 'package:cli/logger.dart'; -import 'package:file/memory.dart'; +import 'package:db/db.dart'; import 'package:mocktail/mocktail.dart'; import 'package:test/test.dart'; import 'package:types/types.dart'; -class _MockAgent extends Mock implements Agent {} - class _MockAgentsApi extends Mock implements AgentsApi {} class _MockApi extends Mock implements Api {} class _MockLogger extends Mock implements Logger {} +class _MockDatabase extends Mock implements Database {} + void main() { test('AgentCache smoke test', () { final api = _MockApi(); - final agent = _MockAgent(); - when(agent.toJson).thenReturn({}); - final newAgent = _MockAgent(); - when(newAgent.toJson).thenReturn({}); + final agent = Agent.test(credits: 1); + final newAgent = Agent.test(credits: 2); final agents = _MockAgentsApi(); when(() => api.agents).thenReturn(agents); when(agents.getMyAgent).thenAnswer( - (_) => Future.value(GetMyAgent200Response(data: newAgent)), + (_) => Future.value(GetMyAgent200Response(data: newAgent.toOpenApi())), ); - final fs = MemoryFileSystem.test(); - final cache = AgentCache(agent, fs: fs, requestsBetweenChecks: 3); + final db = _MockDatabase(); + registerFallbackValue(Agent.test()); + when(() => db.upsertAgent(any())).thenAnswer((_) => Future.value()); + final cache = AgentCache(agent, db, requestsBetweenChecks: 3); expect(cache.agent, agent); cache.ensureAgentUpToDate(api); verifyNever(agents.getMyAgent); @@ -39,20 +39,4 @@ void main() { }); verify(agents.getMyAgent).called(1); }); - - test('AgentCache save/load round trip', () { - final agent = Agent( - accountId: 'accountId', - symbol: 'symbol', - headquarters: 'headquarters', - credits: 100, - shipCount: 1, - startingFaction: 'startingFaction', - ); - final fs = MemoryFileSystem.test(); - AgentCache(agent, fs: fs).save(); - final loaded = AgentCache.load(fs); - expect(loaded, isNotNull); - expect(loaded!.agent.credits, agent.credits); - }); } diff --git a/packages/cli/test/cache/caches_test.dart b/packages/cli/test/cache/caches_test.dart index ef888ef0..0e59297b 100644 --- a/packages/cli/test/cache/caches_test.dart +++ b/packages/cli/test/cache/caches_test.dart @@ -4,8 +4,6 @@ import 'package:file/memory.dart'; import 'package:mocktail/mocktail.dart'; import 'package:test/test.dart'; -class _MockAgent extends Mock implements Agent {} - class _MockAgentsApi extends Mock implements AgentsApi {} class _MockApi extends Mock implements Api {} @@ -26,9 +24,12 @@ void main() { final db = _MockDatabase(); final agentsApi = _MockAgentsApi(); when(() => api.agents).thenReturn(agentsApi); - final agent = _MockAgent(); - when(agentsApi.getMyAgent) - .thenAnswer((_) => Future.value(GetMyAgent200Response(data: agent))); + final agent = Agent.test(); + when(() => db.getAgent(symbol: any(named: 'symbol'))) + .thenAnswer((_) => Future.value(agent)); + when(agentsApi.getMyAgent).thenAnswer( + (_) => Future.value(GetMyAgent200Response(data: agent.toOpenApi())), + ); final fleetApi = _MockFleetApi(); when(() => api.fleet).thenReturn(fleetApi); when( diff --git a/packages/cli/test/nav/navigation_test.dart b/packages/cli/test/nav/navigation_test.dart index c5b1ffc5..21b6e43c 100644 --- a/packages/cli/test/nav/navigation_test.dart +++ b/packages/cli/test/nav/navigation_test.dart @@ -13,8 +13,6 @@ import '../cache/caches_mock.dart'; class _MockApi extends Mock implements Api {} -class _MockAgent extends Mock implements Agent {} - class _MockFleetApi extends Mock implements FleetApi {} class _MockShipReactor extends Mock implements ShipReactor {} @@ -119,9 +117,9 @@ void main() { final fleetApi = _MockFleetApi(); when(() => api.fleet).thenReturn(fleetApi); final shipNav = _MockShipNav(); - final agent = _MockAgent(); - when(() => agent.credits).thenReturn(10000000); + final agent = Agent.test(); when(() => caches.agent.agent).thenReturn(agent); + when(() => caches.agent.adjustCredits(any())).thenAnswer((_) async {}); const shipSymbol = ShipSymbol('S', 1); // We use a real Ship to allow setting/reading from cooldown. final ship = Ship( @@ -216,7 +214,7 @@ void main() { totalPrice: 10000, timestamp: now, ), - agent: agent, + agent: agent.toOpenApi(), ), ), ); diff --git a/packages/cli/test/net/actions_test.dart b/packages/cli/test/net/actions_test.dart index c99e12b4..cae59429 100644 --- a/packages/cli/test/net/actions_test.dart +++ b/packages/cli/test/net/actions_test.dart @@ -2,13 +2,10 @@ import 'package:cli/cache/caches.dart'; import 'package:cli/logger.dart'; import 'package:cli/net/actions.dart'; import 'package:db/db.dart'; -import 'package:file/memory.dart'; import 'package:mocktail/mocktail.dart'; import 'package:test/test.dart'; import 'package:types/types.dart'; -class _MockAgent extends Mock implements Agent {} - class _MockAgentCache extends Mock implements AgentCache {} class _MockApi extends Mock implements Api {} @@ -52,12 +49,11 @@ void main() { when(() => api.fleet).thenReturn(fleetApi); final shipCache = _MockShipCache(); - final agent1 = _MockAgent(); - final agent2 = _MockAgent(); - when(agent2.toJson).thenReturn({}); + final agent1 = Agent.test(credits: 1); + final agent2 = Agent.test(credits: 2); final responseData = PurchaseShip201ResponseData( - agent: agent2, + agent: agent2.toOpenApi(), ship: _MockShip(), transaction: _MockShipyardTransaction(), ); @@ -70,8 +66,10 @@ void main() { (invocation) => Future.value(PurchaseShip201Response(data: responseData)), ); - final fs = MemoryFileSystem.test(); - final agentCache = AgentCache(agent1, fs: fs); + final db = _MockDatabase(); + registerFallbackValue(agent1); + when(() => db.upsertAgent(any())).thenAnswer((_) async {}); + final agentCache = AgentCache(agent1, db); final shipyardSymbol = WaypointSymbol.fromString('S-A-Y'); const shipType = ShipType.PROBE; await purchaseShip( @@ -305,10 +303,10 @@ void main() { final shipCache = _MockShipCache(); final logger = _MockLogger(); - final agent = _MockAgent(); - when(() => agent.credits).thenReturn(100000); + final agent = Agent.test(); final agentCache = _MockAgentCache(); when(() => agentCache.agent).thenReturn(agent); + when(() => agentCache.updateAgent(agent)).thenAnswer((_) async {}); final marketTransaction = MarketTransaction( waypointSymbol: waypointSymbol.waypoint, shipSymbol: shipSymbol.symbol, @@ -335,7 +333,7 @@ void main() { (invocation) => Future.value( RefuelShip200Response( data: RefuelShip200ResponseData( - agent: agent, + agent: agent.toOpenApi(), transaction: marketTransaction, fuel: ShipFuel(capacity: 1000, current: 1000), ), @@ -538,10 +536,10 @@ void main() { final fleetApi = _MockFleetApi(); when(() => api.fleet).thenReturn(fleetApi); final logger = _MockLogger(); - final agent = _MockAgent(); - when(() => agent.credits).thenReturn(100000); + final agent = Agent.test(); final agentCache = _MockAgentCache(); when(() => agentCache.agent).thenReturn(agent); + when(() => agentCache.updateAgent(agent)).thenAnswer((_) async {}); final db = _MockDatabase(); final market = _MockMarket(); when(() => market.tradeGoods).thenReturn([ @@ -596,7 +594,7 @@ void main() { (invocation) => Future.value( SellCargo201Response( data: SellCargo201ResponseData( - agent: agent, + agent: agent.toOpenApi(), cargo: ShipCargo(capacity: 10, units: 0), transaction: MarketTransaction( waypointSymbol: 'S-A-W', @@ -792,9 +790,9 @@ void main() { ), ); final agentCache = _MockAgentCache(); - final agent = _MockAgent(); - when(() => agent.credits).thenReturn(100000); + final agent = Agent.test(); when(() => agentCache.agent).thenReturn(agent); + when(() => agentCache.updateAgent(agent)).thenAnswer((_) async {}); final logger = _MockLogger(); @@ -805,7 +803,7 @@ void main() { AcceptContract200Response( data: AcceptContract200ResponseData( contract: contract, - agent: _MockAgent(), + agent: agent.toOpenApi(), ), ), ), @@ -845,11 +843,11 @@ void main() { when(() => shipNav.status).thenReturn(shipNavStatus); final shipCache = _MockShipCache(); final logger = _MockLogger(); - final agent = _MockAgent(); - when(() => agent.credits).thenReturn(10000000); + final agent = Agent.test(credits: 10000000); final agentCache = _MockAgentCache(); when(() => agentCache.agent).thenReturn(agent); final now = DateTime(2021); + when(() => agentCache.adjustCredits(any())).thenAnswer((_) async {}); when( () => fleetApi.jumpShip( @@ -876,7 +874,7 @@ void main() { totalPrice: 10000, timestamp: now, ), - agent: agent, + agent: agent.toOpenApi(), ), ), ), diff --git a/packages/db/lib/agent.dart b/packages/db/lib/agent.dart new file mode 100644 index 00000000..7c2e3098 --- /dev/null +++ b/packages/db/lib/agent.dart @@ -0,0 +1,61 @@ +import 'package:db/query.dart'; +import 'package:types/types.dart'; + +/// Query to get an agent by symbol. +Query agentBySymbolQuery(String agentSymbol) => Query( + 'SELECT * FROM agent WHERE symbol = @symbol', + parameters: { + 'symbol': agentSymbol, + }, + ); + +/// Convert an agent to a map of column values. +Map agentToColumnMap(Agent agent) => { + 'symbol': agent.symbol, + 'headquarters': agent.headquarters, + 'credits': agent.credits, + 'starting_faction': agent.startingFaction, + 'ship_count': agent.shipCount, + 'account_id': agent.accountId, + }; + +/// Convert a map of column values to an agent. +Agent agentFromColumnMap(Map values) { + return Agent( + symbol: values['symbol'] as String, + headquarters: WaypointSymbol.fromString(values['headquarters'] as String), + credits: values['credits'] as int, + startingFaction: + FactionSymbol.fromJson(values['starting_faction'] as String)!, + shipCount: values['ship_count'] as int, + accountId: values['account_id'] as String?, + ); +} + +/// Update the given agent in the database. +Query upsertAgentQuery(Agent agent) => Query( + ''' + INSERT INTO agent ( + symbol, + headquarters, + credits, + starting_faction, + ship_count, + account_id + ) VALUES ( + @symbol, + @headquarters, + @credits, + @starting_faction, + @ship_count, + @account_id + ) + ON DUPLICATE KEY UPDATE + headquarters = @headquarters, + credits = @credits, + starting_faction = @starting_faction, + ship_count = @ship_count, + account_id = @account_id + ''', + parameters: agentToColumnMap(agent), + ); diff --git a/packages/db/lib/db.dart b/packages/db/lib/db.dart index f3062031..44c857fa 100644 --- a/packages/db/lib/db.dart +++ b/packages/db/lib/db.dart @@ -1,3 +1,4 @@ +import 'package:db/agent.dart'; import 'package:db/chart.dart'; import 'package:db/config.dart'; import 'package:db/construction.dart'; @@ -249,4 +250,16 @@ class Database { final query = getResponseForRequestQuery(requestId); return queryOne(query, responseRecordFromColumnMap); } + + /// Get the agent from the database. + Future getAgent({required String symbol}) async { + final query = agentBySymbolQuery(symbol); + return queryOne(query, agentFromColumnMap); + } + + /// Update the given agent in the database. + Future upsertAgent(Agent agent) async { + final query = upsertAgentQuery(agent); + await insertOne(query); + } } diff --git a/packages/db/scripts/init_db.sh b/packages/db/scripts/init_db.sh index 832addcc..7442b968 100755 --- a/packages/db/scripts/init_db.sh +++ b/packages/db/scripts/init_db.sh @@ -25,4 +25,5 @@ psql -U postgres -d $DB_NAME -f sql/tables/05_faction.sql psql -U postgres -d $DB_NAME -f sql/tables/06_behavior.sql psql -U postgres -d $DB_NAME -f sql/tables/07_extraction.sql psql -U postgres -d $DB_NAME -f sql/tables/08_construction.sql -psql -U postgres -d $DB_NAME -f sql/tables/09_charting.sql \ No newline at end of file +psql -U postgres -d $DB_NAME -f sql/tables/09_charting.sql +psql -U postgres -d $DB_NAME -f sql/tables/10_agent.sql \ No newline at end of file diff --git a/packages/db/sql/flows/drop_tables.sql b/packages/db/sql/flows/drop_tables.sql index 3d0ba1a7..7feee2dc 100644 --- a/packages/db/sql/flows/drop_tables.sql +++ b/packages/db/sql/flows/drop_tables.sql @@ -6,4 +6,5 @@ DROP TABLE IF EXISTS "faction_" CASCADE; DROP TABLE IF EXISTS "behavior_" CASCADE; DROP TABLE IF EXISTS "extraction_" CASCADE; DROP TABLE IF EXISTS "construction_" CASCADE; -DROP TABLE IF EXISTS "charting_" CASCADE; \ No newline at end of file +DROP TABLE IF EXISTS "charting_" CASCADE; +DROP TABLE IF EXISTS "agent_" CASCADE; \ No newline at end of file diff --git a/packages/db/sql/tables/10_agent.sql b/packages/db/sql/tables/10_agent.sql new file mode 100644 index 00000000..3fd72d99 --- /dev/null +++ b/packages/db/sql/tables/10_agent.sql @@ -0,0 +1,15 @@ +-- Describes an agent. +CREATE TABLE IF NOT EXISTS "agent_" ( + -- account id for this agent, only visible for your own agent. + "account_id" text, + -- The symbol of the agent. + "symbol" text NOT NULL, + -- The waypoint symbol of where the agent starts. + "headquarters" text NOT NULL, + -- How many credits the agent has. + "credits" int NOT NULL, + -- The faction of the agent. + "starting_faction" text NOT NULL, + -- How many ships the agent has. + "shipCount" int NOT NULL, +); diff --git a/packages/types/lib/agent.dart b/packages/types/lib/agent.dart new file mode 100644 index 00000000..7ac8269f --- /dev/null +++ b/packages/types/lib/agent.dart @@ -0,0 +1,98 @@ +import 'package:equatable/equatable.dart'; +import 'package:openapi/api.dart' as openapi; +import 'package:types/types.dart'; + +/// An agent in the game. +class Agent extends Equatable { + /// Creates a new agent. + const Agent({ + required this.symbol, + required this.headquarters, + required this.credits, + required this.startingFaction, + required this.shipCount, + this.accountId, + }); + + /// Creates a test agent. + Agent.test({ + this.symbol = 'A', + WaypointSymbol? headquarters, + this.credits = 1000000, + FactionSymbol? startingFaction, + this.shipCount = 1, + this.accountId, + }) : headquarters = headquarters ?? WaypointSymbol.fromString('S-A-W'), + startingFaction = startingFaction ?? FactionSymbol.AEGIS; + + /// Creates an agent from an OpenAPI agent. + factory Agent.fromOpenApi(openapi.Agent agent) { + return Agent( + symbol: agent.symbol, + headquarters: WaypointSymbol.fromString(agent.headquarters), + credits: agent.credits, + startingFaction: FactionSymbol.fromJson(agent.startingFaction)!, + shipCount: agent.shipCount, + accountId: agent.accountId, + ); + } + + /// The symbol of the agent. + final String symbol; + + /// The symbol of the waypoint that is the agent's headquarters. + final WaypointSymbol headquarters; + + /// The amount of credits the agent has. + final int credits; + + /// The faction the agent started with. + final FactionSymbol startingFaction; + + /// The number of ships the agent has. + final int shipCount; + + /// The account ID of the agent. + final String? accountId; + + @override + List get props => [ + symbol, + headquarters, + credits, + startingFaction, + shipCount, + accountId, + ]; + + /// Copies this agent with some changes. + Agent copyWith({ + String? symbol, + WaypointSymbol? headquarters, + int? credits, + FactionSymbol? startingFaction, + int? shipCount, + String? accountId, + }) { + return Agent( + symbol: symbol ?? this.symbol, + headquarters: headquarters ?? this.headquarters, + credits: credits ?? this.credits, + startingFaction: startingFaction ?? this.startingFaction, + shipCount: shipCount ?? this.shipCount, + accountId: accountId ?? this.accountId, + ); + } + + /// Converts this agent to an OpenAPI agent. + openapi.Agent toOpenApi() { + return openapi.Agent( + symbol: symbol, + headquarters: headquarters.waypoint, + credits: credits, + startingFaction: startingFaction.value, + shipCount: shipCount, + accountId: accountId, + ); + } +} diff --git a/packages/types/lib/api.dart b/packages/types/lib/api.dart index 2e1f179d..226db8c4 100644 --- a/packages/types/lib/api.dart +++ b/packages/types/lib/api.dart @@ -4,10 +4,10 @@ import 'dart:math'; import 'package:collection/collection.dart'; import 'package:equatable/equatable.dart'; import 'package:meta/meta.dart'; -import 'package:openapi/api.dart' hide System, SystemWaypoint, Waypoint; +import 'package:openapi/api.dart' hide Agent, System, SystemWaypoint, Waypoint; import 'package:types/mount.dart'; -export 'package:openapi/api.dart' hide System, SystemWaypoint, Waypoint; +export 'package:openapi/api.dart' hide Agent, System, SystemWaypoint, Waypoint; /// The default implementation of getNow for production. /// Used for tests for overriding the current time. @@ -717,16 +717,6 @@ extension FactionUtils on Faction { SystemSymbol get headquartersSystemSymbol => headquartersSymbol.system; } -/// Extensions onto Agent to make it easier to work with. -extension AgentUtils on Agent { - /// Returns the WaypointSymbol for the agent headquarters. - WaypointSymbol get headquartersSymbol => - WaypointSymbol.fromString(headquarters); - - /// Returns the SystemSymbol for the agent headquarters. - SystemSymbol get headquartersSystemSymbol => headquartersSymbol.system; -} - /// Compute the trade symbol for the given mount symbol. /// TradeSymbols are a superset of ShipMountSymbols so this should never fail. TradeSymbol tradeSymbolForMountSymbol(ShipMountSymbolEnum mountSymbol) { diff --git a/packages/types/lib/types.dart b/packages/types/lib/types.dart index c78d4c27..2b80ffdf 100644 --- a/packages/types/lib/types.dart +++ b/packages/types/lib/types.dart @@ -1,3 +1,4 @@ +export 'agent.dart'; export 'api.dart'; export 'behavior.dart'; export 'construction.dart'; diff --git a/packages/types/test/api_test.dart b/packages/types/test/api_test.dart index 671153a7..373006ca 100644 --- a/packages/types/test/api_test.dart +++ b/packages/types/test/api_test.dart @@ -133,16 +133,6 @@ void main() { ); expect(faction.headquartersSymbol, WaypointSymbol.fromString('S-A-W')); }); - test('AgentUtils', () { - final agent = Agent( - symbol: 'A-1', - headquarters: 'S-A-W', - credits: 0, - shipCount: 0, - startingFaction: FactionSymbol.AEGIS.value, - ); - expect(agent.headquartersSymbol, WaypointSymbol.fromString('S-A-W')); - }); test('CargoUtils', () { final cargo = ShipCargo( capacity: 100,