diff --git a/AutoCompleter.py b/AutoCompleter.py new file mode 100644 index 0000000..206fd84 --- /dev/null +++ b/AutoCompleter.py @@ -0,0 +1,206 @@ +import threading +import json +import os +import requests +import traceback +from time import sleep +from PlaceHolder import PlaceHolder +import sys + +try: + # Python 2 + from Tkinter import * + import ttk +except ModuleNotFoundError: + # Python 3 + import tkinter as tk + from tkinter import * + +is_py2 = sys.version[0] == '2' +if is_py2: + import Queue as queue +else: + import queue as queue + + +class AutoCompleter(Entry, PlaceHolder): + def __init__(self, parent, placeholder, **kw): + Entry.__init__(self, parent, **kw) + self.var = self["textvariable"] = StringVar() + self.var.traceid = self.var.trace('w', self.changed) + + self.parent = parent + + self.lb = Listbox(self.parent, selectmode=SINGLE, **kw) + self.lb_up = False + self.has_selected = False + self.queue = Queue.Queue() + + PlaceHolder.__init__(self, placeholder) + + # Create right click menu + self.menu = Menu(self.parent, tearoff=0) + self.menu.add_command(label="Cut") + self.menu.add_command(label="Copy") + self.menu.add_command(label="Paste") + + self.bind("", self.keypressed) + self.lb.bind("", self.keypressed) + self.bind('', self.select_all) + self.bind('', self.show_menu) + self.lb.bind("", self.selection) + self.bind("", self.ac_foc_out) + self.lb.bind("", self.ac_foc_out) + + self.update_me() + + def ac_foc_out(self, event): + x,y = self.parent.winfo_pointerxy() + widget_under_cursor = self.parent.winfo_containing(x,y) + if widget_under_cursor != self.lb and widget_under_cursor != self: + self.foc_out() + self.hide_list() + + def show_menu(self, e): + self.foc_in() + w = e.widget + self.menu.entryconfigure("Cut", + command=lambda: w.event_generate("<>")) + self.menu.entryconfigure("Copy", + command=lambda: w.event_generate("<>")) + self.menu.entryconfigure("Paste", + command=lambda: w.event_generate("<>")) + self.menu.tk.call("tk_popup", self.menu, e.x_root, e.y_root) + + def keypressed(self, event): + key=event.keysym + if key == 'Down': + self.down(event.widget.widgetName) + elif key == 'Up': + self.up(event.widget.widgetName) + elif key in ['Return', 'Right']: + if self.lb_up: + self.selection() + elif key == 'Escape' and self.lb_up: + self.hide_list() + + def select_all(self, event): + event.widget.event_generate('<>') + + def changed(self, name, index, mode): + self.set_default_style() + value = self.var.get() + if value.__len__() < 3 and self.lb_up or self.has_selected: + self.hide_list() + self.has_selected = False + else: + t = threading.Thread(target=self.query_systems, args=[value]) + t.start() + + def selection(self, event=None): + if self.lb_up: + self.has_selected = True + self.var.set(self.lb.get(ACTIVE)) + self.hide_list() + self.icursor(END) + + def up(self, widget): + if self.lb_up: + if self.lb.curselection() == (): + index = '0' + else: + index = self.lb.curselection()[0] + if index != '0': + self.lb.selection_clear(first=index) + index = str(int(index)-1) + self.lb.selection_set(first=index) + if widget != "listbox": + self.lb.activate(index) + + def down(self, widget): + if self.lb_up: + if self.lb.curselection() == (): + index = '0' + else: + index = self.lb.curselection()[0] + if int(index+1) != END: + self.lb.selection_clear(first=index) + index = str(int(index+1)) + + self.lb.selection_set(first=index) + if widget != "listbox": + self.lb.activate(index) + else: + self.query_systems() + + def show_results(self, results): + if results: + self.lb.delete(0, END) + for w in results: + self.lb.insert(END,w) + + self.show_list(len(results)) + else: + if self.lb_up: + self.hide_list() + + def show_list(self, height): + self.lb["height"] = height + if not self.lb_up: + info = self.grid_info() + if info: + self.lb.grid(row=int(info["row"])+1, columnspan=2) + self.lb_up = True + + def hide_list(self): + if self.lb_up: + self.lb.grid_remove() + self.lb_up = False + + def query_systems(self, inp): + inp = inp.strip() + if inp != self.placeholder and inp.__len__() >= 3: + url = "https://spansh.co.uk/api/systems?" + try: + results = requests.get(url, + params={'q': inp}, + headers={'User-Agent': "EDMC_SpanshRouter 1.0"}, + timeout=3) + + lista = json.loads(results.content) + if lista: + self.write(lista) + except: + exc_type, exc_value, exc_traceback = sys.exc_info() + lines = traceback.format_exception(exc_type, exc_value, exc_traceback) + sys.stderr.write(''.join('!! ' + line for line in lines)) + + def write(self, lista): + self.queue.put(lista) + + def clear(self): + self.queue.put(None) + + def update_me(self): + try: + while 1: + lista = self.queue.get_nowait() + self.show_results(lista) + self.update_idletasks() + except Queue.Empty: + pass + self.after(100, self.update_me) + + def set_text(self, text): + self.var.trace_vdelete("w", self.var.traceid) + self.set_default_style() + self.delete(0, END) + self.insert(0, text) + self.var.traceid = self.var.trace('w', self.changed) + +if __name__ == '__main__': + root = Tk() + + widget = AutoCompleter(root, "Test") + widget.grid(row=0) + root.mainloop() \ No newline at end of file diff --git a/PlaceHolder.py b/PlaceHolder.py new file mode 100644 index 0000000..380d973 --- /dev/null +++ b/PlaceHolder.py @@ -0,0 +1,51 @@ +from config import config +try: + # Python 2 + from Tkinter import END +except ModuleNotFoundError: + # Python 3 + import tkinter as tk + from tkinter import * + +class PlaceHolder(): + def __init__(self, placeholder, **kw): + self.placeholder = placeholder + self.placeholder_color = "grey" + + self.bind("", self.foc_in) + self.bind("", self.foc_out) + + self.put_placeholder() + + def put_placeholder(self): + self['fg'] = self.placeholder_color + if self.get() != self.placeholder: + self.set_text(self.placeholder) + + def set_text(self, text): + self.set_default_style() + self.delete(0, END) + self.insert(0, text) + + def force_placeholder_color(self): + self['fg'] = self.placeholder_color + + def set_default_style(self): + theme = config.getint('theme') + self['fg'] = config.get('dark_text') if theme else "black" + + def set_error_style(self, error=True): + if error: + self['fg'] = "red" + else: + self.set_default_style() + + def foc_in(self, *args): + if self['fg'] == "red" or self['fg'] == self.placeholder_color: + self.set_default_style() + if self.get() == self.placeholder: + self.delete('0', 'end') + + def foc_out(self, *args): + if not self.get(): + self.put_placeholder() diff --git a/PlaceHolderEntry.py b/PlaceHolderEntry.py new file mode 100644 index 0000000..ad28031 --- /dev/null +++ b/PlaceHolderEntry.py @@ -0,0 +1,15 @@ +from PlaceHolder import PlaceHolder + +try: + # Python 2 + from Tkinter import * +except ModuleNotFoundError: + # Python 3 + import tkinter as tk + from tkinter import * + +class PlaceHolderEntry(Entry, PlaceHolder): + def __init__(self, parent, placeholder, **kw): + Entry.__init__(self, parent, **kw) + self.var = self["textvariable"] = StringVar() + PlaceHolder.__init__(self, placeholder) \ No newline at end of file diff --git a/SpanshRouter.py b/SpanshRouter.py new file mode 100644 index 0000000..9f13e7b --- /dev/null +++ b/SpanshRouter.py @@ -0,0 +1,591 @@ +import os +import sys +import traceback +import csv +import subprocess +import webbrowser +import json +import re +import requests +from time import sleep +from monitor import monitor +from . import AutoCompleter +from . import PlaceHolderEntry +from .updater import SpanshUpdater + +try: + # Python 2 + from Tkinter import * + import tkFileDialog as filedialog + import tkMessageBox as confirmDialog + import ttk +except ModuleNotFoundError: + # Python 3 + import tkinter as tk + from tkinter import * + import tkinter.filedialog as filedialog + import tkinter.messagebox as confirmDialog + +class SpanshRouter(): + def __init__(self, plugin_dir): + version_file = os.path.join(plugin_dir, "version.json") + with open(version_file, 'r') as version_fd: + self.plugin_version = version_fd.read() + + self.update_available = False + self.next_stop = "No route planned" + self.route = [] + self.next_wp_label = "Next waypoint: " + self.jumpcountlbl_txt = "Estimated jumps left: " + self.parent = None + self.plugin_dir = plugin_dir + self.save_route_path = os.path.join(plugin_dir, 'route.csv') + self.offset_file_path = os.path.join(plugin_dir, 'offset') + self.offset = 0 + self.jumps_left = 0 + self.error_txt = tk.StringVar() + self.plot_error = "Error while trying to plot a route, please try again." + self.system_header = "System Name" + self.jumps_header = "Jumps" + + # -- GUI part -- + def init_gui(self, parent): + self.parent = parent + parentwidth = parent.winfo_width() + self.frame = tk.Frame(parent, borderwidth=2) + self.frame.grid(sticky=tk.NSEW, columnspan=2) + + # Route info + self.waypoint_prev_btn = tk.Button(self.frame, text="^", command=self.goto_prev_waypoint) + self.waypoint_btn = tk.Button(self.frame, text=self.next_wp_label + self.next_stop, command=self.copy_waypoint) + self.waypoint_next_btn = tk.Button(self.frame, text="v", command=self.goto_next_waypoint) + self.jumpcounttxt_lbl = tk.Label(self.frame, text=self.jumpcountlbl_txt + str(self.jumps_left)) + self.error_lbl = tk.Label(self.frame, textvariable=self.error_txt) + + # Plotting GUI + self.source_ac = AutoCompleter(self.frame, "Source System", width=30) + self.dest_ac = AutoCompleter(self.frame, "Destination System", width=30) + self.range_entry = PlaceHolderEntry(self.frame, "Range (LY)", width=10) + self.efficiency_slider = tk.Scale(self.frame, from_=1, to=100, orient=tk.HORIZONTAL, label="Efficiency (%)") + self.efficiency_slider.set(60) + self.plot_gui_btn = tk.Button(self.frame, text="Plot route", command=self.show_plot_gui) + self.plot_route_btn = tk.Button(self.frame, text="Calculate", command=self.plot_route) + self.cancel_plot = tk.Button(self.frame, text="Cancel", command=lambda: self.show_plot_gui(False)) + + self.csv_route_btn = tk.Button(self.frame, text="Import file", command=self.plot_file) + self.clear_route_btn = tk.Button(self.frame, text="Clear route", command=self.clear_route) + + row = 0 + self.waypoint_prev_btn.grid(row=row, columnspan=2) + row += 1 + self.waypoint_btn.grid(row=row, columnspan=2) + row += 1 + self.waypoint_next_btn.grid(row=row, columnspan=2) + row += 1 + self.source_ac.grid(row=row,columnspan=2, pady=(10,0)) # The AutoCompleter takes two rows to show the list when needed, so we skip one + row += 2 + self.dest_ac.grid(row=row,columnspan=2, pady=(10,0)) + row += 2 + self.range_entry.grid(row=row, pady=10, sticky=tk.W) + row += 1 + self.efficiency_slider.grid(row=row, pady=10, columnspan=2, sticky=tk.EW) + row += 1 + self.csv_route_btn.grid(row=row, pady=10, padx=0) + self.plot_route_btn.grid(row=row, pady=10, padx=0) + self.plot_gui_btn.grid(row=row, column=1, pady=10, padx=5, sticky=tk.W) + self.cancel_plot.grid(row=row, column=1, pady=10, padx=5, sticky=tk.E) + row += 1 + self.clear_route_btn.grid(row=row,column=1) + row += 1 + self.jumpcounttxt_lbl.grid(row=row, pady=5, sticky=tk.W) + row += 1 + self.error_lbl.grid(row=row, columnspan=2) + self.error_lbl.grid_remove() + row += 1 + + # Check if we're having a valid range on the fly + self.range_entry.var.trace('w', self.check_range) + + self.show_plot_gui(False) + + if not self.route.__len__() > 0: + self.waypoint_prev_btn.grid_remove() + self.waypoint_btn.grid_remove() + self.waypoint_next_btn.grid_remove() + self.jumpcounttxt_lbl.grid_remove() + self.clear_route_btn.grid_remove() + + if self.update_available: + update_txt = "New Spansh update available!\n" + update_txt += "If you choose to install it, you will have to restart EDMC for it to take effect.\n\n" + update_txt += self.spansh_updater.changelogs + update_txt += "\n\nInstall?" + install_update = confirmDialog.askyesno("SpanshRouter", update_txt) + + if install_update: + confirmDialog.showinfo("SpanshRouter", "The update will be installed as soon as you quit EDMC.") + else: + self.update_available = False + + self.update_gui() + + return self.frame + + def show_plot_gui(self, show=True): + if show: + self.waypoint_prev_btn.grid_remove() + self.waypoint_btn.grid_remove() + self.waypoint_next_btn.grid_remove() + self.jumpcounttxt_lbl.grid_remove() + self.clear_route_btn.grid_remove() + + self.plot_gui_btn.grid_remove() + self.csv_route_btn.grid_remove() + self.source_ac.grid() + self.source_ac.set_text(monitor.system) + self.dest_ac.grid() + self.range_entry.grid() + self.efficiency_slider.grid() + self.plot_route_btn.grid() + self.cancel_plot.grid() + + # Workaround because EDMC keeps switching the placeholder to bright white + if self.source_ac.get() == self.source_ac.placeholder: + self.source_ac.force_placeholder_color() + if self.dest_ac.get() == self.dest_ac.placeholder: + self.dest_ac.force_placeholder_color() + if self.range_entry.get() == self.range_entry.placeholder: + self.range_entry.force_placeholder_color() + self.show_route_gui(False) + + else: + if len(self.source_ac.var.get()) == 0: + self.source_ac.put_placeholder() + if len(self.dest_ac.var.get()) == 0: + self.dest_ac.put_placeholder() + self.source_ac.grid_remove() + self.dest_ac.grid_remove() + self.range_entry.grid_remove() + self.efficiency_slider.grid_remove() + self.plot_gui_btn.grid_remove() + self.plot_route_btn.grid_remove() + self.cancel_plot.grid_remove() + self.plot_gui_btn.grid() + self.csv_route_btn.grid() + self.show_route_gui(True) + + def set_source_ac(self, text): + self.source_ac.delete(0, tk.END) + self.source_ac.insert(0, text) + self.source_ac.set_default_style() + + def show_route_gui(self, show): + self.hide_error() + if not show or not self.route.__len__() > 0: + self.waypoint_prev_btn.grid_remove() + self.waypoint_btn.grid_remove() + self.waypoint_next_btn.grid_remove() + self.jumpcounttxt_lbl.grid_remove() + self.clear_route_btn.grid_remove() + else: + self.waypoint_btn["text"] = self.next_wp_label + self.next_stop + if self.jumps_left > 0: + self.jumpcounttxt_lbl["text"] = self.jumpcountlbl_txt + str(self.jumps_left) + self.jumpcounttxt_lbl.grid() + else: + self.jumpcounttxt_lbl.grid_remove() + + self.waypoint_prev_btn.grid() + self.waypoint_btn.grid() + self.waypoint_next_btn.grid() + + if self.offset == 0: + self.waypoint_prev_btn.config(state=tk.DISABLED) + else: + self.waypoint_prev_btn.config(state=tk.NORMAL) + + if self.offset == self.route.__len__()-1: + self.waypoint_next_btn.config(state=tk.DISABLED) + else: + self.waypoint_next_btn.config(state=tk.NORMAL) + + self.clear_route_btn.grid() + + def update_gui(self): + self.show_route_gui(True) + + def show_error(self, error): + self.error_txt.set(error) + self.error_lbl.grid() + + def hide_error(self): + self.error_lbl.grid_remove() + + def enable_plot_gui(self, enable): + if enable: + self.source_ac.config(state=tk.NORMAL) + self.source_ac.update_idletasks() + self.dest_ac.config(state=tk.NORMAL) + self.dest_ac.update_idletasks() + self.efficiency_slider.config(state=tk.NORMAL) + self.efficiency_slider.update_idletasks() + self.range_entry.config(state=tk.NORMAL) + self.range_entry.update_idletasks() + self.plot_route_btn.config(state=tk.NORMAL, text="Calculate") + self.plot_route_btn.update_idletasks() + self.cancel_plot.config(state=tk.NORMAL) + self.cancel_plot.update_idletasks() + else: + self.source_ac.config(state=tk.DISABLED) + self.source_ac.update_idletasks() + self.dest_ac.config(state=tk.DISABLED) + self.dest_ac.update_idletasks() + self.efficiency_slider.config(state=tk.DISABLED) + self.efficiency_slider.update_idletasks() + self.range_entry.config(state=tk.DISABLED) + self.range_entry.update_idletasks() + self.plot_route_btn.config(state=tk.DISABLED, text="Computing...") + self.plot_route_btn.update_idletasks() + self.cancel_plot.config(state=tk.DISABLED) + self.cancel_plot.update_idletasks() + + # -- END GUI part -- + + + def open_last_route(self): + try: + has_headers = False + with open(self.save_route_path, 'r') as csvfile: + # Check if the file has a header for compatibility with previous versions + dict_route_reader = csv.DictReader(csvfile) + if dict_route_reader.fieldnames[0] == self.system_header: + has_headers = True + + if has_headers: + self.plot_csv(self.save_route_path, clear_previous_route=False) + else: + with open(self.save_route_path, 'r') as csvfile: + route_reader = csv.reader(csvfile) + + for row in route_reader: + if row not in (None, "", []): + self.route.append(row) + + try: + with open(self.offset_file_path, 'r') as offset_fh: + self.offset = int(offset_fh.readline()) + + except: + self.offset = 0 + + self.jumps_left = 0 + for row in self.route[self.offset:]: + if row[1] not in [None, "", []]: + self.jumps_left += int(row[1]) + + self.next_stop = self.route[self.offset][0] + self.copy_waypoint() + except: + print("No previously saved route.") + + def copy_waypoint(self): + if sys.platform == "linux" or sys.platform == "linux2": + command = subprocess.Popen(["echo", "-n", self.next_stop], stdout=subprocess.PIPE) + subprocess.Popen(["xclip", "-selection", "c"], stdin=command.stdout) + else: + self.parent.clipboard_clear() + self.parent.clipboard_append(self.next_stop) + self.parent.update() + + def goto_next_waypoint(self): + if self.offset < self.route.__len__()-1: + self.update_route(1) + + def goto_prev_waypoint(self): + if self.offset > 0: + self.update_route(-1) + + def update_route(self, direction=1): + if direction > 0: + if self.route[self.offset][1] not in [None, "", []]: + self.jumps_left -= int(self.route[self.offset][1]) + self.offset += 1 + else: + self.offset -= 1 + if self.route[self.offset][1] not in [None, "", []]: + self.jumps_left += int(self.route[self.offset][1]) + + if self.offset >= self.route.__len__(): + self.next_stop = "End of the road!" + self.update_gui() + else: + self.next_stop = self.route[self.offset][0] + self.update_gui() + self.copy_waypoint() + self.save_offset() + + def goto_changelog_page(self): + changelog_url = 'https://github.com/CMDR-Kiel42/EDMC_SpanshRouter/blob/master/CHANGELOG.md#' + changelog_url += self.spansh_updater.version.replace('.', '') + webbrowser.open(changelog_url) + + def plot_file(self): + ftypes = [ + ('All supported files', '*.csv *.txt'), + ('CSV files', '*.csv'), + ('Text files', '*.txt'), + ] + filename = filedialog.askopenfilename(filetypes = ftypes) + + if filename.__len__() > 0: + try: + ftype_supported = False + if filename.endswith(".csv"): + ftype_supported = True + self.plot_csv(filename) + + elif filename.endswith(".txt"): + ftype_supported = True + self.plot_edts(filename) + + if ftype_supported: + self.offset = 0 + self.next_stop = self.route[0][0] + self.copy_waypoint() + self.update_gui() + self.save_all_route() + else: + self.show_error("Unsupported file type") + except: + exc_type, exc_value, exc_traceback = sys.exc_info() + lines = traceback.format_exception(exc_type, exc_value, exc_traceback) + sys.stderr.write(''.join('!! ' + line for line in lines)) + self.enable_plot_gui(True) + self.show_error("An error occured while reading the file.") + + def plot_csv(self, filename, clear_previous_route=True): + with open(filename, 'r') as csvfile: + route_reader = csv.DictReader(csvfile) + + if clear_previous_route: + self.clear_route(False) + for row in route_reader: + if row not in (None, "", []): + self.route.append([ + row[self.system_header], + row.get(self.jumps_header, "") # Jumps column is optional + ]) + if row.get(self.jumps_header) != None: + self.jumps_left += int(row[self.jumps_header]) + + def plot_route(self): + self.hide_error() + try: + source = self.source_ac.get().strip() + dest = self.dest_ac.get().strip() + efficiency = self.efficiency_slider.get() + + if ( source and source != self.source_ac.placeholder and + dest and dest != self.dest_ac.placeholder ): + + try: + range_ly = float(self.range_entry.get()) + except ValueError: + self.show_error("Invalid range") + return + + job_url="https://spansh.co.uk/api/route?" + + results = requests.post(job_url, params={ + "efficiency": efficiency, + "range": range_ly, + "from": source, + "to": dest + }, headers={'User-Agent': "EDMC_SpanshRouter 1.0"}) + + if results.status_code == 202: + self.enable_plot_gui(False) + + tries = 0 + while(tries < 20): + response = json.loads(results.content) + job = response["job"] + + results_url = "https://spansh.co.uk/api/results/" + job + route_response = requests.get(results_url, timeout=5) + if route_response.status_code != 202: + break + tries += 1 + sleep(1) + + if route_response: + if route_response.status_code == 200: + route = json.loads(route_response.content)["result"]["system_jumps"] + self.clear_route(show_dialog=False) + for waypoint in route: + self.route.append([waypoint["system"], str(waypoint["jumps"])]) + self.jumps_left += waypoint["jumps"] + self.enable_plot_gui(True) + self.show_plot_gui(False) + self.offset = 1 if self.route[0][0] == monitor.system else 0 + self.next_stop = self.route[self.offset][0] + self.copy_waypoint() + self.update_gui() + self.save_all_route() + else: + sys.stderr.write("Failed to query plotted route from Spansh: code " + str(route_response.status_code) + route_response.text + '\n') + self.enable_plot_gui(True) + failure = json.loads(results.content) + + if route_response.status_code == 400 and "error" in failure: + self.show_error(failure["error"]) + if "starting system" in failure["error"]: + self.source_ac["fg"] = "red" + if "finishing system" in failure["error"]: + self.dest_ac["fg"] = "red" + else: + self.show_error(self.plot_error) + else: + sys.stderr.write("Query to Spansh timed out") + self.enable_plot_gui(True) + self.show_error("The query to Spansh was too long and timed out, please try again.") + else: + sys.stderr.write("Failed to query plotted route from Spansh: code " + str(results.status_code) + results.text + '\n') + self.enable_plot_gui(True) + failure = json.loads(results.content) + + if results.status_code == 400 and "error" in failure: + self.show_error(failure["error"]) + if "starting system" in failure["error"]: + self.source_ac["fg"] = "red" + if "finishing system" in failure["error"]: + self.dest_ac["fg"] = "red" + else: + self.show_error(self.plot_error) + + except: + exc_type, exc_value, exc_traceback = sys.exc_info() + lines = traceback.format_exception(exc_type, exc_value, exc_traceback) + sys.stderr.write(''.join('!! ' + line for line in lines)) + self.enable_plot_gui(True) + self.show_error(self.plot_error) + + def plot_edts(self, filename): + try: + with open(filename, 'r') as txtfile: + route_txt = txtfile.readlines() + self.clear_route(False) + for row in route_txt: + if row not in (None, "", []): + if row.lstrip().startswith('==='): + jumps = int(re.findall("\d+ jump", row)[0].rstrip(' jumps')) + self.jumps_left += jumps + + system = row[row.find('>') + 1:] + if ',' in system: + systems = system.split(',') + for system in systems: + self.route.append([system.strip(), jumps]) + jumps = 1 + self.jumps_left += jumps + else: + self.route.append([system.strip(), jumps]) + except: + exc_type, exc_value, exc_traceback = sys.exc_info() + lines = traceback.format_exception(exc_type, exc_value, exc_traceback) + sys.stderr.write(''.join('!! ' + line for line in lines)) + self.enable_plot_gui(True) + self.show_error("An error occured while reading the file.") + + def clear_route(self, show_dialog=True): + clear = confirmDialog.askyesno("SpanshRouter","Are you sure you want to clear the current route?") if show_dialog else True + + if clear: + self.offset = 0 + self.route = [] + self.next_waypoint = "" + self.jumps_left = 0 + try: + os.remove(self.save_route_path) + except: + print("No route to delete") + try: + os.remove(self.offset_file_path) + except: + print("No offset file to delete") + + self.update_gui() + + def save_all_route(self): + self.save_route() + self.save_offset() + + def save_route(self): + if self.route.__len__() != 0: + with open(self.save_route_path, 'w') as csvfile: + fieldnames = [self.system_header, self.jumps_header] + writer = csv.writer(csvfile) + writer.writerow(fieldnames) + writer.writerows(self.route) + else: + try: + os.remove(self.save_route_path) + except: + print("No route to delete") + + def save_offset(self): + if self.route.__len__() != 0: + with open(self.offset_file_path, 'w') as offset_fh: + offset_fh.write(str(self.offset)) + else: + try: + os.remove(self.offset_file_path) + except: + print("No offset to delete") + + def check_range(self, name, index, mode): + value = self.range_entry.var.get() + if value.__len__() > 0 and value != self.range_entry.placeholder: + try: + float(value) + self.range_entry.set_error_style(False) + self.hide_error() + except ValueError: + self.show_error("Invalid range") + self.range_entry.set_error_style() + + def cleanup_old_version(self): + try: + if (os.path.exists(os.path.join(self.plugin_dir, "AutoCompleter.py")) + and os.path.exists(os.path.join(self.plugin_dir, "SpanshRouter"))): + files_list = os.listdir(self.plugin_dir) + + for filename in files_list: + if (filename != "load.py" + and (filename.endswith(".py") or filename.endswith(".pyc") or filename.endswith(".pyo"))): + os.remove(os.path.join(self.plugin_dir, filename)) + except: + exc_type, exc_value, exc_traceback = sys.exc_info() + lines = traceback.format_exception(exc_type, exc_value, exc_traceback) + sys.stderr.write(''.join('!! ' + line for line in lines)) + + def check_for_update(self): + self.cleanup_old_version() + version_url = "https://raw.githubusercontent.com/CMDR-Kiel42/EDMC_SpanshRouter/master/version.json" + try: + response = requests.get(version_url, timeout=2) + + if response.status_code == 200: + if self.plugin_version != response.content: + self.update_available = True + self.spansh_updater = SpanshUpdater(response.content, self.plugin_dir) + + else: + sys.stderr.write("Could not query latest SpanshRouter version: " + str(response.status_code) + response.text) + except: + exc_type, exc_value, exc_traceback = sys.exc_info() + lines = traceback.format_exception(exc_type, exc_value, exc_traceback) + sys.stderr.write(''.join('!! ' + line for line in lines)) + + def install_update(self): + self.spansh_updater.install() diff --git a/SpanshRouter/SpanshRouter.py b/SpanshRouter/SpanshRouter.py index 57b676a..9f13e7b 100755 --- a/SpanshRouter/SpanshRouter.py +++ b/SpanshRouter/SpanshRouter.py @@ -18,7 +18,7 @@ try: from Tkinter import * import tkFileDialog as filedialog import tkMessageBox as confirmDialog - import ttk + import ttk except ModuleNotFoundError: # Python 3 import tkinter as tk @@ -31,7 +31,7 @@ class SpanshRouter(): version_file = os.path.join(plugin_dir, "version.json") with open(version_file, 'r') as version_fd: self.plugin_version = version_fd.read() - + self.update_available = False self.next_stop = "No route planned" self.route = [] @@ -48,7 +48,7 @@ class SpanshRouter(): self.system_header = "System Name" self.jumps_header = "Jumps" - # -- GUI part -- + # -- GUI part -- def init_gui(self, parent): self.parent = parent parentwidth = parent.winfo_width() @@ -71,7 +71,7 @@ class SpanshRouter(): self.plot_gui_btn = tk.Button(self.frame, text="Plot route", command=self.show_plot_gui) self.plot_route_btn = tk.Button(self.frame, text="Calculate", command=self.plot_route) self.cancel_plot = tk.Button(self.frame, text="Cancel", command=lambda: self.show_plot_gui(False)) - + self.csv_route_btn = tk.Button(self.frame, text="Import file", command=self.plot_file) self.clear_route_btn = tk.Button(self.frame, text="Clear route", command=self.clear_route) @@ -126,7 +126,7 @@ class SpanshRouter(): confirmDialog.showinfo("SpanshRouter", "The update will be installed as soon as you quit EDMC.") else: self.update_available = False - + self.update_gui() return self.frame @@ -249,7 +249,7 @@ class SpanshRouter(): self.cancel_plot.config(state=tk.DISABLED) self.cancel_plot.update_idletasks() - # -- END GUI part -- + # -- END GUI part -- def open_last_route(self): @@ -328,7 +328,7 @@ class SpanshRouter(): changelog_url = 'https://github.com/CMDR-Kiel42/EDMC_SpanshRouter/blob/master/CHANGELOG.md#' changelog_url += self.spansh_updater.version.replace('.', '') webbrowser.open(changelog_url) - + def plot_file(self): ftypes = [ ('All supported files', '*.csv *.txt'), @@ -343,7 +343,7 @@ class SpanshRouter(): if filename.endswith(".csv"): ftype_supported = True self.plot_csv(filename) - + elif filename.endswith(".txt"): ftype_supported = True self.plot_edts(filename) @@ -372,7 +372,7 @@ class SpanshRouter(): for row in route_reader: if row not in (None, "", []): self.route.append([ - row[self.system_header], + row[self.system_header], row.get(self.jumps_header, "") # Jumps column is optional ]) if row.get(self.jumps_header) != None: @@ -405,7 +405,7 @@ class SpanshRouter(): if results.status_code == 202: self.enable_plot_gui(False) - + tries = 0 while(tries < 20): response = json.loads(results.content) @@ -428,7 +428,7 @@ class SpanshRouter(): self.enable_plot_gui(True) self.show_plot_gui(False) self.offset = 1 if self.route[0][0] == monitor.system else 0 - self.next_stop = self.route[self.offset][0] + self.next_stop = self.route[self.offset][0] self.copy_waypoint() self.update_gui() self.save_all_route() @@ -438,7 +438,7 @@ class SpanshRouter(): failure = json.loads(results.content) if route_response.status_code == 400 and "error" in failure: - self.show_error(failure["error"]) + self.show_error(failure["error"]) if "starting system" in failure["error"]: self.source_ac["fg"] = "red" if "finishing system" in failure["error"]: @@ -455,14 +455,14 @@ class SpanshRouter(): failure = json.loads(results.content) if results.status_code == 400 and "error" in failure: - self.show_error(failure["error"]) + self.show_error(failure["error"]) if "starting system" in failure["error"]: self.source_ac["fg"] = "red" if "finishing system" in failure["error"]: self.dest_ac["fg"] = "red" else: self.show_error(self.plot_error) - + except: exc_type, exc_value, exc_traceback = sys.exc_info() lines = traceback.format_exception(exc_type, exc_value, exc_traceback) @@ -532,7 +532,7 @@ class SpanshRouter(): os.remove(self.save_route_path) except: print("No route to delete") - + def save_offset(self): if self.route.__len__() != 0: with open(self.offset_file_path, 'w') as offset_fh: @@ -561,7 +561,7 @@ class SpanshRouter(): files_list = os.listdir(self.plugin_dir) for filename in files_list: - if (filename != "load.py" + if (filename != "load.py" and (filename.endswith(".py") or filename.endswith(".pyc") or filename.endswith(".pyo"))): os.remove(os.path.join(self.plugin_dir, filename)) except: @@ -574,12 +574,12 @@ class SpanshRouter(): version_url = "https://raw.githubusercontent.com/CMDR-Kiel42/EDMC_SpanshRouter/master/version.json" try: response = requests.get(version_url, timeout=2) - + if response.status_code == 200: if self.plugin_version != response.content: self.update_available = True self.spansh_updater = SpanshUpdater(response.content, self.plugin_dir) - + else: sys.stderr.write("Could not query latest SpanshRouter version: " + str(response.status_code) + response.text) except: diff --git a/load.py b/load.py index ef3e092..78d0f9a 100644 --- a/load.py +++ b/load.py @@ -1,4 +1,9 @@ -from SpanshRouter import SpanshRouter +import sys +is_py2 = sys.version[0] == '2' +if is_py2: + from SpanshRouter import SpanshRouter +else: + import SpanshRouter as SpanshRouter global spansh_router spansh_router = None @@ -9,10 +14,9 @@ def plugin_start3(plugin_dir): def plugin_start(plugin_dir): # Check for newer versions - global spansh_router - #spansh_router = SpanshRouter(plugin_dir) + spansh_router = SpanshRouter(plugin_dir) #spansh_router.check_for_update() - spansh_router.open_last_route() + #spansh_router.open_last_route() return 'spansh_router' def plugin_stop(): diff --git a/updater.py b/updater.py new file mode 100644 index 0000000..9388e7d --- /dev/null +++ b/updater.py @@ -0,0 +1,66 @@ +import os +import requests +import zipfile +import sys +import traceback +import json + +class SpanshUpdater(): + def __init__(self, version, plugin_dir): + self.version = version + self.zip_name = "EDMC_SpanshRouter_" + version.replace('.', '') + ".zip" + self.plugin_dir = plugin_dir + self.zip_path = os.path.join(self.plugin_dir, self.zip_name) + self.zip_downloaded = False + self.changelogs = self.get_changelog() + + def download_zip(self): + url = 'https://github.com/CMDR-Kiel42/EDMC_SpanshRouter/releases/download/v' + self.version + '/' + self.zip_name + + try: + r = requests.get(url) + if r.status_code == 200: + with open(self.zip_path, 'wb') as f: + print("Downloading SpanshRouter to " + self.zip_path) + f.write(os.path.join(r.content)) + self.zip_downloaded = True + else: + sys.stderr.write("Failed to fetch SpanchRouter update. Status code: " + str(r.status_code)) + self.zip_downloaded = False + except: + exc_type, exc_value, exc_traceback = sys.exc_info() + lines = traceback.format_exception(exc_type, exc_value, exc_traceback) + sys.stderr.write(''.join('!! ' + line for line in lines)) + self.zip_downloaded = False + finally: + return self.zip_downloaded + + def install(self): + if self.download_zip(): + try: + with zipfile.ZipFile(self.zip_path, 'r') as zip_ref: + zip_ref.extractall(self.plugin_dir) + + os.remove(self.zip_path) + except: + exc_type, exc_value, exc_traceback = sys.exc_info() + lines = traceback.format_exception(exc_type, exc_value, exc_traceback) + sys.stderr.write(''.join('!! ' + line for line in lines)) + else: + sys.stderr.write("Error when downloading the latest SpanshRouter update") + + def get_changelog(self): + url = "https://api.github.com/repos/CMDR-Kiel42/EDMC_SpanshRouter/releases/latest" + try: + r = requests.get(url, timeout=2) + + if r.status_code == 200: + # Get the changelog and replace all breaklines with simple ones + changelogs = json.loads(r.content)["body"] + changelogs = "\n".join(changelogs.splitlines()) + return changelogs + + except: + exc_type, exc_value, exc_traceback = sys.exc_info() + lines = traceback.format_exception(exc_type, exc_value, exc_traceback) + sys.stderr.write(''.join('!! ' + line for line in lines)) \ No newline at end of file