开源代码学习之persepolis【二】
?
一、下載界面
?
1、主界面下載顯示
MainWindow首先對aria進行初始化,啟動aria2。啟動方法在download.py中。
?# start aria2
? ? ? ? start_aria = StartAria2Thread()
? ? ? ? self.threadPool.append(start_aria)
? ? ? ? self.threadPool[0].start()
? ? ? ? self.threadPool[0].ARIA2RESPONDSIGNAL.connect(self.startAriaMessage)
然后定義添加下載鏈接界面,AddLinkWindow可以有多個,都存放在self.addlinkwindows_list中。
調用的時候有三個參數(self, self.callBack, self.persepolis_setting),下載添加界面通過回調函數傳遞參數給主界面的callBack函數。callBack獲取下載信息后,添加到線程池中。
new_download = DownloadLink(gid, self)
? ? ? ? ? ? self.threadPool.append(new_download)
? ? ? ? ? ? self.threadPool[len(self.threadPool) - 1].start()
? ? ? ? ? ? self.threadPool[len(self.threadPool) -
? ? ? ? ? ? ? ? ? ? ? ? ? ? 1].ARIA2NOTRESPOND.connect(self.aria2NotRespond)
注意主界面的addLinkSpiderCallBack函數,該函數調用順序為:
1、下載添加界面獲取下載鏈接改變(linkLineChanged)信息
2、下載添加界面開啟線程AddLinkSpiderThread嘗試獲取鏈接文件大小,通過parent將該線程添加到主界面線程池中。并將AddLinkSpiderThread的信號連接到主線程的addLinkSpiderCallBack函數,同時將下載添加界面的指針child添加到槽函數的參數中,這樣主界面可以通過child訪問下載添加界面。
self.parent.threadPool[len(self.parent.threadPool) - 1].ADDLINKSPIDERSIGNAL.connect(
? ? ? ? ? ? ? ? partial(self.parent.addLinkSpiderCallBack, child=self))
3、AddLinkSpiderThread線程將結果ADDLINKSPIDERSIGNAL信號發送給主界面addLinkSpiderCallBack函數,注意這里發射的時候,只有dict參數,連接的時候有兩個參數。
self.ADDLINKSPIDERSIGNAL.emit(spider_dict)
4、主界面addLinkSpiderCallBack函數通過child調用下載添加界面,設置文件名稱和大小的顯示。
這樣就是下載鏈接界面新增線程到主界面,然后主界面線程執行完成后控制子界面更新,為什么不是下載鏈接添加界面自己開啟一個線程獲取文件大小,然然后根據獲取結果自己改變下載鏈接界面呢?
?
mainwindow.py:
class DownloadLink(QThread):ARIA2NOTRESPOND = pyqtSignal()def __init__(self, gid, parent):QThread.__init__(self)self.gid = gidself.parent = parentdef run(self):# add gid of download to the active gids in temp_db# or update data base , if it was existed beforetry:self.parent.temp_db.insertInSingleTable(self.gid)except:# release lockself.parent.temp_db.lock = Falsedictionary = {'gid': self.gid, 'status': 'active'}self.parent.temp_db.updateSingleTable(dictionary)# if request is not successful then persepolis is checking rpc# connection with download.aria2Version() functionanswer = download.downloadAria(self.gid, self.parent)if answer == False:version_answer = download.aria2Version()if version_answer == 'did not respond':self.ARIA2NOTRESPOND.emit()class MainWindow(MainWindow_Ui):def __init__(self, start_in_tray, persepolis_main, persepolis_setting):super().__init__(persepolis_setting)self.persepolis_setting = persepolis_settingself.persepolis_main = persepolis_main# list of threadsself.threadPool = []# start aria2start_aria = StartAria2Thread()self.threadPool.append(start_aria)self.threadPool[0].start()self.threadPool[0].ARIA2RESPONDSIGNAL.connect(self.startAriaMessage)def addLinkButtonPressed(self, button=None):addlinkwindow = AddLinkWindow(self, self.callBack, self.persepolis_setting)self.addlinkwindows_list.append(addlinkwindow)self.addlinkwindows_list[len(self.addlinkwindows_list) - 1].show()# callback of AddLinkWindowdef callBack(self, add_link_dictionary, download_later, category):# write information in data_baseself.persepolis_db.insertInDownloadTable([dict])self.persepolis_db.insertInAddLinkTable([add_link_dictionary])# if user didn't press download_later_pushButton in add_link window# then create new qthread for new download!if not(download_later):new_download = DownloadLink(gid, self)self.threadPool.append(new_download)self.threadPool[len(self.threadPool) - 1].start()self.threadPool[len(self.threadPool) -1].ARIA2NOTRESPOND.connect(self.aria2NotRespond)# open progress window for download.self.progressBarOpen(gid)# notify user# check that download scheduled or notif not(add_link_dictionary['start_time']):message = QCoreApplication.translate("mainwindow_src_ui_tr", "Download Starts")else:new_spider = SpiderThread(add_link_dictionary, self)self.threadPool.append(new_spider)self.threadPool[len(self.threadPool) - 1].start()self.threadPool[len(self.threadPool) - 1].SPIDERSIGNAL.connect(self.spiderUpdate)message = QCoreApplication.translate("mainwindow_src_ui_tr", "Download Scheduled")notifySend(message, '', 10000, 'no', parent=self)# see addlink.py filedef addLinkSpiderCallBack(self, spider_dict, child):# get file_name and file_sizefile_name = spider_dict['file_name']file_size = spider_dict['file_size']if file_size:file_size = 'Size: ' + str(file_size)child.size_label.setText(file_size)if file_name and not(child.change_name_checkBox.isChecked()):child.change_name_lineEdit.setText(file_name)child.change_name_checkBox.setChecked(True)?
2、下載添加界面
下載添加界面AddLinkWindow將第一個參數self初始化為parent,后續通過該參數對主界面進行訪問,第二個參數為回調函數,用于傳遞參數給主界面,第三個參數將系統設置傳遞給下載添加界面。
在下載鏈接改變時,將AddLinkSpiderThread加入到主界面的threadPool中,并將ADDLINKSPIDERSIGNAL連接到主界面的addLinkSpiderCallBack。
new_spider = AddLinkSpiderThread(dict)
self.parent.threadPool.append(new_spider)
self.parent.threadPool[len(self.parent.threadPool) - 1].ADDLINKSPIDERSIGNAL.connect(
? ? ? ? ? ? ? ? partial(self.parent.addLinkSpiderCallBack, child=self))
AddLinkSpiderThread通過spider.addLinkSpider獲取到文件大小和名稱信息,發送給主界面的addLinkSpiderCallBack函數,注意這里發射的時候,只有dict參數,連接的時候有兩個參數。
self.ADDLINKSPIDERSIGNAL.emit(spider_dict)
在按下確定按鈕后,通過callback回調函數調用傳遞參數給主界面。
addlink.py:
class AddLinkSpiderThread(QThread):ADDLINKSPIDERSIGNAL = pyqtSignal(dict)def __init__(self, add_link_dictionary):QThread.__init__(self)self.add_link_dictionary = add_link_dictionarydef run(self):try:# get file name and file sizefile_name, file_size = spider.addLinkSpider(self.add_link_dictionary)spider_dict = {'file_size': file_size, 'file_name': file_name}# emit resultsself.ADDLINKSPIDERSIGNAL.emit(spider_dict)class AddLinkWindow(AddLinkWindow_Ui):def __init__(self, parent, callback, persepolis_setting, plugin_add_link_dictionary={}):super().__init__(persepolis_setting)self.callback = callbackself.plugin_add_link_dictionary = plugin_add_link_dictionaryself.persepolis_setting = persepolis_settingself.parent = parent self.link_lineEdit.textChanged.connect(self.linkLineChanged)self.ok_pushButton.clicked.connect(partial(self.okButtonPressed, download_later=False))self.download_later_pushButton.clicked.connect(partial(self.okButtonPressed, download_later=True))# enable when link_lineEdit is not empty and find size of file.def linkLineChanged(self, lineEdit):if str(self.link_lineEdit.text()) == '':self.ok_pushButton.setEnabled(False)self.download_later_pushButton.setEnabled(False)else: # find file sizedict = {'link': str(self.link_lineEdit.text())}# spider is finding file sizenew_spider = AddLinkSpiderThread(dict)self.parent.threadPool.append(new_spider)self.parent.threadPool[len(self.parent.threadPool) - 1].start()self.parent.threadPool[len(self.parent.threadPool) - 1].ADDLINKSPIDERSIGNAL.connect(partial(self.parent.addLinkSpiderCallBack, child=self))self.ok_pushButton.setEnabled(True)self.download_later_pushButton.setEnabled(True)def okButtonPressed(self, button, download_later):# user submitted information by pressing ok_pushButton, so get information# from AddLinkWindow and return them to the mainwindow with callback!# save information in a dictionary(add_link_dictionary).self.add_link_dictionary = {'referer': referer, 'header': header, 'user_agent': user_agent, 'load_cookies': load_cookies,'out': out, 'start_time': start_time, 'end_time': end_time, 'link': link, 'ip': ip,'port': port, 'proxy_user': proxy_user, 'proxy_passwd': proxy_passwd,'download_user': download_user, 'download_passwd': download_passwd,'connections': connections, 'limit_value': limit, 'download_path': download_path}# get category of downloadcategory = str(self.add_queue_comboBox.currentText())del self.plugin_add_link_dictionary# return information to mainwindowself.callback(self.add_link_dictionary, download_later, category)# close windowself.close()3、總結
1、線程間傳遞參數可以通過回調函數傳遞,也可以通過信號和槽傳遞。
2、主從線程之間,主線程將self傳遞給從線程,從線程可以對主線程的函數進行調用。從線程也可以將self傳遞給主線程,由主線程對從線程進行函數調用
?
二、下載文件
?
啟動aria2的服務是通過subprocess.Popen啟動的,每個選項的意義在aria2接口文檔都有介紹。
subprocess?模塊允許你生成新的進程,連接它們的輸入、輸出、錯誤管道,并且獲取它們的返回碼。此模塊打算代替一些老舊的模塊與功能os.system, os.popen*, os.spawn.
https://docs.python.org/zh-cn/3/library/subprocess.html#subprocess.Popen.communicate
https://blog.csdn.net/qq_34355232/article/details/87709418
subprocess.Popen([aria2d, '--no-conf', '--enable-rpc', '--rpc-listen-port=' + str(port), '--rpc-max-request-size=2M', '--rpc-listen-all', '--quiet=true'], stderr=subprocess.PIPE, stdout=subprocess.PIPE, stdin=subprocess.PIPE, shell=False, creationflags=NO_WINDOW)
?
添加下載鏈接是通過XML-RPC遠程調用完成的:
server = xmlrpc.client.ServerProxy(server_uri, allow_none=True)
aria2的RPC接口介紹如下,支持JSON-RPC和XML-RPC。
https://aria2.github.io/manual/en/html/aria2c.html#rpc-interface
python的XML-RPC庫介紹文檔很多,找了兩個如下:
https://www.jianshu.com/p/9987913cf734
https://developer.51cto.com/art/201906/597963.htm
GID:aria2通過GID索引管理每個下載,GID為64位二進制數。RPC訪問時,表示為長度16個字符的十六進制字符串。通常aria2為每個下載鏈接產生衣蛾GID,用戶也可以通過GID選項指定。
通過XML-RPC訪問aria2
aria2.addUri([secret,?]uris[,?options[,?position]])?
添加下載的鏈接,URIS是下載鏈接數組,option,positon是一個整數,表示插在下載隊列的位置,0表示第一個。如果沒有提供position參數或者position比隊列的長度長,則添加的下載在下載隊列的最后。該方法返回新注冊下載的GID。
aria2.tellStatus([secret,?]gid[,?keys])
該方法返回指定下載GID的進展,keys是一個字符串數組,指定了需要查詢哪些項目。如果keys為空或者省略,則包含所有的項目。常用的項目有gid、status、totalLength、completedLength、downloadSpeed、uploadSpeed、numSeeders、connections、dir、files。
aria2.tellActive([secret][,?keys])
該方法查詢激活下載的狀態,查詢的項目與aria2.tellStatus類似。
aria2.removeDownloadResult([secret,?]gid)
根據GID從存儲中移除下載完成/下載錯誤/刪除的下載,如果成功返回OK
aria2.remove([secret,?]gid)
根據GID刪除下載,如果下載正在進行先停止該下載。該下載鏈接的狀態變為removed狀態。返回刪除狀態的GID。
aria2.pause([secret,?]gid)
暫停指定GID的下載鏈接,下載鏈接的狀態變為paused。如果下載是激活的,則該下載鏈接放置在等待隊列的最前面。要想將狀態變為waiting,需要用aria2.unpause方法。
download.py
# get port from persepolis_setting port = int(persepolis_setting.value('settings/rpc-port'))# get aria2_path aria2_path = persepolis_setting.value('settings/aria2_path')# xml rpc SERVER_URI_FORMAT = 'http://{}:{:d}/rpc' server_uri = SERVER_URI_FORMAT.format(host, port) server = xmlrpc.client.ServerProxy(server_uri, allow_none=True)# start aria2 with RPCdef startAria():# in Windowselif os_type == OS.WINDOWS:if aria2_path == "" or aria2_path == None or os.path.isfile(str(aria2_path)) == False:cwd = sys.argv[0]current_directory = os.path.dirname(cwd)aria2d = os.path.join(current_directory, "aria2c.exe") # aria2c.exe pathelse:aria2d = aria2_path# NO_WINDOW option avoids opening additional CMD window in MS Windows.NO_WINDOW = 0x08000000if not os.path.exists(aria2d):logger.sendToLog("Aria2 does not exist in the current path!", "ERROR")return None# aria2 command in windowssubprocess.Popen([aria2d, '--no-conf', '--enable-rpc', '--rpc-listen-port=' + str(port),'--rpc-max-request-size=2M', '--rpc-listen-all', '--quiet=true'],stderr=subprocess.PIPE,stdout=subprocess.PIPE,stdin=subprocess.PIPE,shell=False,creationflags=NO_WINDOW)time.sleep(2)# check that starting is successful or not!answer = aria2Version()# return resultreturn answer# check aria2 release version . Persepolis uses this function to # check that aria2 RPC connection is available or not.def aria2Version():try:answer = server.aria2.getVersion()except:# write ERROR messages in terminal and loglogger.sendToLog("Aria2 didn't respond!", "ERROR")answer = "did not respond"return answerdef downloadAria(gid, parent):# add_link_dictionary is a dictionary that contains user download request# information.# get information from data_baseadd_link_dictionary = parent.persepolis_db.searchGidInAddLinkTable(gid)answer = server.aria2.addUri([link], aria_dict)三、數據庫
使用sqlite3數據庫教程:https://docs.python.org/zh-cn/3/library/sqlite3.html
有三個數據庫TempDB在內存中,放置實時數據,PluginsDB放置瀏覽器插件傳來的新鏈接數據,PersepolisDB是主要的數據庫,存放下載信息。
TempDB有兩個表,single_db_table存放下載中的GID,queue_db_table存放下載隊列的GID信息。
PersepolisDB有四個表:
category_db_table存放類型信息,包括'All Downloads'、'Single Downloads'和'Scheduled Downloads'類型。
download_db_table存放主界面顯示的下載狀態表。
addlink_db_table存放下載添加界面添加的下載鏈接。
video_finder_db_table存放下載添加界面添加下載的信息。
# This class manages TempDB # TempDB contains gid of active downloads in every session. class TempDB():def __init__(self):# temp_db saves in RAM# temp_db_connectionself.temp_db_connection = sqlite3.connect(':memory:', check_same_thread=False)def createTables(self):# lock data baseself.lockCursor()self.temp_db_cursor.execute("""CREATE TABLE IF NOT EXISTS single_db_table(self.temp_db_cursor.execute("""CREATE TABLE IF NOT EXISTS queue_db_table(# persepolis main data base contains downloads information # This class is managing persepolis.db class PersepolisDB():def __init__(self):# persepolis.db file pathpersepolis_db_path = os.path.join(config_folder, 'persepolis.db')# persepolis_db_connectionself.persepolis_db_connection = sqlite3.connect(persepolis_db_path, check_same_thread=False)# queues_list contains name of categories and category settingsdef createTables(self):# lock data baseself.lockCursor()# Create category_db_table and add 'All Downloads' and 'Single Downloads' to itself.persepolis_db_cursor.execute("""CREATE TABLE IF NOT EXISTS category_db_table(# download table contains download table download items informationself.persepolis_db_cursor.execute("""CREATE TABLE IF NOT EXISTS download_db_table(# addlink_db_table contains addlink window download informationself.persepolis_db_cursor.execute("""CREATE TABLE IF NOT EXISTS addlink_db_table(# video_finder_db_table contains addlink window download informationself.persepolis_db_cursor.execute("""CREATE TABLE IF NOT EXISTS video_finder_db_table(sqlite3?模塊支持兩種占位符:問號(qmark風格)和命名占位符(命名風格)。
# This is the qmark style:
cur.execute("insert into people values (?, ?)", (who, age))
# And this is the named style:
cur.execute("select * from people where name_last=:who and age=:age", {"who": who, "age": age})
coalesce函數返回其參數中第一個非空表達式的值,也即提供了參數則用新參數,未提供新參數則用原值。
self.temp_db_cursor.execute("""UPDATE single_db_table SET shutdown = coalesce(:shutdown, shutdown),status = coalesce(:status, status)WHERE gid = :gid""", dict)MainWindow在初始化時創建CheckDownloadInfoThread線程,輪詢每一個下載中的鏈接,并將結果返回給主界面的checkDownloadInfo函數進行下載狀態更新。
# CheckDownloadInfoThreadcheck_download_info = CheckDownloadInfoThread(self)self.threadPool.append(check_download_info)self.threadPool[1].start()self.threadPool[1].DOWNLOAD_INFO_SIGNAL.connect(self.checkDownloadInfo)self.threadPool[1].RECONNECTARIASIGNAL.connect(self.reconnectAria)?
?
總結
以上是生活随笔為你收集整理的开源代码学习之persepolis【二】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: js为li列表添加点击事件
- 下一篇: React回调函数两种常用方式