I am making an app that uses a .ini config file to store settings values. With PyInstaller, I am trying to create a standalone -exe file that includes the config file (to be honest, I am not sure if this is possible), or at least stores it somewhere else. Whenever I change a setting on the app, a config file is created in the same location as the .exe file.
The App running before I change a setting. Notice that no .ini config file is made.
The App running afterI change a setting. Notice that an .ini config file is made.
Code that runs the app (main.py):
class MangaDownloader(MDApp):
# This property is declared here as 'global' property, it will contain any found manga related to user input
manga_data = DictProperty(None)
# This property will be a reference to the selected manga site from which the app will download from
downloader = StringProperty(None)
# This property will check to see if a manga is being downloaded; used to show a popup if the download path is changed
is_a_manga_being_downloaded = BooleanProperty(False)
# The folder where all manga will be downloaded to, AKA: the manga root
manga_root_dir = StringProperty(None)
# The folders which will contain manga in english or Japanese
english_manga_dir, japanese_manga_dir = StringProperty(None), StringProperty(None)
def __init__(self):
super().__init__()
self.manga_root_dir = resource_path(os.path.join(self.user_data_dir, "Manga"))
self.default_settings_vals = {
'theme_mode':'Dark',
'color_scheme':'Pink',
'default_downloader': "rawdevart",
'download_path': resource_path(self.manga_root_dir),
'manga_reading_direction': 'Swipe Horizontally', # Defaults to reading horizontally (swiping)
'manga_swiping_direction':"Right to Left (English style)" # Defaults to English style: left to right
}
# Build the settings and sets their default values
def build_config(self, config):
config.setdefaults('Settings', self.default_settings_vals)
def build_settings(self, settings):
settings.add_json_panel('Manga Downloader Settings', self.config, data=AppSettings.json_settings)
# Method that builds all the GUI elements
def build(self):
self.title = "Manga Downloader"
self.dialog = None # Used to get user confirmation
# Settings
self.settings_cls = AppSettings.ScrollableSettings # Section is called 'Settings'
self.use_kivy_settings = False
# Customizable Settings
self.theme_cls.theme_style = self.config.get("Settings", "theme_mode") # Dark or Light
self.theme_cls.primary_palette = self.config.get("Settings", "color_scheme")
self.downloader = self.config.get("Settings", "default_downloader") # The default manga downloading site
# The path where all manga will be downloaded to (default is manga root)
# If the client changes the download path while a manga is being downloaded an error will pop up
self.download_path = resource_path(self.config.get("Settings", "download_path"))
self.manga_reading_direction = self.config.get("Settings", "manga_reading_direction")
self.manga_swiping_direction = self.config.get("Settings", "manga_swiping_direction")
# Manga Root Directory
# If the user has changed the default download path (AKA: the manga root path) then set the manga root to the newly set path
self.manga_root_dir = self.download_path if self.manga_root_dir != self.download_path else self.manga_root_dir
self.english_manga_dir = resource_path(os.path.join(self.manga_root_dir, "English Manga"))
self.japanese_manga_dir = resource_path(os.path.join(self.manga_root_dir, "Raw Japanese Manga"))
create_root_dir(self.manga_root_dir)
create_language_dirs([self.english_manga_dir,self.japanese_manga_dir])
# Screen related
self.screen_manager = ScreenManager()
self.landing_page = LandingPage(self)
screen = MangaScreen(name="Landing Page")
screen.add_widget(self.landing_page)
self.screen_manager.add_widget(screen)
return self.screen_manager
"""Below this comment there are a couple more functions, but they are irelevant to this question"""
# This method can handle any changes made to the settings, it also changes them when they are changed
def on_config_change(self, config, section, key, value):
print(config, section, key, value, "config change event fired")
"""
This func exists because if the client changes the download path while downloading a manga
then not all the chapters will be downloaded to the new path
"""
def change_download_path(value=value):
#root_src, new_dst = os.path.join(self.download_path), os.path.join(value)
root_src, new_dst = resource_path(self.download_path), resource_path(value)
print("src: ", root_src, "dst: ", new_dst)
try:
# Recursive function to move the english and Japanese manga containing folders to the new destination
move_manga_root(root_src,new_dst)
print(f"Download Path was successfully moved to {new_dst}")
self.manga_root_dir = self.download_path = resource_path(self.config.get("Settings", "download_path"))
toast(f"Manga Download Path has been changed to {self.manga_root_dir}")
except PermissionError:
toast("Permission Error occurred; You maybe don't have access")
except:
if root_src != new_dst:
toast("Unknown Error: If you have moved any folders/files yourself, they will appear in the new path")
# A callback function for a confirmation dialog
def reset_settings_config(inst):
if isinstance(self.dialog, MDDialog):
self.dialog.dismiss(force=True)
self.dialog = None
config.setall("Settings",self.default_settings_vals)
config.write()
change_download_path(resource_path(self.default_settings_vals.get("download_path")))
self.close_settings()
self.destroy_settings()
self.open_settings()
# This section will reset all settings to their default values
if key == "configchangebuttons":
self.dialog = None
if not self.dialog:
self.dialog = ConfirmationDialog(
title= "Reset to Factory Settings Confirmation: ",
text= "Warning: This will remove all current settings!\nAny Downloaded Manga will be moved to the default download folder!",
proceed_button_callback = reset_settings_config)
self.dialog.open()
# Moves the root/download folder to the new path
if key == "download_path" and os.path.isdir(resource_path(os.path.join(value))):
if self.is_a_manga_being_downloaded:
# I have given up on this; it is not a part of my requirements
toast("Warning: The download path has been changed while a manga is being downloaded. All new chapters will be downloaded to the new path")
change_download_path()
self.theme_cls.theme_style = self.config.get("Settings", "theme_mode")
self.theme_cls.primary_palette = self.config.get("Settings", "color_scheme")
#self.pc_download_path = self.config.get("Settings", "PCDownloadPath")
#self.android_download_path = self.config.get("Settings", "AndroidDownloadPath")
#self.download_path = self.config.get("Settings", "download_path")
self.downloader = self.config.get("Settings", "default_downloader")
if key == "manga_swiping_direction": self.manga_swiping_direction = self.config.get("Settings", "manga_swiping_direction")
if key == "manga_reading_direction": self.manga_reading_direction = self.config.get("Settings", "manga_reading_direction")
if __name__ == "__main__":
if hasattr(sys, '_MEIPASS'):
resource_add_path(os.path.join(sys._MEIPASS))
if getattr(sys, 'frozen', False):
# this is a Pyinstaller bundle
resource_add_path(sys._MEIPASS)
resource_add_path(os.path.join(sys._MEIPASS, 'DATA'))
MangaDownloader().run()
Here is the code I used to create my settings
class AppSettings:
json_settings = json.dumps([
{'type': 'title', 'title': 'Color Scheme and Theme Settings'},
{'type': 'scrolloptions',
'title': 'Theme',
'desc': 'Dark theme or Light theme',
'section': 'Settings',
'key': 'theme_mode',
'options': ['Dark', 'Light']
},
{'type': 'scrolloptions',
'title': 'Color scheme',
'desc': 'This settings affects the color of: Text, borders... but not the theme(light or dark)',
'section': 'Settings',
'key': 'color_scheme',
'options': ['Red', 'Pink', 'Purple', 'DeepPurple', 'Indigo', 'Blue', 'LightBlue', 'Cyan', 'Teal', 'Green', 'LightGreen', 'Lime', 'Yellow', 'Amber', 'Orange', 'DeepOrange', 'Brown', 'Gray', 'BlueGray']
},
{'type': 'title','title': 'Download Settings'},
{'type': 'scrolloptions',
'title': 'Default Manga Site',
'desc': 'The default site that will be used to download manga',
'section': 'Settings',
'key': 'default_downloader',
'options': ["manganelo", "kissmanga", "rawdevart", "senmanga"]
},
{'type': 'path',
'title': 'Download Folder Path',
'desc': "All downloaded manga will be found in this folder. It will have 2 sub folders for English and Japanese manga",
'section': 'Settings',
'key': 'download_path'
},
{'type': 'title','title': 'Manga Reader Settings'},
{'type': 'scrolloptions',
'title': 'Manga Reading Direction',
'desc': 'Turn on to scroll vertically while reading. Turn off to swipe horizontally for reading',
'section': 'Settings',
'key': 'manga_reading_direction',
'options' : ["Scroll vertically", "Swipe Horizontally"],
},
{'type': 'scrolloptions',
'title': 'Manga Reading Swipe Direction for page turning',
'desc': 'The Japanese way of reading manga is: Left to Right. The English way is: Right to Left.',
'section': 'Settings',
'key': 'manga_swiping_direction',
'options':["Left to Right (Japanese style)", "Right to Left (English style)"],
},
{'type':'title', 'title':'Misc.'},
{"type": "buttons",
"title": "Reset Settings",
"desc": "Reset the settings to their default values",
"section": "Settings",
"key": "configchangebuttons",
"buttons":[{"title":"Reset Settings","id":"reset_settings_btn"}]
},
])
class SettingScrollOptions(SettingOptions):
def _create_popup(self, instance):
# global oORCA
# create the popup
content = GridLayout(cols=1, spacing='5dp')
scrollview = ScrollView(do_scroll_x=False)
scrollcontent = GridLayout(cols=1, spacing='5dp', size_hint=(None, None))
scrollcontent.bind(minimum_height=scrollcontent.setter('height'))
self.popup = popup = Popup(content=content, title=self.title, size_hint=(0.5, 0.9), auto_dismiss=False)
# we need to open the popup first to get the metrics
popup.open()
# Add some space on top
content.add_widget(Widget(size_hint_y=None, height=dp(2)))
# add all the options
uid = str(self.uid)
for option in self.options:
state = 'down' if option == self.value else 'normal'
btn = ToggleButton(text=option, state=state, group=uid, size=(popup.width, dp(55)), size_hint=(None, None), on_release=self._set_option)
scrollcontent.add_widget(btn)
# finally, add a cancel button to return on the previous panel
scrollview.add_widget(scrollcontent)
content.add_widget(scrollview)
content.add_widget(SettingSpacer())
# btn = Button(text='Cancel', size=((oORCA.iAppWidth/2)-sp(25), dp(50)),size_hint=(None, None))
btn = Button(text='Cancel', size=(popup.width, dp(50)), size_hint=(0.9, None), on_release=popup.dismiss)
content.add_widget(btn)
class SettingButtons(SettingItem):
def __init__(self, **kwargs):
# For Python3 compatibility we need to drop the buttons keyword when calling super.
kw = kwargs.copy()
kw.pop('buttons', None)
super(SettingItem, self).__init__(**kw)
for aButton in kwargs["buttons"]:
oButton=Button(text=aButton['title'], font_size= '15sp', on_release=self.on_button_press)
oButton.ID=aButton['id']
self.add_widget(oButton)
def set_value(self, section, key, value):
# set_value normally reads the configparser values and runs on an error
# to do nothing here
return
def on_button_press(self,instance):
self.panel.settings.dispatch('on_config_change',self.panel.config, self.section, self.key, instance.ID)
class ScrollableSettings(SettingsWithSidebar):
def __init__(self, *args, **kwargs):
super().__init__(**kwargs)
self.content_panel = self.interface.children[0]
self.content_panel.bar_width = "10dp"
self.register_type('scrolloptions', AppSettings.SettingScrollOptions)
self.register_type('buttons', AppSettings.SettingButtons)
I was able to figure it out. I simply needed to add a method called get_application_config(self)
to the class with my build()
method.
I have included a snippet to show where to put it.
# Build the settings and sets their default values
def build_config(self, config):
config.setdefaults('Settings', self.default_settings_vals)
def get_application_config(self):
# This defines the path and name where the ini file is located
#return str(os.path.join(os.path.expanduser('~'), 'mangadownloader.ini'))
return str(os.path.join(self.user_data_dir, 'mangadownloader.ini'))
def build_settings(self, settings):
settings.add_json_panel('Manga Downloader Settings', self.config, data=AppSettings.json_settings)