p3lib 1.1.108__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
p3lib/database_if.py ADDED
@@ -0,0 +1,289 @@
1
+ #!/usr/bin/env python
2
+
3
+ import MySQLdb as mysqldb
4
+ from datetime import datetime
5
+
6
+ class DBConfig(object):
7
+ """@brief responsible for holding the attributes if the database configuration."""
8
+
9
+ DEFAULT_TCP_PORT = 3306
10
+
11
+ def __init__(self):
12
+ self.serverAddress = ""
13
+ self.serverPort = DBConfig.DEFAULT_TCP_PORT
14
+ self.username = ""
15
+ self.password = ""
16
+ self.dataBaseName = ""
17
+ self.uio = None
18
+
19
+ class DatabaseIFError(Exception):
20
+ pass
21
+
22
+ class DatabaseIF(object):
23
+ """@brief Responsible for providing an interface to a database to allow
24
+ Creation of database
25
+ Creation of tables
26
+ Execution of database sql commands
27
+ """
28
+
29
+ """@brief Set this to True to see the SQL statements executed."""
30
+ DEBUG=False
31
+
32
+ @staticmethod
33
+ def CheckTableExists(connection, tableName):
34
+ """@brief Check if a table exists in the selected database.
35
+ @param connection The connection to the database.
36
+ @param tableName The name of the table to check for.
37
+ @return True if the table exists, False if not."""
38
+ cursor = connection.cursor()
39
+ cmd="""SELECT COUNT(*) FROM information_schema.tables WHERE table_name = '{0}'""".format(tableName.replace('\'', '\'\''))
40
+ cursor.execute(cmd)
41
+ tableExists = cursor.fetchone()[0] == 1
42
+ cursor.close()
43
+ return tableExists
44
+
45
+ @staticmethod
46
+ def GetValidColName(colName):
47
+ """@brief Get a valid database column name."""
48
+
49
+ VALID_CHAR_LIST = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_"
50
+ for aChar in colName:
51
+ if aChar not in VALID_CHAR_LIST:
52
+ colName = colName.replace(aChar, '_')
53
+
54
+ return colName
55
+
56
+ @staticmethod
57
+ def CreateTable(connection, tableName, tableSchemaDict):
58
+ """"@brief Create a table in the currently USED database..
59
+ @param connection The connection to the database.
60
+ @param param:
61
+ @param tableSchemaDict A python dictionary that defines the table schema.
62
+ Each dictionary key is the name of the column in the table.
63
+ Each associated value is the SQL definition of the column type (E.G VARCHAR(64), FLOAT(5,2) etc)."""
64
+ cursor = connection.cursor()
65
+
66
+ sqlCmd = 'CREATE TABLE IF NOT EXISTS `{}` ('.format(tableName)
67
+ for colName in list(tableSchemaDict.keys()):
68
+ colDef = tableSchemaDict[colName]
69
+ correctedColName = DatabaseIF.GetValidColName(colName)
70
+ sqlCmd = sqlCmd + "`{}` {},\n".format(correctedColName, colDef)
71
+
72
+ sqlCmd = sqlCmd[:-2]
73
+ sqlCmd = sqlCmd + ");"
74
+ DatabaseIF.ExecuteSQL(connection, cursor, sqlCmd)
75
+ cursor.close()
76
+
77
+ @staticmethod
78
+ def GetQuotedValue(value):
79
+ return '\"{}"'.format(str(value))
80
+
81
+ @staticmethod
82
+ def InsertRow(connection, tableName, myDict):
83
+ """@brief Insert a row into the table."""
84
+ cursor = connection.cursor()
85
+ timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
86
+ myDict['TIMESTAMP'] = timestamp
87
+
88
+ keyList = list(myDict.keys())
89
+ valueList = []
90
+ for key in keyList:
91
+ valueList.append(str(myDict[key]))
92
+
93
+ sql = 'INSERT INTO `' + tableName
94
+ sql += '` ('
95
+ sql += ', '.join(keyList)
96
+ sql += ') VALUES ('
97
+ sql += ', '.join(map(DatabaseIF.GetQuotedValue, valueList))
98
+ sql += ');'
99
+ DatabaseIF.ExecuteSQL(connection, cursor, sql)
100
+ cursor.close()
101
+
102
+ @staticmethod
103
+ def ExecuteSQL(connection, cursor, cmd):
104
+ try:
105
+ if DatabaseIF.DEBUG:
106
+ print('ExecuteSQL(): ',cmd)
107
+ rowsAffected = cursor.execute(cmd)
108
+ connection.commit()
109
+ except:
110
+ connection.rollback();
111
+ raise
112
+
113
+ return rowsAffected
114
+
115
+ @staticmethod
116
+ def GetInsertableDict(self, rxDict, tableSchema):
117
+ """@brief Convert the dict into a dict that can be stored in the database.
118
+ All the keys must have names that only contain characters that are valid for table columns.
119
+ Only those keys that are in the tableSchema will be inserted."""
120
+ keys = list(rxDict.keys())
121
+ convertedDict = {}
122
+ for key in keys:
123
+ colName = DatabaseIF.GetValidColName(key)
124
+ #Only add the keys that we wish to store in the database
125
+ if colName in list(tableSchema.keys()):
126
+ convertedDict[colName] = rxDict[key]
127
+ return convertedDict
128
+
129
+ def __init__(self, config):
130
+ """@brief Constructor
131
+ @param config The database configuration instance."""
132
+ self._dbConfig = config
133
+
134
+ self._dbCon = None
135
+
136
+ def _info(self, msg):
137
+ if self._dbConfig.uio:
138
+ self._dbConfig.uio.info(msg)
139
+
140
+
141
+ def _debug(self, msg):
142
+ if self._dbConfig.uio and self._dbConfig.uio.debug:
143
+ self._dbConfig.uio.debug(msg)
144
+
145
+ def connect(self):
146
+ """@brief connect to the database server."""
147
+ self._info("Connecting to {}:{} (database = {})".format(self._dbConfig.serverAddress, self._dbConfig.serverPort, self._dbConfig.dataBaseName))
148
+ self._dbCon = mysqldb.Connection(host=self._dbConfig.serverAddress,\
149
+ port=self._dbConfig.serverPort,\
150
+ user=self._dbConfig.username,\
151
+ passwd=self._dbConfig.password,\
152
+ db=self._dbConfig.dataBaseName)
153
+ self._info("Connected")
154
+
155
+ def connectNoDB(self):
156
+ """@brief connect to the database server."""
157
+ self._info("Connecting to database server @ {}:{}".format(self._dbConfig.serverAddress, self._dbConfig.serverPort))
158
+ self._dbCon = mysqldb.Connection(host=self._dbConfig.serverAddress,\
159
+ port=self._dbConfig.serverPort,\
160
+ user=self._dbConfig.username,\
161
+ passwd=self._dbConfig.password)
162
+ self._info("Connected")
163
+
164
+ def checkTableExists(self, tableName):
165
+ """@brief Check if a table exists in any database.
166
+ @param tableName The name of the table to check for.
167
+ @return True if the table exists, False if not."""
168
+ cursor = self._dbCon.cursor()
169
+ cmd="""SELECT COUNT(*) FROM information_schema.tables WHERE table_name = '{0}'""".format(tableName.replace('\'', '\'\''))
170
+ self._debug("EXECUTE SQL: {}".format(cmd))
171
+ #self.executeSQL(cmd)
172
+ cursor.execute(cmd)
173
+ tableExists = cursor.fetchone()[0] == 1
174
+ cursor.close()
175
+ return tableExists
176
+
177
+ def _createTable(self, tableName, tableSchemaDict):
178
+ """"@brief Create a table in the currently used database..
179
+ @param tableName The name of the database table.
180
+ @param tableSchemaDict A python dictionary that defines the table schema.
181
+ Each dictionary key is the name of the column in the table.
182
+ Each associated value is the SQL definition of the column type (E.G VARCHAR(64), FLOAT(5,2) etc)."""
183
+ cursor = self._dbCon.cursor()
184
+
185
+ sqlCmd = 'CREATE TABLE IF NOT EXISTS `{}` ('.format(tableName)
186
+ for colName in list(tableSchemaDict.keys()):
187
+ colDef = tableSchemaDict[colName]
188
+ correctedColName = DatabaseIF.GetValidColName(colName)
189
+ sqlCmd = sqlCmd + "`{}` {},\n".format(correctedColName, colDef)
190
+
191
+ sqlCmd = sqlCmd[:-2]
192
+ sqlCmd = sqlCmd + ");"
193
+ self.executeSQL(sqlCmd)
194
+ cursor.close()
195
+
196
+ def createTable(self, tableName, tableSchemaDict):
197
+ """@brief Create the table in the connected database.
198
+ @param tableName The table we're innterested in.
199
+ @param tableSchemaDict The schema for the table in dict form."""
200
+ self._createTable(tableName, tableSchemaDict)
201
+
202
+ def getTableRowCount(self, tableName):
203
+ """@brief Get the number of rows in a table.
204
+ @param tableName The name of the table.
205
+ @return the number of rows in the table or -1 if not found."""
206
+ count = -1
207
+ cmd = "SELECT COUNT(*) as count from {};".format(tableName)
208
+ retList = self.executeSQL(cmd)
209
+ if len(retList) > 0:
210
+ count = retList[0]['count']
211
+ return count
212
+
213
+ def deleteRows(self, tableName, rowCount):
214
+ """@brief Delete rows from a table.
215
+ @param tableName The name of the table.
216
+ @param rowCount The number of rows to delete."""
217
+ cmd = "DELETE FROM {} LIMIT {};".format(tableName, rowCount)
218
+ self.executeSQL(cmd)
219
+
220
+ def ensureTableExists(self, tableName, tableSchemaDict, autoCreate):
221
+ """@brief Check that the table a table exists in the database.
222
+ @param tableName The table we're interested in.
223
+ @param tableSchemaDict The schema for the table in dict form.
224
+ @param autoCreate Now redundant. It used to be the case that if True then auto create the table."""
225
+ self._createTable(tableName, tableSchemaDict)
226
+
227
+ def insertRow(self, dataDict, tableName, tableSchemaDict):
228
+ """@brief Insert a row into a database table.
229
+ Must have previously connected to the database.
230
+ @param dataDict The dict holding the table data
231
+ @param tableName The name of the table to insert data into
232
+ @param tableSchemaDict The schema for the database table."""
233
+ insertableDict = DatabaseIF.GetInsertableDict(tableName, dataDict, tableSchemaDict)
234
+ cursor = self._dbCon.cursor()
235
+ timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
236
+ insertableDict['TIMESTAMP'] = timestamp
237
+
238
+ keyList = list(insertableDict.keys())
239
+ valueList = []
240
+ for key in keyList:
241
+ valueList.append(str(insertableDict[key]))
242
+
243
+ sql = 'INSERT INTO `' + tableName
244
+ sql += '` ('
245
+ sql += ', '.join(keyList)
246
+ sql += ') VALUES ('
247
+ sql += ', '.join(map(DatabaseIF.GetQuotedValue, valueList))
248
+ sql += ');'
249
+ self.executeSQL(sql)
250
+ cursor.close()
251
+
252
+ def executeSQL(self, sqlCmd):
253
+ """@brief execute an SQL cmd"""
254
+ self._debug("EXECUTE SQL: {}".format(sqlCmd))
255
+ dictCursor = self._dbCon.cursor(mysqldb.cursors.DictCursor)
256
+
257
+ dictCursor.execute(sqlCmd)
258
+ resultDict = dictCursor.fetchall()
259
+ self._dbCon.commit()
260
+ return resultDict
261
+
262
+ def disconnect(self):
263
+ """@brief Disconnect from the database server."""
264
+ try:
265
+ if self._dbCon:
266
+ self._dbCon.close()
267
+ self._dbCon = None
268
+ self._info("Disconnected from {}:{}".format(self._dbConfig.serverAddress, self._dbConfig.serverPort))
269
+ except:
270
+ pass
271
+
272
+ def createDatabase(self):
273
+ """@brief Create the database"""
274
+ sql = "CREATE DATABASE `{}`;".format(self._dbConfig.dataBaseName)
275
+ self.executeSQL(sql)
276
+ self._info("Created {} database.".format(self._dbConfig.dataBaseName))
277
+
278
+ def dropDatabase(self):
279
+ """@brief Delete the database"""
280
+ sql = "DROP DATABASE `{}`;".format(self._dbConfig.dataBaseName)
281
+ self.executeSQL(sql)
282
+ self._info("Deleted {} database.".format(self._dbConfig.dataBaseName))
283
+
284
+ def dropTable(self, tableName):
285
+ """@brief Delete a table in the database
286
+ @param tableName The table to drop."""
287
+ sql = "DROP TABLE `{}`;".format(tableName)
288
+ self.executeSQL(sql)
289
+ self._info("Deleted {} database table.".format(tableName))
p3lib/file_io.py ADDED
@@ -0,0 +1,154 @@
1
+ import json
2
+ import os
3
+ from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
4
+ from cryptography.hazmat.primitives.hashes import SHA256
5
+ from cryptography.hazmat.backends import default_backend
6
+ from cryptography.fernet import Fernet
7
+ from base64 import urlsafe_b64encode
8
+
9
+ class CryptFile(object):
10
+ """@brief Responsible for encrypting and decrypting data to/from files using a password."""
11
+
12
+ def __init__(self,
13
+ filename: str,
14
+ password: str,
15
+ add_enc_extension: bool = True,
16
+ dict_data: bool = True):
17
+ """@brief Constructor
18
+ @param filename The filename to save the data into.
19
+ @param password The password to used encrypt data to and load encrypted data from file.
20
+ @param add_enc_extension If True then the .enc extension is added to the filename
21
+ supplied.
22
+ @param dict_data If True the data is a python dictionary."""
23
+ self._filename = filename
24
+ self._password = password
25
+ self._add_enc_extension = add_enc_extension
26
+ self._dict_data = dict_data
27
+
28
+ if not self._filename:
29
+ raise Exception("No filename defined to save data to.")
30
+
31
+ if len(self._filename) < 1:
32
+ raise Exception("No filename defined. String length = 0.")
33
+
34
+ if not self._password:
35
+ raise Exception("No password defined to encrypt/decrypt data.")
36
+
37
+ if len(self._password) < 1:
38
+ raise Exception("No password defined. String length = 0.")
39
+
40
+ self._add_extension()
41
+
42
+ def save(self,
43
+ data):
44
+ """@brief Save the data to an encrypted file.
45
+ @param data The data to be encrypted.
46
+ """
47
+ encrypted_data = self._encrypt_data(data)
48
+ with open(self._filename, "wb") as file:
49
+ file.write(encrypted_data)
50
+
51
+ def load(self):
52
+ """@brief Load data from an encrypted file.
53
+ @return The decrypted data.
54
+ """
55
+ with open(self._filename, "rb") as file:
56
+ data_bytes = file.read()
57
+ return self._decrypt_data(data_bytes)
58
+
59
+ def _decrypt_data(self, encrypted_data):
60
+ # Extract the salt (first 16 bytes) from the encrypted data
61
+ salt = encrypted_data[:16]
62
+ encrypted_content = encrypted_data[16:]
63
+ key = self._derive_key_from_password(salt)
64
+ fernet = Fernet(key)
65
+ decrypted_data = fernet.decrypt(encrypted_content)
66
+ if self._dict_data:
67
+ # Convert bytes back to a dict
68
+ data = json.loads(decrypted_data.decode())
69
+
70
+ else:
71
+ data = decrypted_data
72
+
73
+ return data
74
+
75
+ def get_file(self):
76
+ """@return Get the name of the encrypted file."""
77
+ return self._filename
78
+
79
+ def _add_extension(self):
80
+ """@brief Add the enc extension to the filename if required."""
81
+ if self._add_enc_extension and self._filename and not self._filename.endswith('.enc') :
82
+ self._filename = self._filename + ".enc"
83
+
84
+ def _derive_key_from_password(self,
85
+ salt: bytes) -> bytes:
86
+ kdf = PBKDF2HMAC(
87
+ algorithm=SHA256(),
88
+ length=32,
89
+ salt=salt,
90
+ iterations=100_000,
91
+ backend=default_backend(),
92
+ )
93
+ return urlsafe_b64encode(kdf.derive(self._password.encode()))
94
+
95
+ def _encrypt_data(self,
96
+ data):
97
+ # Generate a random salt for key derivation
98
+ salt = os.urandom(16)
99
+ key = self._derive_key_from_password(salt)
100
+ fernet = Fernet(key)
101
+ # If we expect a dict
102
+ if self._dict_data:
103
+ data_bytes = json.dumps(data).encode() # Convert JSON to bytes
104
+
105
+ # else check we have bytes
106
+ elif isinstance(data, bytes):
107
+ data_bytes = data
108
+
109
+ else:
110
+ raise Exception("data to be stored is not a bytes instance.")
111
+
112
+ encrypted_data = fernet.encrypt(data_bytes)
113
+ return salt + encrypted_data # Store the salt with the encrypted data
114
+
115
+ # Example usage
116
+ if __name__ == "__main__":
117
+
118
+ password = input("Enter a password for encryption: ")
119
+
120
+ # JSON data to encrypt
121
+ json_data = {
122
+ "name": "Alice",
123
+ "age": 30,
124
+ "is_admin": True,
125
+ "preferences": {
126
+ "theme": "dark",
127
+ "language": "English"
128
+ }
129
+ }
130
+
131
+ filename = "afile.txt"
132
+
133
+ # Save and load a python dict
134
+ cjf = CryptFile(filename=filename,
135
+ password=password)
136
+ cjf.save(json_data)
137
+ print(f"Saved {cjf.get_file()}")
138
+
139
+ decrypted_data = cjf.load()
140
+ print(f"Decrypted data: {decrypted_data}")
141
+
142
+
143
+ # Save and load data bytes
144
+ data_bytes = "123".encode()
145
+ cjf = CryptFile(filename=filename,
146
+ password=password,
147
+ dict_data=False)
148
+ cjf.save(data_bytes)
149
+ print(f"Saved {cjf.get_file()}")
150
+
151
+ decrypted_data = cjf.load()
152
+ print("Decrypted data:")
153
+ for _byte in decrypted_data:
154
+ print(f"_byte={_byte}")
@@ -0,0 +1,146 @@
1
+ #!/bin/env python3
2
+
3
+ import os
4
+ import sys
5
+ import platform
6
+
7
+ class GnomeDesktopApp(object):
8
+ """@brief Responsible for adding and removing gnome desktop files for launching applications on a Linux system."""
9
+
10
+ @staticmethod
11
+ def IsLinux():
12
+ """@return True if running on a Linux platform."""
13
+ linux = False
14
+ if platform.system() == "Linux":
15
+ linux = True
16
+ return linux
17
+
18
+ def __init__(self, icon_file, app_name=None, comment='', categories='Utility;Application;'):
19
+ """@brief Constructor.
20
+ @param icon_file The name of the icon file. This can be an absolute file name the filename on it's own.
21
+ If just a filename is passed then the icon file must sit in a folder named 'assets'.
22
+ This assets folder must be in the same folder as the startup file, it's parent or
23
+ the python3 site packages folder.
24
+ @param app_name The name of the application.
25
+ If not defined then the name of the program executed at startup is used.
26
+ This name has _ and - character replace with space characters and each
27
+ word starts with a capital letter.
28
+ @param comment This comment should detail what the program does and is stored
29
+ in the gnome desktop file that is created.
30
+ @param categories The debian app categories. default='Utility;Application;'.
31
+ """
32
+ if not GnomeDesktopApp.IsLinux():
33
+ raise Exception("The GnomeDesktopApp() class cannot be instantiated on a non Linux platform")
34
+ self._startup_file = self._get_startup_file()
35
+ self._gnome_desktop_file = None
36
+ self._app_name = self._get_app_name()
37
+ self._check_icon(icon_file)
38
+ self._comment = comment
39
+ self._categories = categories
40
+ self._gnome_desktop_file = self._get_gnome_desktop_file()
41
+
42
+ def _get_startup_file(self):
43
+ """@return Get the abs name of the program first started."""
44
+ return os.path.abspath(sys.argv[0])
45
+
46
+ def _get_app_name(self, app_name=None):
47
+ """@brief Get the name of the app.
48
+ @param app_name The name of the app or None. If None then the name of the app is the
49
+ basename of the startup file minus it's extension.
50
+ @return The name of the app."""
51
+ if not app_name:
52
+ # Get just the name of the file
53
+ app_name = os.path.basename(self._startup_file)
54
+ # Remove file extension
55
+ app_name = os.path.splitext(app_name)[0]
56
+ app_name = app_name.replace('_', ' ')
57
+ app_name = app_name.replace('-', ' ')
58
+ app_name = app_name.title()
59
+ return app_name
60
+
61
+ def _check_icon(self, icon_file):
62
+ """@brief Check that the icon file exists as this is required for the gnome desktop entry.
63
+ @param icon_file The name of the icon file.
64
+ return None"""
65
+ self._abs_icon_file = os.path.abspath(icon_file)
66
+ if not os.path.isfile(self._abs_icon_file):
67
+ startup_path = os.path.dirname(self._startup_file)
68
+ path1 = os.path.join(startup_path, 'assets')
69
+ self._abs_icon_file = os.path.join(path1, icon_file)
70
+ if not os.path.isfile(self._abs_icon_file):
71
+ startup_parent_path = os.path.join(startup_path, '..')
72
+ path2 = os.path.join(startup_parent_path, 'assets')
73
+ self._abs_icon_file = os.path.join(path2, icon_file)
74
+ if not os.path.isfile(self._abs_icon_file):
75
+ # Try all the site packages folders we know about.
76
+ for path in sys.path:
77
+ if 'site-packages' in path:
78
+ site_packages_path = path
79
+ path3 = os.path.join(site_packages_path, 'assets')
80
+ self._abs_icon_file = os.path.join(path3, icon_file)
81
+ if os.path.isfile(self._abs_icon_file):
82
+ return self._abs_icon_file
83
+
84
+ raise Exception(f"{self._app_name} icon file ({icon_file}) not found.")
85
+
86
+ def _get_gnome_desktop_file(self):
87
+ """@brief Determine and return the name of the gnome desktop file.
88
+ @return The gnome desktop file."""
89
+ # Get just the name of the file
90
+ desktop_file_name = os.path.basename(self._startup_file)
91
+ # Remove file extension
92
+ desktop_file_name = os.path.splitext(desktop_file_name)[0]
93
+ if not desktop_file_name.endswith('.desktop'):
94
+ desktop_file_name = desktop_file_name + '.desktop'
95
+ home_folder = os.path.expanduser("~")
96
+ gnome_desktop_apps_folder = os.path.join(home_folder, '.local/share/applications')
97
+ gnome_desktop_file = os.path.join(gnome_desktop_apps_folder, desktop_file_name)
98
+ return gnome_desktop_file
99
+
100
+ def _create_gnome_desktop_file(self):
101
+ """@brief Create the gnome desktop file for this app."""
102
+ if os.path.isfile(self._gnome_desktop_file):
103
+ raise Exception(f"{self._gnome_desktop_file} file already exists.")
104
+ lines = []
105
+ lines.append('[Desktop Entry]')
106
+ lines.append('Version=1.0')
107
+ lines.append('Type=Application')
108
+ lines.append('Encoding=UTF-8')
109
+ lines.append(f'Name={self._app_name}')
110
+ lines.append(f'Comment={self._comment}')
111
+ lines.append(f'Icon={self._abs_icon_file}')
112
+ lines.append(f'Exec={self._startup_file}')
113
+ lines.append('Terminal=false')
114
+ lines.append(f'Categories={self._categories}')
115
+
116
+ with open(self._gnome_desktop_file, "w", encoding="utf-8") as fd:
117
+ fd.write("\n".join(lines))
118
+
119
+ def create(self, overwrite=False):
120
+ """@brief Create a desktop icon.
121
+ @param overwrite If True overwrite any existing file. If False raise an error if the file is already present."""
122
+ # If this file not found error
123
+ if not os.path.isfile(self._startup_file):
124
+ raise Exception(f"{self._startup_file} file not found.")
125
+ if overwrite:
126
+ self.delete()
127
+ self._create_gnome_desktop_file()
128
+
129
+ def delete(self):
130
+ """@brief Delete the gnome desktop file if present.
131
+ @return True if a desktop file was deleted."""
132
+ deleted = False
133
+ if os.path.isfile(self._gnome_desktop_file):
134
+ os.remove(self._gnome_desktop_file)
135
+ deleted = True
136
+ return deleted
137
+
138
+ """
139
+ Example usage
140
+ gda = GnomeDesktopApp('savings.png')
141
+ gda.create(overwrite=True)
142
+ """
143
+
144
+
145
+
146
+