1
+ < << << << HEAD
1
2
from .Updater_OP import *
2
3
from .Updater import engine
3
4
import bpy
17
18
# version of the addon and provides options to check for updates and update the addon.
18
19
19
20
21
+ == == == =
22
+ import datetime
23
+ import os
24
+ import zipfile
25
+ import io
26
+ import json
27
+ import webbrowser
28
+ import requests
29
+ import shutil
30
+ import bpy
31
+ bl_info = {
32
+ "name" : "KUpdater" ,
33
+ "description" : "KUpdater allows users to directly update their addons in blender." ,
34
+ "author" : "Kent Edoloverio" ,
35
+ "blender" : (3 , 5 , 1 ),
36
+ "version" : (1 , 3 , 1 ),
37
+ "category" : "3D View" ,
38
+ "location" : "3D View > KUpdater" ,
39
+ "warning" : "" ,
40
+ "wiki_url" : "https://github.com/kents00/KNTY-Updater" ,
41
+ "tracker_url" : "https://github.com/kents00/KNTY-Updater/issues" ,
42
+ }
43
+
44
+
45
+ class GithubEngine :
46
+ def __init__ (self ):
47
+ self ._api_url = 'https://api.github.com'
48
+ self ._token = None
49
+ self ._user = None
50
+ self ._repo = None
51
+ self ._current_version = None
52
+ self ._latest_version = None
53
+ self ._response = None
54
+ self ._update_date = None
55
+
56
+ def clear_state (self ):
57
+ self ._token = None
58
+ self ._user = None
59
+ self ._repo = None
60
+ self ._current_version = None
61
+ self ._latest_version = None
62
+ self ._response = None
63
+ self ._update_date = None
64
+
65
+ @property
66
+ def token (self ):
67
+ return self ._token
68
+
69
+ @token .setter
70
+ def token (self , value ):
71
+ if value is None :
72
+ self ._token = None
73
+ else :
74
+ self ._token = str (value )
75
+
76
+ @property
77
+ def user (self ):
78
+ return self ._user
79
+
80
+ @user .setter
81
+ def user (self , value ):
82
+ self ._user = str (value )
83
+
84
+ @property
85
+ def repo (self ):
86
+ return self ._repo
87
+
88
+ @repo .setter
89
+ def repo (self , value ):
90
+ self ._repo = str (value )
91
+
92
+ @property
93
+ def api_url (self ):
94
+ return self ._api_url
95
+
96
+ @property
97
+ def update_date (self ):
98
+ return self ._update_date
99
+
100
+ @api_url .setter
101
+ def api_url (self , value ):
102
+ if not self .check_is_url (value ):
103
+ raise ValueError ("Not a valid URL: " + value )
104
+ self ._api_url = value
105
+
106
+ @staticmethod
107
+ def check_is_url (url ):
108
+ if not ("http://" in url or "https://" in url ):
109
+ return False
110
+ if "." not in url :
111
+ return False
112
+ return True
113
+
114
+ def delete_file_in_folder (self ):
115
+ """
116
+ The function `delete_file_in_folder` deletes all files inside a specified folder.
117
+ """
118
+ folder_path = os .path .join (
119
+ os .path .dirname (__file__ ), ".." , f"{ self .repo } " )
120
+
121
+ directories = [item for item in os .listdir (
122
+ folder_path ) if os .path .isdir (os .path .join (folder_path , item ))]
123
+
124
+ try :
125
+ for directory_name in directories :
126
+ if directory_name .startswith (f"{ self .user } " ):
127
+ target_folder = os .path .join (folder_path , directory_name )
128
+ for root , dirs , files in os .walk (target_folder ):
129
+ for file in files :
130
+ file_path = os .path .join (root , file )
131
+ if file == "version_info.json" :
132
+ os .remove (file_path )
133
+
134
+ print (f"Files inside { folder_path } deleted successfully." )
135
+ except FileNotFoundError as e :
136
+ print (f"Error deleting files in { folder_path } : { e } " )
137
+
138
+ def delete_folder (self ):
139
+ """
140
+ The `delete_folder` function deletes a specific folder and its contents within a given
141
+ repository.
142
+ """
143
+ folder_path = os .path .join (
144
+ os .path .dirname (__file__ ), ".." , f"{ self .repo } " )
145
+
146
+ directories = [item for item in os .listdir (
147
+ folder_path ) if os .path .isdir (os .path .join (folder_path , item ))]
148
+ try :
149
+ for directory_name in directories :
150
+ if directory_name .startswith (f"{ self .user } " ):
151
+ target_folder = os .path .join (folder_path , directory_name )
152
+ shutil .rmtree (target_folder )
153
+ print (f"Folder { folder_path } deleted successfully." )
154
+ except FileNotFoundError as e :
155
+ print (f"Error deleting folder { folder_path } : { e } " )
156
+
157
+ def extract_folder (self ):
158
+ """
159
+ The function `extract_folder` extracts the contents of a specific folder from a given repository
160
+ and copies them to the base path.
161
+ """
162
+ folder_path = os .path .join (
163
+ os .path .dirname (__file__ ), ".." , f"{ self .repo } " )
164
+ directories = [item for item in os .listdir (
165
+ folder_path ) if os .path .isdir (os .path .join (folder_path , item ))]
166
+ # Find the specific folder that starts with username
167
+ target_folder = None
168
+ for directory_name in directories :
169
+ if directory_name .startswith (f"{ self .user } " ):
170
+ target_folder = os .path .join (folder_path , directory_name )
171
+ break
172
+
173
+ if target_folder is not None :
174
+ destination_folder = folder_path
175
+ print (f"Found target folder: { target_folder } " )
176
+ for item in os .listdir (target_folder ):
177
+ source_item_path = os .path .join (target_folder , item )
178
+ destination_item_path = os .path .join (destination_folder , item )
179
+
180
+ if os .path .isfile (source_item_path ):
181
+ shutil .copy2 (source_item_path , destination_item_path )
182
+ elif os .path .isdir (source_item_path ):
183
+ shutil .copytree (source_item_path , destination_item_path )
184
+ print ("Contents extracted to base path." )
185
+ else :
186
+ print ("Target folder not found." )
187
+
188
+ @bpy .app .handlers .persistent
189
+ def check_for_updates (self ):
190
+ """
191
+ The above function checks for updates of a Blender add-on by making a request to a specified API
192
+ endpoint and compares the latest version with the current version of the add-on.
193
+ :return: The code is returning the latest version of the addon, the current version of the
194
+ addon, and the update date.
195
+ """
196
+ update_url = f"{ self .api_url } /repos/{ self .user } /{ self .repo } /releases/latest"
197
+ addon_path = os .path .dirname (__file__ )
198
+
199
+ try :
200
+ response = requests .get (update_url )
201
+ response .raise_for_status ()
202
+ except requests .exceptions .RequestException as e :
203
+ print ("Error checking for updates:" , e )
204
+ if response .status_code != 200 :
205
+ print ("Response content:" , response .content )
206
+ return None
207
+
208
+ data = json .loads (response .text )
209
+ date = datetime .datetime .now ()
210
+ latest_version = data ["tag_name" ]
211
+ current_version = f"{ bl_info ['version' ][0 ]} .{ bl_info ['version' ][1 ]} .{ bl_info ['version' ][2 ]} "
212
+
213
+ addon_version = {
214
+ "current_version" : current_version ,
215
+ "latest_version" : latest_version ,
216
+ "update_date" : str (date ),
217
+ }
218
+ json_file_path = os .path .join (addon_path , "version_info.json" )
219
+ try :
220
+ with open (json_file_path , 'w' ) as json_file :
221
+ json .dump (addon_version , json_file , indent = 1 )
222
+ except zipfile .BadZipFile as e :
223
+ print ("Error extracting zip file:" , e )
224
+ return None
225
+ if self ._latest_version != self ._current_version :
226
+ return self ._latest_version
227
+ return self ._current_version , self ._update_date
228
+
229
+ @bpy .app .handlers .persistent
230
+ def update (self ):
231
+ """
232
+ The `update` function downloads a zip file from a specified URL, extracts its contents, and
233
+ performs some additional operations on the extracted files.
234
+ :return: None if there is an error extracting the zip file.
235
+ """
236
+ zipball_url = f"{ self .api_url } /repos/{ self .user } /{ self .repo } /zipball/{ self .check_for_updates ()} "
237
+
238
+ response = requests .get (zipball_url )
239
+ self ._response = response
240
+
241
+ addon_path = os .path .dirname (__file__ )
242
+ zip_data = io .BytesIO (response .content )
243
+
244
+ try :
245
+ with zipfile .ZipFile (zip_data , 'r' ) as zip_ref :
246
+ zip_ref .extractall (addon_path )
247
+ self .extract_folder ()
248
+ self .delete_file_in_folder ()
249
+ self .delete_folder ()
250
+ except zipfile .BadZipFile as e :
251
+ print ("Error extracting zip file:" , e )
252
+ return None
253
+
254
+
255
+ engine = GithubEngine ()
256
+
257
+
258
+ class AddCubeOperator (bpy .types .Operator ):
259
+ bl_label = "ADD CUBE"
260
+ bl_idname = "add_simple.cube"
261
+ bl_options = {'REGISTER' , 'UNDO' }
262
+
263
+ def execute (self , context ):
264
+ bpy .ops .mesh .primitive_cube_add ()
265
+ return {'FINISHED' }
266
+
267
+
268
+ # The `Release_Notes` class is an operator in Blender that opens the release notes of an addon in a
269
+ # web browser.
270
+ class Release_Notes (bpy .types .Operator ):
271
+ bl_label = "View the Release Notes"
272
+ bl_idname = "addonupdater.release_notes"
273
+
274
+ def execute (self , context ):
275
+ webbrowser .open (
276
+ f"https://github.com/{ engine .user } /{ engine .repo } " , new = 1 )
277
+ return {'FINISHED' }
278
+
279
+
280
+ # The above class is an operator in Blender that checks for updates to an add-on and updates it if a
281
+ # new version is available.
282
+ class Update (bpy .types .Operator ):
283
+ bl_label = "Update"
284
+ bl_idname = "addonupdater.checkupdate"
285
+
286
+ @classmethod
287
+ def poll (cls , context ):
288
+ return engine ._current_version != engine ._latest_version
289
+
290
+ def execute (self , context ):
291
+ engine .update ()
292
+ if engine ._response .status_code != 200 :
293
+ self .report ({'ERROR' }, "Error getting update" )
294
+ return {'CANCELLED' }
295
+
296
+ if engine ._current_version == engine ._latest_version :
297
+ self .report (
298
+ {'ERROR' }, "You are already using the latest version of the add-on." )
299
+ return {'CANCELLED' }
300
+
301
+ self .report (
302
+ {'INFO' }, "Add-on has been updated. Please restart Blender to apply changes." )
303
+ return {'FINISHED' }
304
+
305
+
306
+ # The Check_for_update class is a Blender operator that checks for updates to an add-on and reports if
307
+ # a new version is available.
308
+ class Check_for_update (bpy .types .Operator ):
309
+ bl_label = "Check Update"
310
+ bl_idname = "addonupdater.update"
311
+
312
+ def invoke (self , context , event ):
313
+ self .execute (self )
314
+ return {'FINISHED' }
315
+
316
+ def execute (self , context ):
317
+ engine .check_for_updates ()
318
+ if not engine .user or not engine .repo :
319
+ self .report (
320
+ {'ERROR' }, "GitHub user and repository details are not set." )
321
+ return {'CANCELLED' }
322
+ if engine ._current_version != engine ._latest_version :
323
+ self .report ({'INFO' }, "A new version is available!" )
324
+ elif engine ._current_version == engine ._latest_version :
325
+ self .report (
326
+ {'INFO' }, "You are already using the latest version of the add-on." )
327
+ return {'FINISHED' }
328
+
329
+
330
+ # The above class is an addon preferences class in Python that displays information about the latest
331
+ # version of the addon and provides options to check for updates and update the addon.
332
+ > >> >> >> 56 f10dafd2c56f3de74956cb97c99b407b3305be
20
333
class AddonPreferences (bpy .types .AddonPreferences ):
21
334
bl_idname = __name__
22
335
@@ -63,6 +376,7 @@ def draw(self, context):
63
376
row .label (text = "Error loading version information." )
64
377
65
378
379
+ < << << << HEAD
66
380
class AddCubeOperator (bpy .types .Operator ):
67
381
bl_label = "ADD CUBE"
68
382
bl_idname = "add_simple.cube"
@@ -73,6 +387,8 @@ def execute(self, context):
73
387
return {'FINISHED' }
74
388
75
389
390
+ == == == =
391
+ >> >> >> > 56 f10dafd2c56f3de74956cb97c99b407b3305be
76
392
class AddonUpdaterPanel (bpy .types .Panel ):
77
393
bl_label = "Addon Updater"
78
394
bl_space_type = "VIEW_3D"
@@ -104,6 +420,10 @@ def register():
104
420
105
421
engine .user = "kents00" # Replace this with your username
106
422
engine .repo = "KUpdater" # Replace this with your repository name
423
+ < << << << HEAD
424
+ == == == =
425
+ engine .token = None # Set your GitHub token here if necessary
426
+ >> >> >> > 56 f10dafd2c56f3de74956cb97c99b407b3305be
107
427
108
428
for cls in classes :
109
429
bpy .utils .register_class (cls )
0 commit comments