-1

I built my app, which has access to some files in its folder, which it can change (rewrite). Exactly they are config.ini and save file. When I run it in VS Code everything works perfectly, and when I run it in the directory "dist" it also works perfectly. But when I made a setup file through InstallForge I got the problem. In the first run it can change these files, but in the second - no.

I tried adding directories instead of relative path and remove deleting files replacing it with rewriting them as empty, but it didn't work. I added these files in --add-data in pyinstaller, and still didn't work. When I run it second time and try to save config it crashes, and when I try to save save file it gives an error: [Errno 13] Permission Denied. The app installs itself in the Program Files x86 folder. The code of saving config in the app.py (pyqt6 app):

def save(self):
        global smtp_server, port, email, password, frequency, method, server_ip, time, TIMES

        email_s = self.email_line.text().strip()
        password_s = self.password_line.text().strip()
        smtp_server_s = self.smtp_server_line.text().strip()
        port_s = self.port_chose.currentText()
        frequency_s = self.check_frequency.currentIndex()
        method_s = self.check_method.currentIndex()
        server_ip_s = self.ip_server.currentIndex()

        if email_s is None or not validate_email(email_s):
            email_broken = QMessageBox(self)
            email_broken.setWindowTitle("Ошибка сохранения")
            email_broken.setText("Введенный email недействителен!")
            email_broken.setIcon(QMessageBox.Icon.Critical)
            email_broken.setWindowIcon(QIcon(LOGO))
            button = email_broken.exec()
        
        elif (password_s is None) or (password_s == "") or (password_s == " "):
            password_broken = QMessageBox(self)
            password_broken.setWindowTitle("Ошибка записи")
            password_broken.setText("Введенный пароль недействителен!")
            password_broken.setIcon(QMessageBox.Icon.Critical)
            password_broken.setWindowIcon(QIcon(LOGO))
            button = password_broken.exec()

        elif (smtp_server_s is None) or (smtp_server_s == "") or (" " in smtp_server_s):
            smtp_broken = QMessageBox(self)
            smtp_broken.setWindowTitle("Ошибка записи")
            smtp_broken.setText("Введенный smtp-сервер недействителен!")
            smtp_broken.setIcon(QMessageBox.Icon.Critical)
            smtp_broken.setWindowIcon(QIcon(LOGO))
            button = smtp_broken.exec()

        else:
            create_config(smtp_server_s, port_s, email_s, password_s, frequency_s, method_s, server_ip_s)
            smtp_server = smtp_server_s
            port = int(port_s)
            email = email_s
            password = password_s
            frequency = frequency_s
            method = int(method_s)
            server_ip = int(server_ip_s)
            time = TIMES[frequency]
            save_dialog = QMessageBox(self)
            save_dialog.setWindowTitle("Сохранение настроек")
            save_dialog.setText("Изменения сохранены")
            save_dialog.setStandardButtons(QMessageBox.StandardButton.Ok)
            save_dialog.setIcon(QMessageBox.Icon.Information)
            save_dialog.setWindowIcon(QIcon(LOGO))
            button = save_dialog.exec()
            if button == QMessageBox.StandardButton.Ok:
                pass

And the code of creating config.ini:

def create_config(server, port, login, password, frequency, method, ip_server):
    global CONFIG_FILE
    config = configparser.ConfigParser()
    config.add_section("ACCOUNT")
    config.add_section("SETTINGS")
    config.set("ACCOUNT", "server", server)
    config.set("ACCOUNT", "port", str(port))
    config.set("ACCOUNT", "login", login)
    config.set("ACCOUNT", "password", password)
    config.set("SETTINGS", "frequency", str(frequency))
    config.set("SETTINGS", "method", str(method))
    config.set("SETTINGS", "ip_server", str(ip_server))
    with open(CONFIG_FILE, "w") as config_file:
        config.write(config_file)

The code of saving save file:

def create_save_file(ip, email, server):
    file = open(SAVE_FILE, "w")
    file.write(f"{ip}\n{email}\n{server}")
    file.close()

I also declare constants of file paths:

BASEDIR = path.dirname(__file__)
CONFIG_FILE = path.join(BASEDIR, "config.ini")
SAVE_FILE = path.join(BASEDIR, "save")
3
  • User configuration files should never be saved in the program directory. You should use the paths the system provides instead (see QStandardPaths). Also, consider using QSettings. Commented Jun 18 at 12:35
  • pyinstaller doesn't install program. It creates temporary folder, extract all data and code and run it, and when you close program then it deletes temporary folder with all files inside. If you want to save data then you should do it in user's home folder, not in application's folder. Commented Jun 18 at 12:37
  • It may need something like os.path.expandvars('$HOME') to get user's home folder, or even os.path.expandvars('$HOME/folder_for_your_settings') and later mkdirs() to create own folder for settings Commented Jun 18 at 12:42

1 Answer 1

0

The way to fix this problem is to use Appdata folder for all changing files, as it's done in most of programmes. So I implemented the next code:

APPDATADIR = path.join(getenv('APPDATA'), "Company", "App")
if not(path.exists(APPDATADIR)):
    makedirs(APPDATADIR)
CONFIG_FILE = path.join(APPDATADIR, "config.ini")
SAVE_FILE = path.join(APPDATADIR, "save")
FIRST_START_DIR = path.join(APPDATADIR, "firststart")
IS_FIRST_START = not(path.exists(FIRST_START_DIR))

Now it saves everything in the Appdata, and the file for evaluating the first start also was moved.

Sign up to request clarification or add additional context in comments.

1 Comment

This is one way, not the way. As already written in the comments to your question, using QSettings and QStandardPaths can do all that more easily, also making the program ready for cross-platform requirements, in case you'll ever need it; it doesn't mean that it's always the preferred/better way, as it obviously depends on your needs, but you should be aware of it and consider it no matter what. OTOH, don't use globals unless you really know what you're doing.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.