-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathstart_version_control.py
343 lines (269 loc) · 11.6 KB
/
start_version_control.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
# ----------------------------------------------------------------------------
# Copyright (c) 2021, Diego Garcia Huerta.
#
# Your use of this software as distributed in this GitHub repository, is
# governed by the MIT License
#
# Your use of the Shotgun Pipeline Toolkit is governed by the applicable
# license agreement between you and Autodesk / Shotgun.
#
# Read LICENSE and SHOTGUN_LICENSE for full details about the licenses that
# pertain to this software.
# ----------------------------------------------------------------------------
import os
import sgtk
from sgtk.util.filesystem import ensure_folder_exists
import rumba
import rumbapy
__author__ = "Diego Garcia Huerta"
__contact__ = "https://www.linkedin.com/in/diegogh/"
HookBaseClass = sgtk.get_hook_baseclass()
class RumbaStartVersionControlPlugin(HookBaseClass):
"""
Simple plugin to insert a version number into the rumba file path if one
does not exist.
"""
@property
def icon(self):
"""
Path to an png icon on disk
"""
# look for icon one level up from this hook's folder in "icons" folder
return os.path.join(self.disk_location, os.pardir, "icons", "version_up.png")
@property
def name(self):
"""
One line display name describing the plugin
"""
return "Begin file versioning"
@property
def description(self):
"""
Verbose, multi-line description of what the plugin does. This can
contain simple html for formatting.
"""
return """
Adds a version number to the filename.<br><br>
Once a version number exists in the file, the publishing will
automatically bump the version number. For example,
<code>filename.ext</code> will be saved to
<code>filename.v001.ext</code>.<br><br>
If the session has not been saved, validation will fail and a button
will be provided in the logging output to save the file.<br><br>
If a file already exists on disk with a version number, validation will
fail and the logging output will include button to save the file to a
different name.<br><br>
"""
@property
def item_filters(self):
"""
List of item types that this plugin is interested in.
Only items matching entries in this list will be presented to the
accept() method. Strings can contain glob patters such as *, for example
["rumba.*", "file.rumba"]
"""
return ["rumba.session"]
@property
def settings(self):
"""
Dictionary defining the settings that this plugin expects to receive
through the settings parameter in the accept, validate, publish and
finalize methods.
A dictionary on the following form::
{
"Settings Name": {
"type": "settings_type",
"default": "default_value",
"description": "One line description of the setting"
}
The type string should be one of the data types that toolkit accepts as
part of its environment configuration.
"""
return {}
def accept(self, settings, item):
"""
Method called by the publisher to determine if an item is of any
interest to this plugin. Only items matching the filters defined via the
item_filters property will be presented to this method.
A publish task will be generated for each item accepted here. Returns a
dictionary with the following booleans:
- accepted: Indicates if the plugin is interested in this value at
all. Required.
- enabled: If True, the plugin will be enabled in the UI, otherwise
it will be disabled. Optional, True by default.
- visible: If True, the plugin will be visible in the UI, otherwise
it will be hidden. Optional, True by default.
- checked: If True, the plugin will be checked in the UI, otherwise
it will be unchecked. Optional, True by default.
:param settings: Dictionary of Settings. The keys are strings, matching
the keys returned in the settings property. The values are `Setting`
instances.
:param item: Item to process
:returns: dictionary with boolean keys accepted, required and enabled
"""
path = _session_path()
if path:
version_number = self._get_version_number(path, item)
if version_number is not None:
self.logger.info(
"Rumba '%s' plugin rejected the current session..." % (self.name,)
)
self.logger.info(" There is already a version number in the file...")
self.logger.info(" Rumba file path: %s" % (path,))
return {"accepted": False}
else:
# the session has not been saved before (no path determined).
# provide a save button. the session will need to be saved before
# validation will succeed.
self.logger.warn(
"The Rumba session has not been saved.", extra=_get_save_as_action()
)
self.logger.info(
"Rumba '%s' plugin accepted the current session." % (self.name,),
extra=_get_version_docs_action(),
)
# accept the plugin, but don't force the user to add a version number
# (leave it unchecked)
return {"accepted": True, "checked": False}
def validate(self, settings, item):
"""
Validates the given item to check that it is ok to publish.
Returns a boolean to indicate validity.
:param settings: Dictionary of Settings. The keys are strings, matching
the keys returned in the settings property. The values are `Setting`
instances.
:param item: Item to process
:returns: True if item is valid, False otherwise.
"""
publisher = self.parent
path = _session_path()
if not path:
# the session still requires saving. provide a save button.
# validation fails
error_msg = "The Rumba session has not been saved."
self.logger.error(error_msg, extra=_get_save_as_action())
raise Exception(error_msg)
# NOTE: If the plugin is attached to an item, that means no version
# number could be found in the path. If that's the case, the work file
# template won't be much use here as it likely has a version number
# field defined within it. Simply use the path info hook to inject a
# version number into the current file path
# get the path to a versioned copy of the file.
version_path = publisher.util.get_version_path(path, "v001")
if os.path.exists(version_path):
error_msg = "A file already exists with a version number. Please choose another name."
self.logger.error(error_msg, extra=_get_save_as_action())
raise Exception(error_msg)
return True
def publish(self, settings, item):
"""
Executes the publish logic for the given item and settings.
:param settings: Dictionary of Settings. The keys are strings, matching
the keys returned in the settings property. The values are `Setting`
instances.
:param item: Item to process
"""
publisher = self.parent
# get the path in a normalized state. no trailing separator, separators
# are appropriate for current os, no double separators, etc.
path = sgtk.util.ShotgunPath.normalize(_session_path())
# ensure the session is saved in its current state
_save_session(path)
# get the path to a versioned copy of the file.
version_path = publisher.util.get_version_path(path, "v001")
# save to the new version path
_save_session(version_path)
self.logger.info("A version number has been added to the Rumba file...")
self.logger.info(" Rumba file path: %s" % (version_path,))
def finalize(self, settings, item):
"""
Execute the finalization pass. This pass executes once
all the publish tasks have completed, and can for example
be used to version up files.
:param settings: Dictionary of Settings. The keys are strings, matching
the keys returned in the settings property. The values are `Setting`
instances.
:param item: Item to process
"""
pass
def _get_version_number(self, path, item):
"""
Try to extract and return a version number for the supplied path.
:param path: The path to the current session
:return: The version number as an `int` if it can be determined, else
None.
NOTE: This method will use the work template provided by the
session collector, if configured, to determine the version number. If
not configured, the version number will be extracted using the zero
config path_info hook.
"""
publisher = self.parent
version_number = None
work_template = item.properties.get("work_template")
if work_template:
if work_template.validate(path):
self.logger.debug("Using work template to determine version number.")
work_fields = work_template.get_fields(path)
if "version" in work_fields:
version_number = work_fields.get("version")
else:
self.logger.debug("Work template did not match path")
else:
self.logger.debug("Work template unavailable for version extraction.")
if version_number is None:
self.logger.debug("Using path info hook to determine version number.")
version_number = publisher.util.get_version_number(path)
return version_number
def _session_path():
"""
Return the path to the current session
:return:
"""
path = rumba.active_document_filename()
if path == "untitled":
path = None
return path
def _save_session(path):
"""
Save the current session to the supplied path.
"""
# Ensure that the folder is created when saving
folder = os.path.dirname(path)
ensure_folder_exists(folder)
active_doc = rumba.active_document()
if active_doc:
main_window = rumbapy.widget("MainWindow")
main_window.save_at(path)
# TODO: method duplicated in all the rumba hooks
def _get_save_as_action():
"""
Simple helper for returning a log action dict for saving the session
"""
engine = sgtk.platform.current_engine()
callback = _save_as
# if workfiles2 is configured, use that for file save
if "tk-multi-workfiles2" in engine.apps:
app = engine.apps["tk-multi-workfiles2"]
if hasattr(app, "show_file_save_dlg"):
callback = app.show_file_save_dlg
return {
"action_button": {
"label": "Save As...",
"tooltip": "Save the current session",
"callback": callback,
}
}
def _save_as():
main_window = rumbapy.widget("MainWindow")
main_window.save_as()
def _get_version_docs_action():
"""
Simple helper for returning a log action to show version docs
"""
return {
"action_open_url": {
"label": "Version Docs",
"tooltip": "Show docs for version formats",
"url": "https://support.shotgunsoftware.com/hc/en-us/articles/115000068574-User-Guide-WIP-#What%20happens%20when%20you%20publish",
}
}