Persepolis of open source code learning [1]

Persepolis is a download management GUI based on aria2. It is an open source free cross-platform software written in python and pyqt5, supporting GNU/Linux, BSDs, MacOS, and Microsoft Windows. Simply put, Persepolis adds an interface to aria2. Here we mainly learn the usage of python and pyqt5.

1. Start the software


os_type = platform.system()   #获取操作系统类型
cwd = os.path.abspath(__file__) #程序绝对路径D:\Users\persepolis-master\test\
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上一级目录的模块

The meaning comes from 

python import module will go to sys.path to search, sys.path is a list, and we can modify it dynamically. To import a module of a certain directory, we add sys.path.insert(0,somedir) to the search path, and then import it. In this case, to import the module of the upper level directory, you can sys.path.insert(0, parentdir). But this way of writing an absolute path will not work if the file is placed elsewhere. So use the dynamic method to get the upper level directory. Why use sys.path.insert(0,parentdir) instead of sys.path.append(parentdir)?
Because it traverses the search path, if there is a module with the same name in other paths, the import will be wrong. Use sys.path.insert(0, parentdir) to ensure that this path is searched first.

Solve the problem of circular import
In python, the problem of circular import, or circular import, is often encountered.
This funny situation often occurs in reality. When installing a wireless network card, you need to download the network card driver from the Internet. When installing the compression software, the installation program of the compression software downloaded from the Internet is actually compressed.. Cyclic dependency is similar to This situation.
for example,





from server import db

class User(db.Model):









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

This creates the problem of circular import. Models need a from server import db,而server and from models import User。
there are several ways to solve circular import.
1. Lazy import
means that the import statement is written in the method or function, and its scope is limited to the local.
The disadvantage of this method is that there will be performance issues.
2. Change from xxx import yyy to import xxx;xxx.yyy to access the form
3. Organize the code
The problem of circular import often means that there is a problem with the layout of the code.
Competing resources can be combined or separated.
In the case of merging, they are all written into one file.
The separation is to extract the resources that need to be imported to a third-party file.
In short, the cycle becomes one-way.


Two, initialization


1. Configuration file path

elif os_type == OS.WINDOWS:
    config_folder = os.path.join(
        home_address, 'AppData', 'Local', 'persepolis_download_manager')

2. Single instance operation

The method commonly used in development to allow only one instance to run is to create a mutex. Since the mutex only allows one process or thread to occupy, otherwise it will fail to create, use this feature to achieve singleton operation.
CreateMutex finds out whether an instance of the specified process already exists in the current system. If not, create a mutex. The CreateMutex() function can be used to create a named or unnamed mutex object.

