From e11bd4d636d3e560f94e0805eef9556436a79658 Mon Sep 17 00:00:00 2001 From: sloumdrone Date: Sun, 18 Nov 2018 08:43:36 -0800 Subject: [PATCH] Added support for binary downloads --- .gitignore | 1 + burrow.py | 2 +- connect.py | 16 ++++----- gui.py | 99 ++++++++++++++++++++++++++++++++++++++++++----------- home.gopher | 1 + parser.py | 5 ++- 6 files changed, 92 insertions(+), 32 deletions(-) diff --git a/.gitignore b/.gitignore index c1a29ee..2550ee3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ __pycache__/* __pycache__ *.swp *.json +*.pyc diff --git a/burrow.py b/burrow.py index 8770d7d..c270e58 100644 --- a/burrow.py +++ b/burrow.py @@ -2,7 +2,7 @@ ################################################ ## Burrow, a gopher client/browser ## - - - - - - - - - - - - - - - - - - - - - - - -## Version 0.2.1 +## Version 0.8.4 ################################################ from gui import GUI diff --git a/connect.py b/connect.py index b07df7f..d0ce3b7 100644 --- a/connect.py +++ b/connect.py @@ -14,10 +14,13 @@ class connect: socket_conn.connect((host, port)) socket_conn.sendall((resource + '\r\n').encode('utf-8')) - if itemtype in ['I','p','g']: - response = socket_conn.makefile(mode = 'rb', errors = 'ignore') - else: + if itemtype in ['i','0','1','3']: response = socket_conn.makefile(mode = 'r', errors = 'ignore') + else: + response = socket_conn.makefile(mode = 'rb', errors = 'ignore') + + self.raw_response = response.read() + self.filetype = itemtype except socket.timeout: print('Socket timeout') socket_conn.close() @@ -27,13 +30,6 @@ class connect: socket_conn.close() return {'type': '1', 'body': '3ERROR: Unable to communicate with remote server\tfalse\tnull.host\t1'} - try: - self.raw_response = response.read() - self.filetype = itemtype - except UnicodeDecodeError: - self.raw_response = '3ERROR: Unable to decode server response\tfalse\tnull.host\t1' - self.filetype = '3' - socket_conn.close() return {'type': self.filetype, 'body': self.raw_response} diff --git a/gui.py b/gui.py index 9b8a6df..dd85cff 100644 --- a/gui.py +++ b/gui.py @@ -2,6 +2,7 @@ import tkinter as tk import tkinter.simpledialog as dialog +from tkinter.filedialog import asksaveasfilename as savedialog from connect import connect as conn from parser import parser import time @@ -17,7 +18,6 @@ class GUI: self.history = [] self.history_location = -1 self.message_bar_content = '' - # self.config = None self.read_config() self.conn = conn() self.parser = parser() @@ -93,8 +93,10 @@ class GUI: #load the home screen self.load_home_screen(1) + #-----------Start GUI configuration----------------------- + def add_event_listeners(self): buttons = [ self.btn_back, @@ -121,7 +123,6 @@ class GUI: self.site_display.tag_bind('generic_r_click', "", (lambda event, href=None: self.show_context_menu(event, href))) - def pack_geometry(self): self.top_bar.pack(expand=False,fill=tk.BOTH,side=tk.TOP,anchor=tk.NW) self.top_bar.pack_propagate(False) @@ -157,32 +158,44 @@ class GUI: def show_context_menu(self, e, href=None): + current_page = self.entry_url.get() self.context_menu.delete(0,20) #add navigation options - if len(self.history) > 1 or self.history_location > 0: + if len(self.history) > 1 and self.history_location > 0: back = (lambda event=e: self.go_back(event)) - self.context_menu.add_command(label="Back", command=back) - if len(self.history) > 1 or self.history_location < len(self.history) - 2: + self.context_menu.add_command(label=" Back ", command=back) + else: + self.context_menu.add_command(label=" Back ", state=tk.DISABLED) + if len(self.history) > 1 and self.history_location < len(self.history) - 1: forward = (lambda event=e: self.go_forward(event)) - self.context_menu.add_command(label="Forward", command=forward) - if self.entry_url.get() != 'home': + self.context_menu.add_command(label=" Forward ", command=forward) + else: + self.context_menu.add_command(label=" Forward ", state=tk.DISABLED) + refresh = (lambda event=e, link=current_page: self.handle_request(event, link, False)) + self.context_menu.add_command(label=" Refresh ", command=refresh) + if current_page != 'home': home = (lambda event=e: self.load_home_screen(event)) - self.context_menu.add_command(label="Home", command=home) - + self.context_menu.add_command(label=" Home ", command=home) + else: + self.context_menu.add_command(label=" Home ", state=tk.DISABLED) + save_as_file = (lambda data=self.site_display.get(1.0,tk.END), url=current_page: self.write_to_file(data, url)) + self.context_menu.add_command(label=" Save as... ", command=save_as_file) if href: - copy_link = (lambda link=href: self.copy_to_clipboard(link)) - self.context_menu.add_command(label="Copy URL to clipboard", command=copy_link) - self.context_menu.add_separator() + copy_link = (lambda link=href: self.copy_to_clipboard(link)) + self.context_menu.add_command(label=" Copy URL to clipboard ", command=copy_link) + if self.is_favorite(href): + self.context_menu.add_separator() delete_favorite = (lambda event=e, link=href: self.remove_favorite(event, link)) - self.context_menu.add_command(label="Delete from favorites", command=delete_favorite) + self.context_menu.add_command(label=" Delete from favorites ", command=delete_favorite) rename_favorite = (lambda event=e, link=href: self.rename_favorite(event, link)) - self.context_menu.add_command(label="Rename this favorite", command=rename_favorite) + self.context_menu.add_command(label=" Rename this favorite ", command=rename_favorite) elif href: + self.context_menu.add_separator() add_favorite = (lambda event=e, link=href: self.add_to_favorites(event, link)) - self.context_menu.add_command(label="Add to favorites", command=add_favorite) + self.context_menu.add_command(label=" Add to favorites ", command=add_favorite) self.context_menu.tk_popup(e.x_root, e.y_root) @@ -193,6 +206,7 @@ class GUI: # ------------Start navigation methods---------------------------- + def handle_request(self,event=False, url=False, history=True): self.loading_bar = tk.Label(self.entry_url, text=' Loading... ', width=12, relief=tk.FLAT, height=1, fg='#FFFFFF', bg=self.TYPES) self.loading_bar.pack(side=tk.RIGHT, padx=(0,10)) @@ -226,7 +240,6 @@ class GUI: self.send_to_screen(data['body'],data['type']) - def parse_url(self, url=False): parsed_url = self.parser.parse_url(url) @@ -295,14 +308,11 @@ class GUI: def go_forward(self, event): if len(self.history) <= 1 or self.history_location >= len(self.history) - 1: return False - self.history_location += 1 href = self.history[self.history_location] self.handle_request(False, href, False) - - #-------------Start view methods---------------- @@ -403,6 +413,7 @@ class GUI: styletag = 'favoritecolor' else: styletag = 'linkcolor' + hover = (lambda event, href=link, tag_name=tag_name: self.hoverlink(event, href, tag_name)) clear = (lambda event, tag_name=tag_name: self.clear_status(event, tag_name)) self.site_display.tag_bind(tag_name, "", hover) @@ -420,18 +431,47 @@ class GUI: data = data[:-2] self.site_display.config(state=tk.NORMAL) self.site_display.delete(1.0, tk.END) - self.site_display.insert(tk.END, data) + self.site_display.insert(tk.END, data, 'generic_r_click') self.site_display.config(state=tk.DISABLED) def show_image(self, data): self.current_image = self.build_image(data) + callback = (lambda event, image=data, write='wb': self.write_to_file(contents=image, event=event, write=write)) + hover = (lambda event, href='Download this image...', tag_name='image_download': self.hoverlink(event, href, tag_name)) + clear = (lambda event, tag_name='image_download': self.clear_status(event, tag_name)) + self.site_display.tag_bind('image_download', "", callback) + self.site_display.tag_bind('image_download', "", hover) + self.site_display.tag_bind('image_download', '', clear) self.site_display.config(state=tk.NORMAL) self.site_display.delete(1.0, tk.END) + self.site_display.insert(tk.END,'Download this image',('linkcolor','image_download')) + self.site_display.insert(tk.END, '\n\n') self.site_display.image_create(tk.END, image = self.current_image) self.site_display.config(state=tk.DISABLED) + def show_bin_download(self, data): + url = self.entry_url.get() + filename = url.rpartition('/') + if len(filename) > 1: + filename = filename[-1] + else: + filename = '' + + callback = (lambda event, bindata=data, write='wb': self.write_to_file(contents=bindata, event=event, write=write)) + hover = (lambda event, href='Download {}'.format(filename), tag_name='bin_download': self.hoverlink(event, href, tag_name)) + clear = (lambda event, tag_name='bin_download': self.clear_status(event, tag_name)) + self.site_display.tag_bind('bin_download', "", callback) + self.site_display.tag_bind('bin_download', "", hover) + self.site_display.tag_bind('bin_download', '', clear) + self.site_display.config(state=tk.NORMAL) + self.site_display.delete(1.0, tk.END) + self.site_display.insert(tk.END,'Download ') + self.site_display.insert(tk.END,filename,('linkcolor','bin_download')) + self.site_display.config(state=tk.DISABLED) + + def send_to_screen(self, data, itemtype='1', clear=True): if itemtype == '0': self.show_text(data) @@ -440,6 +480,8 @@ class GUI: self.show_menu(data, clear) elif itemtype in ['p','I','g']: self.show_image(data) + elif itemtype in ['s','9','M','c',';','d','5']: + self.show_bin_download(data) try: self.loading_bar.destroy() @@ -562,6 +604,23 @@ class GUI: return True + def write_to_file(self, contents=None, page_url=None, event=None, write='w'): + url = self.entry_url.get() + filetype = url.rpartition('.') + if len(filetype) > 1: + filetype = filetype[-1] + else: + filetype = 'txt' + + filename = savedialog(initialdir="~/Desktop/", defaultextension='.{}'.format(filetype), title="Save As File", filetypes=((filetype,'*.{}'.format(filetype)),('all files','*.*'))) + if not filename or filename is None or contents is None: + return False + with open(filename, write) as f: + f.write(contents) + return True + + + if __name__ == '__main__': app = GUI() app.root.mainloop() diff --git a/home.gopher b/home.gopher index 9abdffe..74bf058 100644 --- a/home.gopher +++ b/home.gopher @@ -10,6 +10,7 @@ i##########CONTENT PORTALS########## false null.host 1 i false null.host 1 1Floodgap / gopher.floodgap.com 70 1Super Dimensional Fortress / sdf.org 70 +7Veronica-2: Search Engine /v2/vs gopher.floodgap.com 70 i false null.host 1 i false null.host 1 i#############FAVORITES############# false null.host 1 diff --git a/parser.py b/parser.py index f1bf56e..8c7c15e 100644 --- a/parser.py +++ b/parser.py @@ -18,7 +18,7 @@ class parser: if url == 'home': return False - regex = r'^(?P(?:(gopher|http):\/\/)?)?(?P[\w\-\.\d]+)(?P(?::\d+)?)?(?P(?:\/[\dgIp])?)?(?P(?:\/.*)?)?$' + regex = r'^(?P(?:(gopher|http):\/\/)?)?(?P[\w\-\.\d]+)(?P(?::\d+)?)?(?P(?:\/[01345679gIhisp])?)?(?P(?:\/.*)?)?$' match = re.match(regex, url) @@ -37,6 +37,9 @@ class parser: if not resource: resource = '/' + if not itemtype: + return False + self.filetype = itemtype[len(itemtype) - 1] if itemtype else '1' self.protocol = protocol if protocol else 'gopher://'