开源代码学习之persepolis【一】

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

from server import db

class User(db.Model):

  pass

在server.py中,

1

2

3

4

5

6

from flask import Flask

from flask.ext.sqlalchemy import SQLAlchemy

app = Flask(__name__)

app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:tmp/test.db'

db = SQLAlchemy(app)

from models import User

这样就产生了循环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

 

猜你喜欢

转载自blog.csdn.net/bluewhu/article/details/104388200