1+ #!/usr/bin/env python3
2+ # -*- coding:utf-8 -*-
3+ ###
4+ # Project Name: python4desktop - Created Date: Thursday September 17th 2020
5+ # Author: loitd - Email: loitranduc@gmail.com
6+ # Description: A downloader without GUI
7+ # Copyright (c) 2020 loitd. WWW: https://github.com/loitd
8+ # -----
9+ # Last Modified: Thursday September 17th 2020 10:14:57 pm By: loitd
10+ # -----
11+ # HISTORY:
12+ # Date By Comments
13+ # ---------- ------ ----------------------------------------------------------
14+ # 2020-09-17 loitd Initialized
15+ ###
16+ import os , time
17+ from lutils import utils
18+ from tkinter import Tk , messagebox , TOP , BOTTOM , RIGHT , LEFT , YES , NO , X , Y , filedialog , ttk , Frame , Label , Entry , Button , HORIZONTAL , PhotoImage
19+ from queue import Queue
20+ from functools import partial
21+ from requests import get , exceptions
22+ from shutil import copyfileobj
23+ from threading import Thread , Lock
24+
25+ def maingui ():
26+ root = Tk ()
27+ root .iconbitmap ("py4de.ico" )
28+ root .title ("Python4Desktop Downloader" )
29+ # ----url
30+ row = Frame (root )
31+ row .pack (side = TOP , fill = X , padx = 5 , pady = 5 )
32+ lbl = Label (row , width = 10 , text = "URL:" )
33+ lbl .pack (side = LEFT )
34+ ent = Entry (row , width = 88 )
35+ ent .pack (side = RIGHT , expand = YES , fill = X )
36+ ent .insert (0 , testurl )
37+ # save file name
38+ row = Frame (root )
39+ row .pack (side = TOP , fill = X , padx = 5 , pady = 5 )
40+ lbl2 = Label (row , width = 10 , text = "Save as:" )
41+ lbl2 .pack (side = LEFT )
42+ btnSave = Button (row , text = "..." , command = saveFileAs )
43+ btnSave .pack (side = RIGHT , expand = NO )
44+ lblSave = Label (row , width = 88 , text = "Please select destination file name/path" )
45+ lblSave .pack (side = RIGHT , expand = YES , fill = X )
46+ btnSave ['command' ] = partial (saveFileAs , lblSave )
47+ # ProgressBar
48+ rowProgress = Frame (root )
49+ rowProgress .pack (side = TOP , fill = X , padx = 5 , pady = 5 )
50+ lbl = Label (rowProgress , width = 10 , text = "PROGRESS" )
51+ lbl .pack (side = LEFT )
52+ progress = ttk .Progressbar (rowProgress , orient = HORIZONTAL , length = 100 , mode = "determinate" )
53+ progress .pack (side = RIGHT , expand = YES , fill = X )
54+ # Buttons
55+ btnFrame = Frame (root )
56+ btnFrame .pack (side = BOTTOM , expand = YES , fill = X )
57+ # Exit
58+ imgexit = PhotoImage (file = r"imgexit.gif" ).subsample (3 )
59+ btnExit = Button (btnFrame , text = "Exit" , image = imgexit , compound = LEFT , command = (lambda root = root : exit (root )))
60+ btnExit .image = imgexit # is a must to keep a reference
61+ btnExit .pack (side = LEFT , padx = 5 , pady = 5 )
62+ # Download
63+ imgdl = PhotoImage (file = r"imgdld.gif" ).subsample (3 )
64+ btnDld = Button (btnFrame , text = "DOWNLOAD" , compound = LEFT )
65+ btnDld ['image' ] = imgdl # is a must to keep a reference
66+ btnDld ['command' ] = partial (start_download , btnDld , ent , progress , lblSave )
67+ btnDld .pack (side = RIGHT , padx = 5 , pady = 5 )
68+ # ---loops
69+ cmdloop (root , cmdq )
70+ root .mainloop ()
71+
72+ def saveFileAs (lblSave ):
73+ files = [('All Files' , '*.*' ), ('Python Files' , '*.py' ), ('Text Document' , '*.txt' )]
74+ filename = filedialog .asksaveasfile (filetypes = files , defaultextension = files )
75+ # print(filename, filename.name)
76+ cmdq .put (( updateFilename , (lblSave , filename .name ), {} ))
77+ return filename
78+
79+ def cmdloop (root , cmdq ):
80+ try :
81+ if not cmdq .empty ():
82+ f ,a ,k = cmdq .get_nowait ()
83+ f (* a , ** k )
84+ except Exception as e :
85+ raise (e )
86+ # pass
87+ root .after (200 , cmdloop , root , cmdq )
88+
89+ def start_download (btnDld , ent , progress , lblSave ):
90+ try :
91+ # cmdq.put((messagebox.showinfo, ('title', 'message'), {}))
92+ cmdq .put (( tonggle_button , (btnDld , ), {} ))
93+ # get some inputs
94+ url = ent .get ()
95+ filename = lblSave ['text' ]
96+ if url and url != "" : mainq .put_nowait (url )
97+ utils .printlog ("Queue size: {0}" .format (mainq .qsize ()))
98+ lk1 = Lock ()
99+ # start DOWNLOAD thread
100+ dld = Thread (name = "DOWNLOAD" , target = download , daemon = True , args = (filename , lk1 , btnDld ))
101+ dld .start ()
102+ # start STATUS thread
103+ stt = Thread (name = "STATUS" , target = status , daemon = True , args = (filename , lk1 , progress ))
104+ stt .start ()
105+ # Additional
106+ # JOINING - Blocking
107+ # dld.join()
108+ # stt.join()
109+ # mainq.join() ## block until all tasks are done -> q.task_done()
110+ except Exception as e :
111+ raise (e )
112+ finally :
113+ pass
114+
115+ def exit (root ):
116+ EXITFLAG = 1
117+ root .quit ()
118+
119+ def download (filename , lk1 , btnDld ):
120+ lk1 .acquire () #acquire the lock before status can
121+ try :
122+ if mainq .empty (): return 0
123+ url = mainq .get (1 , 1 ) #block=True, timeout=None
124+ # filename = url.split('/')[-1]
125+ utils .printlog ("Start download: {0}" .format (url ))
126+ with get (url , stream = True , timeout = 3 ) as r :
127+ filesize = r .headers ['Content-length' ].encode ("utf8" )
128+ utils .printlog ("Download size: {0} kilobytes" .format (filesize ))
129+ with open (filename + ".tmp" , 'wb' ) as fh :
130+ fh .write (filesize )
131+ with open (filename , 'wb' ) as f :
132+ lk1 .release ()
133+ copyfileobj (r .raw , f )
134+ return filename , filesize
135+ except exceptions .ConnectionError as e :
136+ utils .printlog ("Error during connect to URL: {0}" .format (e ))
137+ # messagebox.showerror("Error", "Connection to {0} timeout. Please check your internet connection.".format(url))
138+ return 0 ,0
139+ except exceptions .MissingSchema as e :
140+ utils .printlog ("Invalid URL: {0}" .format (e ))
141+ except Exception as e :
142+ raise (e )
143+ finally :
144+ utils .printlog ("Finalizing with qsize: {0}" .format (mainq .qsize ()))
145+ if lk1 .locked (): lk1 .release ()
146+ cmdq .put (( tonggle_button , (btnDld , ), {} )) #toggle the button
147+
148+ def status (filename , lk1 , progress ):
149+ lk1 .acquire ()
150+ try :
151+ with open (filename + ".tmp" , "rb" ) as fh :
152+ filesize = fh .read ()
153+ filesize = int (filesize )
154+ utils .printlog ("Got filesize = {0}" .format (filesize ))
155+ os .remove (filename + ".tmp" )
156+ while 1 : #Return true if the lock is acquired
157+ thesize = os .stat (filename ).st_size
158+ if thesize == filesize :
159+ utils .printlog ("Download finished" )
160+ if lk1 .locked (): lk1 .release ()
161+ cmdq .put (( setProgress , (progress , 100 ), {} )) #set progress
162+ break
163+ else :
164+ percent = (thesize * 100 / filesize )
165+ utils .printwait ("{0}/{1} - Percentage: {2:.2f}% complete" .format (thesize , filesize , percent ), 1 )
166+ cmdq .put (( setProgress , (progress , percent ), {} )) #set progress
167+ except FileNotFoundError as e :
168+ utils .printlog ("File not found" )
169+ return 0
170+ except Exception as e :
171+ raise (e )
172+ finally :
173+ if lk1 .locked (): lk1 .release ()
174+
175+ def tonggle_button (btn ):
176+ utils .printlog ("Current BTN state is: {0}" .format (btn ['state' ]))
177+ if btn ['state' ] != 'disabled' :
178+ btn ['state' ] = 'disabled'
179+ else :
180+ btn ['state' ] = 'normal'
181+
182+ def setProgress (progressBar , percent ):
183+ progressBar ['value' ] = percent
184+
185+ def updateFilename (lbl , filename ):
186+ lbl ['text' ] = filename
187+
188+ if __name__ == "__main__" :
189+ testurl = "https://file-examples-com.github.io/uploads/2017/11/file_example_MP3_5MG.mp3"
190+ testurl = "https://file-examples-com.github.io/uploads/2017/11/file_example_MP3_1MG.mp3"
191+ testurl = "https://file-examples-com.github.io/uploads/2017/11/file_example_MP3_700KB.mp3"
192+ filename = "file_example_MP3_1MG.mp3"
193+ filename = "file_example_MP3_700KB.mp3"
194+ EXITFLAG = 0
195+ cmdq = Queue ()
196+ mainq = Queue (1 )
197+ # Start main gui thread
198+ gui = Thread (name = "GUI" , target = maingui , daemon = True , args = ())
199+ gui .start ()
200+ # Join the main thread
201+ gui .join ()
0 commit comments