开源代码学习之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【二】相关推荐
- 开源代码学习之persepolis【一】
https://github.com/persepolisdm/persepolis https://github.com/aria2/aria2 Persepolis是一款以aria2为基础打造的下 ...
- github 开源 代码 学习 集合(转载)
一个支持多种item类型的recycleView依赖注入库 1.通过注解的方式方便的把ViewHolder注入到recycleView中. 2.去除findViewByID等冗余操作. 3.去除编写a ...
- anaconda+python+pycharm代码学习——自动化办公(二)——安装mysql数据库and使用Navicat 连接 mysql
安装mysql数据库一定注意 就安装5.版本的 别安装8. 我就是因为安装了8. 才一直安装不上 5.版本的超级好安装 这里参考这篇 非常好!!! 但是成功之后cmd输入mysql 却报错 mysql ...
- x264代码学习笔记(二):x264_encoder_encode函数
encode()函数中循环调用encode_frame()函数进行逐帧编码: 调用x264_encoder_encode()函数完成一帧编码: 将编码后的码流载入码流文件中. static int e ...
- 开源代码分析技巧之三——老外如是说
开源代码分析技巧之三--老外如是说 继续从深入分析开源代码说起,当然源码分析没有太多捷径可走.笔者只是探讨下,如何分析会更好些.特通过Samba技术邮件群组,向老外提问"如何更好的分析Sam ...
- 开源代码分析技巧之四——国外技术社区提问
开源代码分析技巧之四--国外技术社区提问 在分析源码的时候,我们或多或少都会遇到过技术瓶颈.如果不突破这个瓶颈,接下来的研究就无法继续进行.并且不止对自己是瓶颈.对团队人员.技术顾问.资深人士都没有很 ...
- 杰奇reader.php源码,杰奇CMS reader.php开源代码
杰奇的reader.php开源代码,可以进行二次开发!测试可用! define('JIEQI_MODULE_NAME', 'article'); require_once('../../global. ...
- 开源代码的使用 二次开发
开源开发,就我的理解,有三种. 1.当作底层基础,使用.例如大家使用mysql就算.有人会认为我说错了.但我认为,开发不代表就是要同一个语言,甚至修改代码.例如我们使用动态库,原先的动态库是什么写的并 ...
- CSDN日报19035——流浪地球 春节十二响程序开源代码
游戏开发 | [流浪地球]春节十二响程序开源代码 作者:刺客五六柒 前几天看完流浪地球,被李长条的春节十二响惊到了,这几天看了下别的博主写的开源伪代码(借鉴了框架),试着用CMD实现了模拟的行星发动机 ...
最新文章
- AI推理与Compiler
- 【MM模块】Batch 批次管理 2
- 深入理解计算机系统(2.6)------整数的运算
- Numpy 排序 -- sort()、argsort()
- php对称算法_php里简单的对称加密算法
- JavaScript 变量、函数与原型链
- 29 | 案例篇:Redis响应严重延迟,如何解决?
- mysql的单个数据库物理迁移出现ERROR 1146 (42S02): Table 'xx' doesn't exist [问题点数:100分]...
- 团队项目电梯会议视频
- 微信开发 根据openid 获取用户基本信息
- ALIN10129-自查方案
- 调试,是一件有挑战的事情
- 人生这场牌,怎么打才是最优解?
- 【三】Jmeter:测试片段
- 服务器阵列卡装系统蓝屏,电脑开raid做系统蓝屏-电脑开机就蓝屏怎么解决?
- 云栖大会day2总结 上午
- 专升本第七部分 计算机网络基础与网页设计
- 何为功能平价?特斯拉「抛弃」多传感融合,背后有哪些门道
- 在苹果 M1 上运行 Linux 虚拟机变得容易了
- 信号偏移成为边界地区呼吸的痛