https://github.com/persepolisdm/persepolis
https://github.com/aria2/aria2
Persepolis是一款以aria2为基础打造的下载管理GUI,他是用python和pyqt5写的开源免费跨平台软件,支持GNU/Linux , BSDs, MacOS, 和Microsoft Windows。简单说Persepolis就是给aria2加了个界面,这里主要学习python和pyqt5的用法。
一、启动软件
\persepolis-master\test\test.py
os_type = platform.system() #获取操作系统类型
cwd = os.path.abspath(__file__) #程序绝对路径D:\Users\persepolis-master\test\test.py
run_dir = os.path.dirname(cwd) #程序文件目录D:\Users\persepolis-master\test
parent_dir = os.path.dirname(run_dir) #程序文件父目录D:\Users\persepolis-master,os.path.dirname可以一层一层往上找目录
sys.path.insert(0, parent_dir) #import上一级目录的模块
含义来自https://www.jb51.net/article/85867.htm
python import module会去sys.path搜索,sys.path是个列表,并且我们可以动态修改。 要import某个目录的module,我们sys.path.insert(0,somedir)来加入搜索路径,就可以import了。 既然这样,要import上一级目录的module,可以sys.path.insert(0,parentdir)。 不过这种写绝对路径的方式,如果文件放到其它地方,就不行了。 所以用动态方法来获取上一级目录。为什么用sys.path.insert(0,parentdir) 而不是用sys.path.append(parentdir)呢
因为是遍历搜索路径的,所以如果在其它路径里也有个同名的module,会import错。用sys.path.insert(0,parentdir)可以确保先搜索这个路径。
解决循环import的问题
在python中常常会遇到循环import即circular import的问题。
现实中经常出现这种滑稽的情况,安装无线网卡的时候,需要上网下载网卡驱动..安装压缩软件的时候,从网上下载的压缩软件安装程序居然是被压缩了的..循环依赖就类似于这种情况。
举个例子,
在models.py中,
1 2 3 |
|
在server.py中,
1 2 3 4 5 6 |
|
这样就产生了循环import的问题,models需要from
server
import
db,而
server又需要from
models
import
User。
解决循环import的方法主要有几种。
1.延迟导入(lazy import)
即把import语句写在方法或函数里面,将它的作用域限制在局部。
这种方法的缺点就是会有性能问题。
2.将from xxx import yyy改成import xxx;xxx.yyy来访问的形式
3.组织代码
出现循环import的问题往往意味着代码的布局有问题。
可以合并或者分离竞争资源。
合并的话就是都写到一个文件里面去。
分离的话就是把需要import的资源提取到一个第三方文件去。
总之就是将循环变成单向。
二、初始化
\persepolis-master\persepolis\scripts\persepolis.py
1、配置文件路径
elif os_type == OS.WINDOWS:
config_folder = os.path.join(
home_address, 'AppData', 'Local', 'persepolis_download_manager')
2、单实例运行
开发中常用的只允许一个实例运行的办法,创建一个互斥量。由于互斥量只允许一个进程或者线程占用,否则会创建失败,利用这个特性可以做到单例运行。
CreateMutex找出当前系统是否已经存在指定进程的实例。如果没有则创建一个互斥体。CreateMutex()函数可用来创建一个有名或无名的互斥量对象。
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes, // 指向安全属性的指针
BOOL bInitialOwner, // 初始化互斥对象的所有者
LPCTSTR lpName // 指向互斥对象名的指针 );
返回值
Long,如执行成功,就返回互斥体对象的句柄;零表示出错。会设置GetLastError。
即使返回的是一个有效句柄,但倘若指定的名字已经存在,GetLastError也会设为ERROR_ALREADY_EXISTS
lpMutexAttributes SECURITY_ATTRIBUTES,指定一个SECURITY_ATTRIBUTES结构,或传递零值(将参数声明为ByVal As Long,并传递零值),表示使用不允许继承的默认描述符。
bInitialOwner BOOL,如创建进程希望立即拥有互斥体,则设为TRUE。一个互斥体同时只能由一个线程拥有。是FALSE,表示刚刚创建的这个Mutex不属于任何线程也就是没有任何线程拥有他。
lpName String,指定互斥体对象的名字。如已经存在拥有这个名字的一个互斥体,则打开现有的已命名互斥体。这个名字可能不与现有的事件、信号机、可等待计时器或文件映射相符,否则执行失败GetLastError函数返回 ERROR_INVALID_HANDLE。该名称可以有一个"Global\" 或"Local\" 前缀,明确地建立在全局或会话命名空间的对象。剩余的名称可以包含任何字符,除反斜杠字符(\)。
互斥量:
采用互斥对象机制。互斥锁,像一个物件,这个物件只能同时被一个线程持有。 只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程访问。互斥不仅能实现同一应用程序的公共资源安全共享,还能实现不同应用程序的公共资源安全共享。
一、创建 创建互斥锁的方法是调用函数CreateMutex: CreateMutex(&sa, bInitialOwner, szName);第一个参数是一个指向SECURITY_ATTRIBUTES结构体的指针,一般的情况下,可以是nullptr。 第二个参数类型为BOOL,表示互斥锁创建出来后是否被当前线程持有。 第三个参数类型为字符串(const TCHAR*),是这个互斥锁的名字,如果是nullptr,则互斥锁是匿名的。 例: HANDLE hMutex = CreateMutex(nullptr, FALSE, nullptr);上面的代码创建了一个匿名的互斥锁,创建出来后,当前线程不持有这个互斥锁。
二、持有 WaitForSingleObject函数可以让一个线程持有互斥锁。用法: WaitForSingleObject(hMutex, dwTimeout);这个函数的作用比较多。这里只介绍第一个参数为互斥锁句柄时的作用。 它的作用是等待,直到一定时间之后,或者,其他线程均不持有hMutex。第二个参数是等待的时间(单位:毫秒),如果该参数为INFINITE,则该函数会一直等待下去。
三、释放 用ReleaseMutex函数可以让当前线程“放开”一个互斥锁(不持有它了),以便让其他线程可以持有它。用法 ReleaseMutex(hMutex)
四、销毁 当程序不再需要互斥锁时,要销毁它。 CloseHandle(hMutex)
五、命名互斥锁 如果CreateMutex函数的第三个参数传入一个字符串,那么所创建的锁就是命名的。当一个命名的锁被创建出来以后,当前进程和其他进程如果试图创建相同名字的锁,CreateMutex会返回原来那把锁的句柄,并且GetLastError函数会返回ERROR_ALREADY_EXISTS。这个特点可以使一个程序在同一时刻最多运行一个实例。
原文链接:https://blog.csdn.net/enterlly/article/details/79158920
from win32event import CreateMutex
from win32api import GetLastError
from winerror import ERROR_ALREADY_EXISTS
from sys import exit
handle = CreateMutex(None, 1, 'persepolis_download_manager')
if GetLastError() == ERROR_ALREADY_EXISTS:
lock_file_validation = False
print("already exit")
else:
lock_file_validation = True
print('start app')
3、QSettings初始化
用户对应用程序经常有这样的要求:要求它能记住它的settings,比如窗口大小,位置,一些别的设置,还有一个经常用的,就是recent files,等等这些都可以通过Qsettings来实现。
我们知道,这些settings一般都是存在系统里的,比如windows一般都写在系统注册表或者写INI文件,mac系统一般都在XML文件里,那么按照一般的标准来说,许多应用程序是用INI文件来实现的。而Qsettings就是提供了一种方便的方法来存储和恢复应用程序的settings。
QSettings的API是基于Qvariant,Qvariant是一种数据类型的集合,它包含了大部分通常的Qt数据类型,比如QString,QRec,QImage,等等。
当我们创建一个Qsettings的对象时,我们需要传递给它两个参数,第一个是公司或者组织的名称,第二个是应用程序的名称。如果为ini文件,还需要指定文件名称和格式。
# load persepolis_settings
persepolis_setting = QSettings('persepolis_download_manager', 'persepolis')
然后在应用程序的任何地方想要声明一个Qsettings类型的变量,便不需要书写两个参数了,直接用 settings = Qsettings()即可。
persepolis_download_manager.setting = QSettings()
那么如何用它来保持应用程序的settings信息呢?以AboutWindow初始化为例:
size = self.persepolis_setting.value('AboutWindow/size', QSize(545, 375))
意思是,如果settings里有以前存下的AboutWindow/size值则读取,如果没有则会使用默认值QSize(545, 375)。
AboutWindow关闭时,将self.size()和self.pos()分别保存在'AboutWindow/size'和'AboutWindow/position'中。
def __init__(self, persepolis_setting):
super().__init__(persepolis_setting)
self.persepolis_setting = persepolis_setting
# setting window size and position
size = self.persepolis_setting.value(
'AboutWindow/size', QSize(545, 375))
position = self.persepolis_setting.value(
'AboutWindow/position', QPoint(300, 300))
def closeEvent(self, event):
# saving window size and position
self.persepolis_setting.setValue('AboutWindow/size', self.size())
self.persepolis_setting.setValue('AboutWindow/position', self.pos())
self.persepolis_setting.sync()
event.accept()
Qsettings里常用的方法:
Qsettings.allKeys(self) 返回所有的key,以list的形式
Qsettings.applicationName(self) 返回应用程序名称
Qsettings.clear(self) 清除此settings里的内容
Bool Qsettings.contains(self,key) 如果存在名为key则返回真
Qsettings.remove(self, keyname) 清除key及其所对应的value
Qsetting.fileName() 返回写入注册表地址,或者INI文件路径
下面的文档介绍了最近文件列表的更新方法:
https://cloud.tencent.com/developer/article/1487068
三、界面初始化
PersepolisApplication继承自QApplication,只定义了几个窗口改变的方法。
MainWindow继承自MainWindow_Ui,是界面显示的主窗口,定义了界面上各种操作。
mainwindow = MainWindow(start_in_tray, persepolis_download_manager, persepolis_download_manager.setting)
第一个参数表示是否在托盘中显示,如果为真则初始状态为隐藏。
第二个参数persepolis_download_manager是一个PersepolisApplication,传递到主窗口后未只进行了保存,未找到哪里有使用
self.persepolis_main = persepolis_main
第三个参数persepolis_download_manager.setting = QSettings()为系统设置
class PersepolisApplication(QApplication):
def __init__(self, argv):
super().__init__(argv)
def setPersepolisStyle(self, style):
# set style
def setPersepolisFont(self, font, font_size, custom_font):
# font and font_size
# color_scheme
def setPersepolisColorScheme(self, color_scheme):
self.persepolis_color_scheme = color_scheme
if color_scheme == 'Dark Fusion':
dark_fusion = DarkFusionPalette()
self.setPalette(dark_fusion)
file = QFile(":/dark_style.qss")
file.open(QFile.ReadOnly | QFile.Text)
stream = QTextStream(file)
self.setStyleSheet(stream.readAll())
...
# create QApplication
persepolis_download_manager = PersepolisApplication(sys.argv)
persepolis_download_manager.setting = QSettings()
mainwindow = MainWindow(start_in_tray, persepolis_download_manager, persepolis_download_manager.setting)
class MainWindow(MainWindow_Ui):
def __init__(self, start_in_tray, persepolis_main, persepolis_setting):
super().__init__(persepolis_setting)
self.persepolis_setting = persepolis_setting
self.persepolis_main = persepolis_main
class MainWindow_Ui(QMainWindow):
def __init__(self, persepolis_setting):
super().__init__()
# MainWindow
self.persepolis_setting = persepolis_setting