HANDLE CreateMutex(   
LPSECURITY_ATTRIBUTES lpMutexAttributes, // pointer to security attributes   
BOOL bInitialOwner, // initialize the owner of the mutex object   
LPCTSTR lpName // pointer to the name of the mutex object);

The return value is   
Long. If the execution succeeds, it returns the handle of the mutex object; zero means an error. GetLastError will be set.
Even if it returns a valid handle, if the specified name already exists, GetLastError will be set to ERROR_ALREADY_EXISTS

lpMutexAttributes SECURITY_ATTRIBUTES, specify a SECURITY_ATTRIBUTES structure, or pass a zero value (declare the parameter as ByVal As Long, and pass a zero value), which means to use the default descriptor that does not allow inheritance.

bInitialOwner BOOL, set to TRUE if the creating process wants to have the mutex immediately. A mutex can only be owned by one thread at a time. If it is FALSE, it means that the Mutex just created does not belong to any thread, that is, no thread owns it.

lpName String, specifies the name of the mutex object. If a mutex with this name already exists, open the existing named mutex. This name may not match the existing event, signal, waitable timer or file mapping, otherwise the GetLastError function will return  ERROR_INVALID_HANDLE if the execution fails . The name can have a "Global\" or "Local\" prefix, to explicitly establish an object in the global or session namespace. The remaining name can contain any characters except the backslash character (\).

        The mechanism of mutual exclusion is adopted. Mutex, like an object, this object can only be held by one thread at the same time. Only the thread that owns the mutex has the right to access the public resource. Because there is only one mutex, it can be guaranteed that the public resource will not be accessed by multiple threads at the same time. Mutual exclusion can not only realize the safe sharing of public resources of the same application, but also realize the safe sharing of public resources of different applications.
        1. The method to create a mutex is to call the function CreateMutex: CreateMutex(&sa, bInitialOwner, szName); The first parameter is a pointer to the SECURITY_ATTRIBUTES structure. In general, it can be nullptr. The second parameter type is BOOL, which indicates whether the mutex is held by the current thread after it is created. The third parameter type is a string (const TCHAR*), which is the name of this mutex. If it is nullptr, the mutex is anonymous. Example: HANDLE hMutex = CreateMutex(nullptr, FALSE, nullptr); The above code creates an anonymous mutex, after it is created, the current thread does not hold this mutex.

         2. Holding the WaitForSingleObject function allows a thread to hold the mutex lock. Usage: WaitForSingleObject(hMutex, dwTimeout); This function has more functions. Here only introduces the role when the first parameter is the handle of the mutex lock. Its function is to wait until a certain period of time later, or other threads do not hold hMutex. The second parameter is the waiting time (unit: milliseconds). If the parameter is INFINITE, the function will wait forever.

        Third, release the ReleaseMutex function allows the current thread to "release" a mutex lock (not holding it) so that other threads can hold it. Usage ReleaseMutex (hMutex)

         4. Destruction When the program no longer needs the mutex, it must be destroyed. CloseHandle(hMutex)

         Five, named mutex If the third parameter of the CreateMutex function is passed a string, then the created lock is named. When a named lock is created, if the current process and other processes try to create a lock with the same name, CreateMutex will return the handle of the original lock, and the GetLastError function will return ERROR_ALREADY_EXISTS. This feature allows a program to run at most one instance at the same time.

Original link:

    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")
        lock_file_validation = True
        print('start app')


3. QSettings initialization

Users often have such requirements for an application: it is required to remember its settings, such as window size, position, and some other settings. There is also a frequently used one, recent files, etc. These can all be achieved through Qsettings .

 We know that these settings are generally stored in the system. For example, windows are generally written in the system registry or written in INI files. Mac systems are generally written in XML files. According to general standards, many applications use INI. File to achieve. And Qsettings provides a convenient way to store and restore application settings.

QSettings API is based on Qvariant, Qvariant is a collection of data types, which contains most of the usual Qt data types, such as QString, QRec, QImage, and so on.

When we create a Qsettings object, we need to pass to it two parameters, the first is the name of the company or organization, and the second is the name of the application. If it is an ini file, you also need to specify the file name and format.

# load persepolis_settings

persepolis_setting = QSettings('persepolis_download_manager', 'persepolis')

Then, if you want to declare a variable of type Qsettings anywhere in the application, you don't need to write two parameters, just use settings = Qsettings().
persepolis_download_manager.setting = QSettings()

So how to use it to keep the settings information of the application? Take AboutWindow initialization as an example:

size = self.persepolis_setting.value('AboutWindow/size', QSize(545, 375))

This means that if there is a previously saved AboutWindow/size value in the settings, it will be read, and if not, the default value QSize(545, 375) will be used.

When AboutWindow is closed, save self.size() and self.pos() in'AboutWindow/size' and'AboutWindow/position' respectively.

    def __init__(self, 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())

Commonly used methods in Qsettings:

   Qsettings.allKeys(self) returns all keys in the form of a list

   Qsettings.applicationName(self) returns the application name

   Qsettings.clear(self) Clear the contents of this settings

   Bool Qsettings.contains(self,key) returns true if the name key exists

   Qsettings.remove(self, keyname) clear the key and its corresponding value

   Qsetting.fileName() returns the address written to the registry or the path of the INI file


The following document describes how to update the recent file list:


Three, interface initialization


PersepolisApplication inherits from QApplication and only defines a few window change methods.

MainWindow inherits from MainWindow_Ui, is the main window displayed on the interface, and defines various operations on the interface.

mainwindow = MainWindow(start_in_tray, persepolis_download_manager, persepolis_download_manager.setting)

The first parameter indicates whether to display in the tray, if it is true, the initial state is hidden.

The second parameter persepolis_download_manager is a PersepolisApplication. After being passed to the main window, it has not only been saved, but no use has been found.

self.persepolis_main = persepolis_main

The third parameter persepolis_download_manager.setting = QSettings() is the system setting

class PersepolisApplication(QApplication):
    def __init__(self, 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()
            file = QFile(":/dark_style.qss")
   | QFile.Text)
            stream = QTextStream(file)


# 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):
        self.persepolis_setting = persepolis_setting
        self.persepolis_main = persepolis_main

class MainWindow_Ui(QMainWindow):
    def __init__(self, persepolis_setting):
        # MainWindow
        self.persepolis_setting = persepolis_setting


Guess you like