) and # might have undocked while we were waiting for retry in which case station data is unreliable data2.get('lastSystem', {}).get('name') == monitor.system and data2.get('lastStarport', {}).get('name') == monitor.station): data = data2 if args.s: if data['lastStarport'].get('ships', {}).get('shipyard_list'): shipyard.export(data, args.s) elif not args.j and monitor.stationservices and 'Shipyard' in monitor.stationservices: sys.stderr.write("Failed to get shipyard data\n") else: sys.stderr.write("Station doesn't have a shipyard\n") if args.n: try: eddn_sender = eddn.EDDN(None) eddn_sender.export_commodities(data, monitor.is_beta) eddn_sender.export_outfitting(data, monitor.is_beta) eddn_sender.export_shipyard(data, monitor.is_beta) except Exception as e: sys.stderr.write("Failed to send data to EDDN: %s\n" % unicode(e).encode('ascii', 'replace')) sys.exit(EXIT_SUCCESS) except companion.ServerError as e: sys.stderr.write('Server is down\n') sys.exit(EXIT_SERVER) except companion.SKUError as e: sys.stderr.write('Server SKU problem\n') sys.exit(EXIT_SERVER)
def main(): """Run the main code of the program.""" try: # arg parsing parser = argparse.ArgumentParser( prog=appcmdname, description='Prints the current system and station (if docked) to stdout and optionally writes player ' 'status, ship locations, ship loadout and/or station data to file. ' 'Requires prior setup through the accompanying GUI app.' ) parser.add_argument('-v', '--version', help='print program version and exit', action='store_const', const=True) group_loglevel = parser.add_mutually_exclusive_group() group_loglevel.add_argument('--loglevel', metavar='loglevel', help='Set the logging loglevel to one of: ' 'CRITICAL, ERROR, WARNING, INFO, DEBUG, TRACE', ) group_loglevel.add_argument('--trace', help='Set the Debug logging loglevel to TRACE', action='store_true', ) parser.add_argument('-a', metavar='FILE', help='write ship loadout to FILE in Companion API json format') parser.add_argument('-e', metavar='FILE', help='write ship loadout to FILE in E:D Shipyard plain text format') parser.add_argument('-l', metavar='FILE', help='write ship locations to FILE in CSV format') parser.add_argument('-m', metavar='FILE', help='write station commodity market data to FILE in CSV format') parser.add_argument('-o', metavar='FILE', help='write station outfitting data to FILE in CSV format') parser.add_argument('-s', metavar='FILE', help='write station shipyard data to FILE in CSV format') parser.add_argument('-t', metavar='FILE', help='write player status to FILE in CSV format') parser.add_argument('-d', metavar='FILE', help='write raw JSON data to FILE') parser.add_argument('-n', action='store_true', help='send data to EDDN') parser.add_argument('-p', metavar='CMDR', help='Returns data from the specified player account') parser.add_argument('-j', help=argparse.SUPPRESS) # Import JSON dump args = parser.parse_args() if args.version: updater = Updater(provider='internal') newversion: Optional[EDMCVersion] = updater.check_appcast() if newversion: print(f'{appversion} ({newversion.title!r} is available)') else: print(appversion) return if args.trace: edmclogger.set_channels_loglevel(logging.TRACE) elif args.loglevel: if args.loglevel not in ('CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG', 'TRACE'): print('loglevel must be one of: CRITICAL, ERROR, WARNING, INFO, DEBUG, TRACE', file=sys.stderr) sys.exit(EXIT_ARGS) edmclogger.set_channels_loglevel(args.loglevel) logger.debug(f'Startup v{appversion} : Running on Python v{sys.version}') logger.debug(f'''Platform: {sys.platform} argv[0]: {sys.argv[0]} exec_prefix: {sys.exec_prefix} executable: {sys.executable} sys.path: {sys.path}''' ) log_locale('Initial Locale') if args.j: logger.debug('Import and collate from JSON dump') # Import and collate from JSON dump data = json.load(open(args.j)) config.set('querytime', int(getmtime(args.j))) else: # Get state from latest Journal file logger.debug('Getting state from latest journal file') try: logdir = config.get('journaldir') or config.default_journal_dir logger.debug(f'logdir = "{logdir}"') logfiles = sorted((x for x in os.listdir(logdir) if JOURNAL_RE.search(x)), key=lambda x: x.split('.')[1:]) logfile = join(logdir, logfiles[-1]) logger.debug(f'Using logfile "{logfile}"') with open(logfile, 'r') as loghandle: for line in loghandle: try: monitor.parse_entry(line) except Exception: logger.debug(f'Invalid journal entry {line!r}') except Exception: logger.exception("Can't read Journal file") sys.exit(EXIT_SYS_ERR) if not monitor.cmdr: logger.error('Not available while E:D is at the main menu') sys.exit(EXIT_SYS_ERR) # Get data from Companion API if args.p: logger.debug(f'Attempting to use commander "{args.p}"') cmdrs = config.get('cmdrs') or [] if args.p in cmdrs: idx = cmdrs.index(args.p) else: for idx, cmdr in enumerate(cmdrs): if cmdr.lower() == args.p.lower(): break else: raise companion.CredentialsError() companion.session.login(cmdrs[idx], monitor.is_beta) else: logger.debug(f'Attempting to use commander "{monitor.cmdr}" from Journal File') cmdrs = config.get('cmdrs') or [] if monitor.cmdr not in cmdrs: raise companion.CredentialsError() companion.session.login(monitor.cmdr, monitor.is_beta) querytime = int(time()) data = companion.session.station() config.set('querytime', querytime) # Validation if not deep_get(data, 'commander', 'name', default='').strip(): logger.error("No data['command']['name'] from CAPI") sys.exit(EXIT_SERVER) elif not deep_get(data, 'lastSystem', 'name') or \ data['commander'].get('docked') and not \ deep_get(data, 'lastStarport', 'name'): # Only care if docked logger.error("No data['lastSystem']['name'] from CAPI") sys.exit(EXIT_SERVER) elif not deep_get(data, 'ship', 'modules') or not deep_get(data, 'ship', 'name', default=''): logger.error("No data['ship']['modules'] from CAPI") sys.exit(EXIT_SERVER) elif args.j: pass # Skip further validation elif data['commander']['name'] != monitor.cmdr: logger.error(f'Commander "{data["commander"]["name"]}" from CAPI doesn\'t match "{monitor.cmdr}" from Journal') # noqa: E501 sys.exit(EXIT_CREDENTIALS) elif data['lastSystem']['name'] != monitor.system or \ ((data['commander']['docked'] and data['lastStarport']['name'] or None) != monitor.station) or \ data['ship']['id'] != monitor.state['ShipID'] or \ data['ship']['name'].lower() != monitor.state['ShipType']: logger.error('Mismatch(es) between CAPI and Journal for at least one of: StarSystem, Last Star Port, Ship ID or Ship Name/Type') # noqa: E501 sys.exit(EXIT_LAGGING) # stuff we can do when not docked if args.d: logger.debug(f'Writing raw JSON data to "{args.d}"') out = json.dumps(data, ensure_ascii=False, indent=2, sort_keys=True, separators=(',', ': ')) with open(args.d, 'wb') as f: f.write(out.encode("utf-8")) if args.a: logger.debug(f'Writing Ship Loadout in Companion API JSON format to "{args.a}"') loadout.export(data, args.a) if args.e: logger.debug(f'Writing Ship Loadout in ED Shipyard plain text format to "{args.e}"') edshipyard.export(data, args.e) if args.l: logger.debug(f'Writing Ship Locations in CSV format to "{args.l}"') stats.export_ships(data, args.l) if args.t: logger.debug(f'Writing Player Status in CSV format to "{args.t}"') stats.export_status(data, args.t) if data['commander'].get('docked'): print(f'{deep_get(data, "lastSystem", "name", default="Unknown")},' f'{deep_get(data, "lastStarport", "name", default="Unknown")}' ) else: print(deep_get(data, 'lastSystem', 'name', default='Unknown')) if (args.m or args.o or args.s or args.n or args.j): if not data['commander'].get('docked'): logger.error("Can't use -m, -o, -s, -n or -j because you're not currently docked!") return elif not deep_get(data, 'lastStarport', 'name'): logger.error("No data['lastStarport']['name'] from CAPI") sys.exit(EXIT_LAGGING) # Ignore possibly missing shipyard info elif not (data['lastStarport'].get('commodities') or data['lastStarport'].get('modules')): logger.error("No commodities or outfitting (modules) in CAPI data") return else: return # Finally - the data looks sane and we're docked at a station if args.j: logger.debug('Importing data from the CAPI return...') # Collate from JSON dump collate.addcommodities(data) collate.addmodules(data) collate.addships(data) if args.m: logger.debug(f'Writing Station Commodity Market Data in CSV format to "{args.m}"') if data['lastStarport'].get('commodities'): # Fixup anomalies in the commodity data fixed = companion.fixup(data) commodity.export(fixed, COMMODITY_DEFAULT, args.m) else: logger.error("Station doesn't have a market") if args.o: if data['lastStarport'].get('modules'): logger.debug(f'Writing Station Outfitting in CSV format to "{args.o}"') outfitting.export(data, args.o) else: logger.error("Station doesn't supply outfitting") if (args.s or args.n) and not args.j and not \ data['lastStarport'].get('ships') and data['lastStarport']['services'].get('shipyard'): # Retry for shipyard sleep(SERVER_RETRY) new_data = companion.session.station() # might have undocked while we were waiting for retry in which case station data is unreliable if new_data['commander'].get('docked') and \ deep_get(new_data, 'lastSystem', 'name') == monitor.system and \ deep_get(new_data, 'lastStarport', 'name') == monitor.station: data = new_data if args.s: if deep_get(data, 'lastStarport', 'ships', 'shipyard_list'): logger.debug(f'Writing Station Shipyard in CSV format to "{args.s}"') shipyard.export(data, args.s) elif not args.j and monitor.stationservices and 'Shipyard' in monitor.stationservices: logger.error('Failed to get shipyard data') else: logger.error("Station doesn't have a shipyard") if args.n: try: eddn_sender = eddn.EDDN(None) logger.debug('Sending Market, Outfitting and Shipyard data to EDDN...') eddn_sender.export_commodities(data, monitor.is_beta) eddn_sender.export_outfitting(data, monitor.is_beta) eddn_sender.export_shipyard(data, monitor.is_beta) except Exception: logger.exception('Failed to send data to EDDN') except companion.ServerError: logger.error('Frontier CAPI Server returned an error') sys.exit(EXIT_SERVER) except companion.SKUError: logger.error('Frontier CAPI Server SKU problem') sys.exit(EXIT_SERVER) except companion.CredentialsError: logger.error('Frontier CAPI Server: Invalid Credentials') sys.exit(EXIT_CREDENTIALS)
def __init__(self, master): self.holdofftime = config.getint('querytime') + companion.holdoff self.session = companion.Session() self.eddn = eddn.EDDN(self) self.w = master self.w.title(applongname) self.w.rowconfigure(0, weight=1) self.w.columnconfigure(0, weight=1) plug.load_plugins(master) if platform != 'darwin': if platform == 'win32': self.w.wm_iconbitmap(default='EDMarketConnector.ico') else: from PIL import Image, ImageTk self.w.tk.call('wm', 'iconphoto', self.w, '-default', ImageTk.PhotoImage(Image.open("EDMarketConnector.png"))) self.theme_icon = tk.PhotoImage(data = 'R0lGODlhFAAQAMZQAAoKCQoKCgsKCQwKCQsLCgwLCg4LCQ4LCg0MCg8MCRAMCRANChINCREOChIOChQPChgQChgRCxwTCyYVCSoXCS0YCTkdCTseCT0fCTsjDU0jB0EnDU8lB1ElB1MnCFIoCFMoCEkrDlkqCFwrCGEuCWIuCGQvCFs0D1w1D2wyCG0yCF82D182EHE0CHM0CHQ1CGQ5EHU2CHc3CHs4CH45CIA6CIE7CJdECIdLEolMEohQE5BQE41SFJBTE5lUE5pVE5RXFKNaFKVbFLVjFbZkFrxnFr9oFsNqFsVrF8RsFshtF89xF9NzGNh1GNl2GP+KG////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////yH5BAEKAH8ALAAAAAAUABAAAAeegAGCgiGDhoeIRDiIjIZGKzmNiAQBQxkRTU6am0tPCJSGShuSAUcLoIIbRYMFra4FAUgQAQCGJz6CDQ67vAFJJBi0hjBBD0w9PMnJOkAiJhaIKEI7HRoc19ceNAolwbWDLD8uAQnl5ga1I9CHEjEBAvDxAoMtFIYCBy+kFDKHAgM3ZtgYSLAGgwkp3pEyBOJCC2ELB31QATGioAoVAwEAOw==') self.theme_minimize = tk.BitmapImage(data = '#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x3f,\n 0xfc, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };\n') self.theme_close = tk.BitmapImage(data = '#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n 0x00, 0x00, 0x00, 0x00, 0x0c, 0x30, 0x1c, 0x38, 0x38, 0x1c, 0x70, 0x0e,\n 0xe0, 0x07, 0xc0, 0x03, 0xc0, 0x03, 0xe0, 0x07, 0x70, 0x0e, 0x38, 0x1c,\n 0x1c, 0x38, 0x0c, 0x30, 0x00, 0x00, 0x00, 0x00 };\n') frame = tk.Frame(self.w, name=appname.lower()) frame.grid(sticky=tk.NSEW) frame.columnconfigure(1, weight=1) self.cmdr_label = tk.Label(frame) self.ship_label = tk.Label(frame) self.cmdr_label.grid(row=1, column=0, sticky=tk.W) self.ship_label.grid(row=2, column=0, sticky=tk.W) self.cmdr = tk.Label(frame, anchor=tk.W) self.ship = HyperlinkLabel(frame, url = self.shipyard_url) self.cmdr.grid(row=1, column=1, sticky=tk.EW) self.ship.grid(row=2, column=1, sticky=tk.EW) for plugin in plug.PLUGINS: appitem = plugin.get_app(frame) if appitem: if isinstance(appitem, tuple) and len(appitem)==2: row = frame.grid_size()[1] appitem[0].grid(row=row, column=0, sticky=tk.W) appitem[1].grid(row=row, column=1, sticky=tk.EW) else: appitem.grid(columnspan=2, sticky=tk.W) self.button = ttk.Button(frame, text=_('Update'), width=28, default=tk.ACTIVE, state=tk.DISABLED) # Update button in main window self.theme_button = tk.Label(frame, width = platform == 'darwin' and 32 or 28, state=tk.DISABLED) self.status = tk.Label(frame, name='status', anchor=tk.W) row = frame.grid_size()[1] self.button.grid(row=row, columnspan=2, sticky=tk.NSEW) self.theme_button.grid(row=row, columnspan=2, sticky=tk.NSEW) theme.register_alternate((self.button, self.theme_button, self.theme_button), {'row':row, 'columnspan':2, 'sticky':tk.NSEW}) self.status.grid(columnspan=2, sticky=tk.EW) self.button.bind('<Button-1>', self.getandsend) theme.button_bind(self.theme_button, self.getandsend) for child in frame.winfo_children(): child.grid_configure(padx=5, pady=(platform!='win32' and 2 or 0)) self.menubar = tk.Menu() if platform=='darwin': # Can't handle (de)iconify if topmost is set, so suppress iconify button # http://wiki.tcl.tk/13428 and p15 of https://developer.apple.com/legacy/library/documentation/Carbon/Conceptual/HandlingWindowsControls/windowscontrols.pdf root.call('tk::unsupported::MacWindowStyle', 'style', root, 'document', 'closeBox resizable') # https://www.tcl.tk/man/tcl/TkCmd/menu.htm self.system_menu = tk.Menu(self.menubar, name='apple') self.system_menu.add_command(command=lambda:self.w.call('tk::mac::standardAboutPanel')) self.system_menu.add_command(command=lambda:self.updater.checkForUpdates()) self.menubar.add_cascade(menu=self.system_menu) self.file_menu = tk.Menu(self.menubar, name='file') self.file_menu.add_command(command=self.save_raw) self.menubar.add_cascade(menu=self.file_menu) self.edit_menu = tk.Menu(self.menubar, name='edit') self.edit_menu.add_command(accelerator='Command-c', state=tk.DISABLED, command=self.copy) self.menubar.add_cascade(menu=self.edit_menu) self.w.bind('<Command-c>', self.copy) self.view_menu = tk.Menu(self.menubar, name='view') self.view_menu.add_command(command=lambda:stats.StatsDialog(self)) self.menubar.add_cascade(menu=self.view_menu) window_menu = tk.Menu(self.menubar, name='window') self.menubar.add_cascade(menu=window_menu) self.help_menu = tk.Menu(self.menubar, name='help') self.w.createcommand("::tk::mac::ShowHelp", self.help_general) self.help_menu.add_command(command=self.help_releases) self.menubar.add_cascade(menu=self.help_menu) self.w['menu'] = self.menubar # https://www.tcl.tk/man/tcl/TkCmd/tk_mac.htm self.w.call('set', 'tk::mac::useCompatibilityMetrics', '0') self.w.createcommand('tkAboutDialog', lambda:self.w.call('tk::mac::standardAboutPanel')) self.w.createcommand("::tk::mac::Quit", self.onexit) self.w.createcommand("::tk::mac::ShowPreferences", lambda:prefs.PreferencesDialog(self.w, self.postprefs)) self.w.createcommand("::tk::mac::ReopenApplication", self.w.deiconify) # click on app in dock = restore self.w.protocol("WM_DELETE_WINDOW", self.w.withdraw) # close button shouldn't quit app self.w.resizable(tk.FALSE, tk.FALSE) # Can't be only resizable on one axis else: self.file_menu = self.view_menu = tk.Menu(self.menubar, tearoff=tk.FALSE) self.file_menu.add_command(command=lambda:stats.StatsDialog(self)) self.file_menu.add_command(command=self.save_raw) self.file_menu.add_command(command=lambda:prefs.PreferencesDialog(self.w, self.postprefs)) self.file_menu.add_separator() self.file_menu.add_command(command=self.onexit) self.menubar.add_cascade(menu=self.file_menu) self.edit_menu = tk.Menu(self.menubar, tearoff=tk.FALSE) self.edit_menu.add_command(accelerator='Ctrl+C', state=tk.DISABLED, command=self.copy) self.menubar.add_cascade(menu=self.edit_menu) self.help_menu = tk.Menu(self.menubar, tearoff=tk.FALSE) self.help_menu.add_command(command=self.help_general) self.help_menu.add_command(command=self.help_releases) self.help_menu.add_command(command=lambda:self.updater.checkForUpdates()) self.menubar.add_cascade(menu=self.help_menu) if platform == 'win32': # Must be added after at least one "real" menu entry self.always_ontop = tk.BooleanVar(value = config.getint('always_ontop')) self.system_menu = tk.Menu(self.menubar, name='system', tearoff=tk.FALSE) self.system_menu.add_separator() self.system_menu.add_checkbutton(label=_('Always on top'), variable = self.always_ontop, command=self.ontop_changed) # Appearance setting self.menubar.add_cascade(menu=self.system_menu) self.w.bind('<Control-c>', self.copy) self.w.protocol("WM_DELETE_WINDOW", self.onexit) theme.register(self.menubar) # menus and children aren't automatically registered theme.register(self.file_menu) theme.register(self.edit_menu) theme.register(self.help_menu) # Alternate title bar and menu for dark theme self.theme_menubar = tk.Frame(frame) self.theme_menubar.columnconfigure(2, weight=1) theme_titlebar = tk.Label(self.theme_menubar, text=applongname, image=self.theme_icon, cursor='fleur', anchor=tk.W, compound=tk.LEFT) theme_titlebar.grid(columnspan=3, padx=2, sticky=tk.NSEW) self.drag_offset = None theme_titlebar.bind('<Button-1>', self.drag_start) theme_titlebar.bind('<B1-Motion>', self.drag_continue) theme_titlebar.bind('<ButtonRelease-1>', self.drag_end) if platform == 'win32': # Can't work out how to deiconify on Linux theme_minimize = tk.Label(self.theme_menubar, image=self.theme_minimize) theme_minimize.grid(row=0, column=3, padx=2) theme.button_bind(theme_minimize, self.oniconify, image=self.theme_minimize) theme_close = tk.Label(self.theme_menubar, image=self.theme_close) theme_close.grid(row=0, column=4, padx=2) theme.button_bind(theme_close, self.onexit, image=self.theme_close) self.theme_file_menu = tk.Label(self.theme_menubar, anchor=tk.W) self.theme_file_menu.grid(row=1, column=0, padx=5, sticky=tk.W) theme.button_bind(self.theme_file_menu, lambda e: self.file_menu.tk_popup(e.widget.winfo_rootx(), e.widget.winfo_rooty() + e.widget.winfo_height())) self.theme_edit_menu = tk.Label(self.theme_menubar, anchor=tk.W) self.theme_edit_menu.grid(row=1, column=1, sticky=tk.W) theme.button_bind(self.theme_edit_menu, lambda e: self.edit_menu.tk_popup(e.widget.winfo_rootx(), e.widget.winfo_rooty() + e.widget.winfo_height())) self.theme_help_menu = tk.Label(self.theme_menubar, anchor=tk.W) self.theme_help_menu.grid(row=1, column=2, sticky=tk.W) theme.button_bind(self.theme_help_menu, lambda e: self.help_menu.tk_popup(e.widget.winfo_rootx(), e.widget.winfo_rooty() + e.widget.winfo_height())) theme.register(self.theme_minimize) # images aren't automatically registered theme.register(self.theme_close) self.blank_menubar = tk.Frame(frame) tk.Label(self.blank_menubar).grid() tk.Label(self.blank_menubar).grid() theme.register_alternate((self.menubar, self.theme_menubar, self.blank_menubar), {'row':0, 'columnspan':2, 'sticky':tk.NSEW}) self.w.resizable(tk.TRUE, tk.FALSE) # update geometry if config.get('geometry'): match = re.match('\+([\-\d]+)\+([\-\d]+)', config.get('geometry')) if match: if platform == 'darwin': if int(match.group(2)) >= 0: # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 self.w.geometry(config.get('geometry')) elif platform == 'win32': # Check that the titlebar will be at least partly on screen import ctypes from ctypes.wintypes import POINT # https://msdn.microsoft.com/en-us/library/dd145064 MONITOR_DEFAULTTONULL = 0 if ctypes.windll.user32.MonitorFromPoint(POINT(int(match.group(1)) + 16, int(match.group(2)) + 16), MONITOR_DEFAULTTONULL): self.w.geometry(config.get('geometry')) else: self.w.geometry(config.get('geometry')) self.w.attributes('-topmost', config.getint('always_ontop') and 1 or 0) theme.register(frame) theme.apply(self.w) self.w.bind('<Map>', self.onmap) # Special handling for overrideredict self.w.bind('<Enter>', self.onenter) # Special handling for transparency self.w.bind('<FocusIn>', self.onenter) # " self.w.bind('<Leave>', self.onleave) # " self.w.bind('<FocusOut>', self.onleave) # " self.w.bind('<Return>', self.getandsend) self.w.bind('<KP_Enter>', self.getandsend) self.w.bind_all('<<Invoke>>', self.getandsend) # Hotkey monitoring self.w.bind_all('<<JournalEvent>>', self.journal_event) # Journal monitoring self.w.bind_all('<<DashboardEvent>>', self.dashboard_event) # Dashboard monitoring self.w.bind_all('<<PluginError>>', self.plugin_error) # Statusbar self.w.bind_all('<<Quit>>', self.onexit) # Updater # Load updater after UI creation (for WinSparkle) import update self.updater = update.Updater(self.w) if not getattr(sys, 'frozen', False): self.updater.checkForUpdates() # Sparkle / WinSparkle does this automatically for packaged apps try: config.get_password('') # Prod SecureStorage on Linux to initialise except RuntimeError: pass # Migration from <= 2.25 if not config.get('cmdrs') and config.get('username') and config.get('password'): try: self.session.login(config.get('username'), config.get('password'), False) data = self.session.profile() prefs.migrate(data['commander']['name']) except: if __debug__: print_exc() config.delete('username') config.delete('password') config.delete('logdir') # Check system time drift = abs(time() - self.eddn.time()) if drift > DRIFT_THRESHOLD: tkMessageBox.showerror(applongname, _('This app requires accurate timestamps.') + '\n' + # Error message shown if system time is wrong (TZ_THRESHOLD < drift < CLOCK_THRESHOLD and _("Check your system's Time Zone setting.") or # Error message shown if system time is wrong _("Check your system's Date and Time settings.")), # Error message shown if system time is wrong parent = self.w) self.w.destroy() return self.postprefs(False) # Companion login happens in callback from monitor if keyring.get_keyring().priority < 1: self.status['text'] = 'Warning: Storing passwords as text' # Shouldn't happen unless no secure storage on Linux # Try to obtain exclusive lock on journal cache, even if we don't need it yet if not self.eddn.load(): self.status['text'] = 'Error: Is another copy of this app already running?' # Shouldn't happen - don't bother localizing
def Main(): ''' Main function. ''' # Connect to the API and grab all the info! api = companion.EDAPI(debug=args.debug, json_file=args.json_file, login=args.login) # User specified --export. Print JSON and exit. if args.export: with open(args.export, 'w') as outfile: json.dump(api.profile, outfile, indent=4, sort_keys=True) sys.exit() # Colors c = ansiColors() # User specified the --keys option. Use this to display some subset of the # API response and exit. if args.keys is not None: # A little legend. for key in args.keys[0]: print(key, end="->") print() # Start a the root ref = api.profile # Try to walk the tree for key in args.keys[0]: try: ref = ref[key] except: print("key:", key) print("not found. Contents at previous key:") try: pprint(sorted(ref.keys())) except: pprint(ref) sys.exit(1) # Print whatever we found here. try: if args.tree: pprint(ref) else: pprint(sorted(ref.keys())) except: pprint(ref) # Exit without doing anything else. sys.exit() # Sanity check that we are docked if not api.profile['commander']['docked']: print(c.WARNING + 'Commander not docked.' + c.ENDC) print(c.FAIL + 'Aborting!' + c.ENDC) sys.exit(1) # Print the commander profile print('Commander:', c.OKGREEN + api.profile['commander']['name'] + c.ENDC) print('Credits : {:>12,d}'.format(api.profile['commander']['credits'])) print('Debt : {:>12,d}'.format(api.profile['commander']['debt'])) print('Capacity : {} tons'.format( api.profile['ship']['cargo']['capacity'])) # NOQA print("+------------+------------------+---+") # NOQA print("| Rank Type | Rank Name | # |") # NOQA print("+------------+------------------+---+") # NOQA for rankType in sorted(api.profile['commander']['rank']): rank = api.profile['commander']['rank'][rankType] if rankType in rank_names: try: rankName = rank_names[rankType][rank] except: rankName = "Rank " + str(rank) else: rankName = '' print("| {:>10} | {:>16} | {:1} |".format( rankType, rankName, rank, )) print("+------------+------------------+---+") # NOQA print('Docked:', api.profile['commander']['docked']) system = api.profile['lastSystem']['name'] station = api.profile['lastStarport']['name'] print('System:', c.OKBLUE + system + c.ENDC) print('Station:', c.OKBLUE + station + c.ENDC) # Write out an environment file. if args.vars: print('Writing {}...'.format(api._envfile)) with open(api._envfile, "w") as myfile: myfile.write('export TDFROM="{}/{}"\n'.format( api.profile['lastSystem']['name'], api.profile['lastStarport']['name'])) myfile.write('export TDCREDITS={}\n'.format( api.profile['commander']['credits'])) myfile.write('export TDCAP={}\n'.format( api.profile['ship']['cargo']['capacity'])) # Process the commodities market. eddn_commodities = [] if 'commodities' in api.profile['lastStarport']: def commodity_int(key): try: ret = int(float(commodity[key]) + 0.5) except (ValueError, KeyError): ret = 0 return ret for commodity in api.profile['lastStarport']['commodities']: # Ignore any special categories. if commodity['categoryname'] in cat_ignore: continue # Add it to the EDDN list. if args.eddn: itemEDDN = { "name": commodity['name'], "meanPrice": commodity_int('meanPrice'), "buyPrice": commodity_int('buyPrice'), "stock": commodity_int('stock'), "stockBracket": commodity['stockBracket'], "sellPrice": commodity_int('sellPrice'), "demand": commodity_int('demand'), "demandBracket": commodity['demandBracket'], } if len(commodity['statusFlags']) > 0: itemEDDN["statusFlags"] = commodity['statusFlags'] eddn_commodities.append(itemEDDN) # Process shipyard. eddn_ships = [] if 'ships' in api.profile['lastStarport']: # Ships that can be purchased. if 'shipyard_list' in api.profile['lastStarport']['ships']: for ship in api.profile['lastStarport']['ships'][ 'shipyard_list'].values(): # NOQA # Add to EDDN. eddn_ships.append(ship['name']) # Ships that are restricted. if 'unavailable_list' in api.profile['lastStarport']['ships']: for ship in api.profile['lastStarport']['ships'][ 'unavailable_list']: # NOQA # Add to EDDN. eddn_ships.append(ship['name']) # Process outfitting. eddn_modules = [] if 'modules' in api.profile['lastStarport']: # For EDDN, only add non-commander specific items that can be # purchased. # https://github.com/jamesremuscat/EDDN/wiki for module in api.profile['lastStarport']['modules'].values(): if (module.get('sku', None) in (None, 'ELITE_HORIZONS_V_PLANETARY_LANDINGS') and (module['name'].startswith( ('Hpt_', 'Int_')) or module['name'].find('_Armour_') > 0)): eddn_modules.append(module['name']) # Publish to EDDN if args.eddn: # Open a connection. con = eddn.EDDN(api.profile['commander']['name'], not args.hash, 'EDAPI', __version__) con._debug = args.debug if eddn_commodities: print('Posting commodities to EDDN...') con.publishCommodities(system, station, eddn_commodities) if eddn_ships: print('Posting shipyard to EDDN...') con.publishShipyard(system, station, sorted(eddn_ships)) if eddn_modules: print('Posting outfitting to EDDN...') con.publishOutfitting(system, station, sorted(eddn_modules)) # No errors. return False