mirror of
https://github.com/SK8-ENGINE/skate-3-rust-engine.git
synced 2026-10-26 19:47:44 +00:00
Fix updater branch status messages and Skyline remote VFX init.
Branch channel now reports already-current and missing-release cases clearly instead of a generic 404 error, and remote smoke/skids initialize without requiring local F10 spawn. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
1 parent
3dd61acc62
commit
9eaabf198e
3 files changed
+182
-85
No files matched your search
@@ -64,7 +64,7 @@ local state = {
|
||||
steer=0, shift=0, time=0, enter_after=0,
|
||||
keys={}, buttons=0, hud_time=0, mass_audit=nil, keyboard_axis=0, aero_load=0,
|
||||
enter_requested=false, exit_requested=false, reenter_on_ready=false,
|
||||
hull_debug=false, interaction_text="",
|
||||
hull_debug=false, interaction_text="", mp_hint_time=0,
|
||||
}
|
||||
|
||||
local function clamp(x,a,b) return math.max(a,math.min(b,x)) end
|
||||
@@ -388,6 +388,35 @@ local REMOTE_WHEEL_KEYS={rr="wheel_rr",rl="wheel_rl"}
|
||||
local function vfx_net_active()
|
||||
return sdk.net and type(sdk.net.info)=="function" and (sdk.net.info().active==true)
|
||||
end
|
||||
local function refresh_mp_status(dt)
|
||||
if not sdk.net or type(sdk.net.info)~="function" then
|
||||
sdk.ui.text("skyline_mp","")
|
||||
return
|
||||
end
|
||||
local info=sdk.net.info()
|
||||
if not info or info.active~=true then
|
||||
sdk.ui.text("skyline_mp","")
|
||||
return
|
||||
end
|
||||
state.mp_hint_time=math.max(0,(state.mp_hint_time or 0)-dt)
|
||||
if state.mp_hint_time>0 then return end
|
||||
state.mp_hint_time=0.5
|
||||
local lines={
|
||||
state.spawned
|
||||
and "Your car is spawned and should replicate to other players."
|
||||
or "Press F10 to spawn your car. Other players cannot see your vehicle until you do.",
|
||||
"Both players need Skyline enabled with identical mod files and the skyline-driving-update build.",
|
||||
}
|
||||
if vfx.supported then
|
||||
lines[#lines+1]=vfx.mesh
|
||||
and "VFX buffers ready (remote smoke/skids work without spawning your own car)."
|
||||
or "VFX supported but buffers not ready yet."
|
||||
else
|
||||
lines[#lines+1]="VFX unsupported: update to a build with mesh_buffer (graphics API 4)."
|
||||
end
|
||||
if info.status and info.status~="" then lines[#lines+1]=info.status end
|
||||
sdk.ui.text("skyline_mp",table.concat(lines,"\n"))
|
||||
end
|
||||
local function clear_mesh(mesh)
|
||||
mesh.positions={};mesh.normals={};mesh.colors={};mesh.uvs={};mesh.indices={}
|
||||
end
|
||||
@@ -995,11 +1024,28 @@ local function tick_remote_vfx(dt)
|
||||
end
|
||||
maybe_upload_skids()
|
||||
end
|
||||
local function prepare_effects()
|
||||
local function refresh_vfx_support()
|
||||
vfx.supported=sdk.graphics and (sdk.graphics.version or 0)>=4
|
||||
and type(sdk.graphics.mesh_buffer)=="function"
|
||||
and type(sdk.graphics.mesh_buffer_append)=="function"
|
||||
if not vfx.supported then return end
|
||||
return vfx.supported
|
||||
end
|
||||
local function ensure_vfx_mesh()
|
||||
if not refresh_vfx_support() then return false end
|
||||
if vfx.mesh then return true end
|
||||
vfx.smoke=vfx.smoke or {}
|
||||
vfx.remote_smoke=vfx.remote_smoke or {}
|
||||
vfx.remote_wheels=vfx.remote_wheels or {}
|
||||
vfx.mesh={
|
||||
smoke={positions={},normals={},colors={},uvs={},indices={}},
|
||||
remote_smoke={positions={},normals={},colors={},uvs={},indices={}},
|
||||
}
|
||||
sdk.graphics.mesh_buffer("vfx_smoke",SMOKE_BUFFER_OPTS)
|
||||
sdk.graphics.mesh_buffer("vfx_smoke_remote",SMOKE_BUFFER_OPTS)
|
||||
return true
|
||||
end
|
||||
local function prepare_effects()
|
||||
if not refresh_vfx_support() then return end
|
||||
clear_session_skids()
|
||||
vfx.skid_buffer_serial=0
|
||||
vfx.smoke={}; vfx.smoke_mesh_live=false; vfx.smoke_dirty=false
|
||||
@@ -1048,7 +1094,7 @@ local function tick_wheel_vfx(contacts,dt)
|
||||
publish_net_vfx(contacts)
|
||||
end
|
||||
local function upload_wheel_vfx(event)
|
||||
if not vfx.supported or not vfx.mesh then return end
|
||||
if not ensure_vfx_mesh() then return end
|
||||
if sdk.snapshot.paused or sdk.snapshot.replay then return end
|
||||
local dt=clamp(number(event and event.dt,1/60),0,0.1)
|
||||
tick_remote_vfx(dt)
|
||||
@@ -1777,13 +1823,14 @@ return {
|
||||
if not sdk.graphics or (sdk.graphics.version or 0)<2 or not sdk.player.detaching then
|
||||
error("Skyline 4.3.1: the embedded Lua SDK wrapper does not match the native Model Collision Repair API")
|
||||
end
|
||||
reset_simulation(); remove_rig(); prepare_audio(); prepare_presentation()
|
||||
reset_simulation(); remove_rig(); prepare_audio(); prepare_presentation(); ensure_vfx_mesh()
|
||||
set_collision_debug(sdk.settings.show_collision_hull==true)
|
||||
sdk.log("Skyline 4.3.1: render-model compound collision, native impacts, animated wheels, safe exits; J toggles actual colliders")
|
||||
sdk.ui.text("skyline_status",""); sdk.ui.text("skyline_handling",""); sdk.ui.text("skyline_wheels","")
|
||||
end,
|
||||
on_update=function(event)
|
||||
update_audio(event); update_dashboard(event); upload_wheel_vfx(event)
|
||||
refresh_mp_status(clamp(number(event and event.dt,1/60),0,0.1))
|
||||
end,
|
||||
on_settings=function(event)
|
||||
if event.key=="audio_enabled" and event.value==false then stop_audio() end
|
||||
@@ -1800,6 +1847,7 @@ return {
|
||||
clear_session_skids()
|
||||
sdk.ui.text("skyline_audio",""); sdk.ui.text("skyline_presentation","")
|
||||
sdk.ui.text("skyline_help",""); sdk.ui.text("skyline_status",""); sdk.ui.text("skyline_handling",""); sdk.ui.text("skyline_wheels","")
|
||||
sdk.ui.text("skyline_mp","")
|
||||
end,
|
||||
on_fixed_update=function(event)
|
||||
local dt=number(event and event.dt,1/120)
|
||||
@@ -1854,13 +1902,14 @@ return {
|
||||
end,
|
||||
on_event=function(event)
|
||||
if event.name=="world_changed" then
|
||||
stop_audio(); clear_presentation(); clear_effects(); clear_session_skids(); sound.prepared=false; prepare_audio()
|
||||
state.spawned=false; state.occupied=false; state.pending=nil
|
||||
remove_rig(); stop_audio(); clear_presentation(); sound.prepared=false; prepare_audio()
|
||||
state.pending=nil
|
||||
state.enter_requested=false; state.exit_requested=false; state.reenter_on_ready=false
|
||||
state.enter_after=state.time+C.reentry_delay; interaction_text("")
|
||||
set_collision_debug(state.hull_debug)
|
||||
state.keys={}; state.buttons=0; reset_simulation()
|
||||
sdk.ui.text("skyline_status",""); sdk.ui.text("skyline_handling",""); sdk.ui.text("skyline_wheels","")
|
||||
sdk.ui.text("skyline_mp","")
|
||||
end
|
||||
end,
|
||||
}
|
||||
+43
-33
@@ -150,6 +150,13 @@ class UpdaterTests(unittest.TestCase):
|
||||
redirected = u.DownloadRedirect().redirect_request(req, None, 302, 'Found', {}, 'https://release-assets.githubusercontent.com/file')
|
||||
self.assertIsNone(redirected.get_header('Authorization'))
|
||||
|
||||
def test_branch_choice_fallback(self):
|
||||
with patch.object(u, 'list_branches', side_effect=OSError('offline')):
|
||||
names = u.branch_choices(threading.Event(), 'feature/foo')
|
||||
self.assertEqual(names[0], 'feature/foo')
|
||||
self.assertIn('skyline-driving-update', names)
|
||||
self.assertIn('main', names)
|
||||
|
||||
def test_github_api_token_on_actions_requests(self):
|
||||
with patch.dict(os.environ, {'SKATE_UPDATE_GITHUB_TOKEN': 'test-token'}, clear=False):
|
||||
headers = u.github_request_headers(u.BRANCHES_API)
|
||||
@@ -198,51 +205,54 @@ class UpdaterTests(unittest.TestCase):
|
||||
u.stage(candidate, Path(temp), threading.Event(), lambda _: None)
|
||||
self.assertEqual((Path(temp) / 'new/skate3rust.exe').read_bytes(), b'new')
|
||||
|
||||
def test_branch_discovery_and_stage(self):
|
||||
meta = {**metadata(9), 'tag': 'experimental', 'revision': 'b' * 40}
|
||||
meta['files'] = {name: hashlib.sha256(b'new').hexdigest() for name in u.FILES[:-1]}
|
||||
package = u.package_name(meta)
|
||||
archive = io.BytesIO()
|
||||
with zipfile.ZipFile(archive, 'w') as inner:
|
||||
for name in [*meta['files'], 'release.json']:
|
||||
inner.writestr(u.PREFIX + name, json.dumps(meta) if name == 'release.json' else b'new')
|
||||
package_bytes = archive.getvalue()
|
||||
digest = hashlib.sha256(package_bytes).hexdigest()
|
||||
artifact = io.BytesIO()
|
||||
with zipfile.ZipFile(artifact, 'w') as outer:
|
||||
outer.writestr(package, package_bytes)
|
||||
outer.writestr(package + '.sha256', f'{digest} {package}')
|
||||
outer.writestr('release.json', json.dumps(meta))
|
||||
artifact_bytes = artifact.getvalue()
|
||||
def test_branch_release_status_messages(self):
|
||||
meta = {**metadata(12), 'tag': 'skyline-driving-update', 'revision': 'd' * 40}
|
||||
release = dict(
|
||||
id=90,
|
||||
tag_name='skyline-driving-update',
|
||||
draft=False,
|
||||
prerelease=True,
|
||||
assets=[
|
||||
dict(name=u.PACKAGE, state='uploaded', browser_download_url='package'),
|
||||
dict(name=u.PACKAGE + '.sha256', state='uploaded', browser_download_url='checksum'),
|
||||
dict(name='release.json', state='uploaded', browser_download_url='manifest'),
|
||||
],
|
||||
)
|
||||
|
||||
def fetch(url, *args):
|
||||
if url.endswith('/tags/skyline-driving-update'):
|
||||
return json.dumps(release).encode()
|
||||
if url.endswith('/tags/main'):
|
||||
raise u.urllib.error.HTTPError(url, 404, 'not found', {}, None)
|
||||
if url == 'manifest':
|
||||
return json.dumps(meta).encode()
|
||||
raise AssertionError(url)
|
||||
|
||||
current = {**metadata(12), 'revision': meta['revision']}
|
||||
with patch.object(u, 'fetch', fetch):
|
||||
message, revision = u.branch_release_status(current, 'skyline-driving-update', threading.Event())
|
||||
self.assertIn('already installed', message)
|
||||
self.assertEqual(revision, meta['revision'])
|
||||
message, revision = u.branch_release_status(current, 'main', threading.Event())
|
||||
self.assertIn('no published installer', message.lower())
|
||||
self.assertIsNone(revision)
|
||||
|
||||
def test_branch_discovery_without_published_release(self):
|
||||
def fetch(url, *args):
|
||||
if '/releases/tags/' in url:
|
||||
raise u.urllib.error.HTTPError(url, 404, 'not found', {}, None)
|
||||
if url.startswith(u.BRANCHES_API):
|
||||
return json.dumps([{'name': 'main'}, {'name': 'skyline'}]).encode()
|
||||
if '/workflows/' in url and '/runs?' in url:
|
||||
return json.dumps({'workflow_runs': [
|
||||
{'id': 77, 'conclusion': 'success', 'head_sha': meta['revision']},
|
||||
]}).encode()
|
||||
if url.endswith('/artifacts'):
|
||||
return json.dumps({'artifacts': [
|
||||
{'name': u.ARTIFACT_NAME, 'archive_download_url': 'https://api.github.com/repos/' + u.REPO + '/actions/artifacts/1/zip'},
|
||||
]}).encode()
|
||||
if url.endswith('/artifacts/1/zip'):
|
||||
return artifact_bytes
|
||||
raise AssertionError(url)
|
||||
|
||||
current = metadata(8)
|
||||
with patch.object(u, 'fetch', fetch):
|
||||
names = u.list_branches(threading.Event())
|
||||
self.assertIn('skyline', names)
|
||||
candidate = u.discover_branch(current, 'skyline', threading.Event())
|
||||
self.assertIsNotNone(candidate)
|
||||
self.assertEqual(candidate[0], 9)
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
u.stage(candidate, Path(temp), threading.Event(), lambda _: None)
|
||||
self.assertEqual((Path(temp) / 'new/skate3rust.exe').read_bytes(), b'new')
|
||||
self.assertIsNone(u.discover_branch({**current, 'revision': meta['revision']}, 'skyline', threading.Event()))
|
||||
self.assertIsNone(u.discover_branch(current, 'skyline', threading.Event()))
|
||||
message, revision = u.branch_release_status(current, 'skyline', threading.Event())
|
||||
self.assertIn('No published release', message)
|
||||
self.assertIsNone(revision)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
+83
-45
@@ -27,6 +27,7 @@ FILES = ('skate3rust.exe', 'support/skate3setup.exe', 'support/skate3update.exe'
|
||||
'steam-relay/skate-steam-relay.exe', 'steam-relay/steam_api64.dll', 'release.json')
|
||||
|
||||
PREFIX = 'skate3rust-windows-x64/'
|
||||
DEFAULT_BRANCH_CHOICES = ('skyline-driving-update', 'main')
|
||||
|
||||
|
||||
class DownloadRedirect(urllib.request.HTTPRedirectHandler):
|
||||
@@ -132,6 +133,8 @@ def fetch(url, cancel, limit, progress=lambda value: None):
|
||||
if error.code in (403, 429):
|
||||
raise ValueError('GitHub rate limit or access restriction; try again later') from error
|
||||
if error.code == 404:
|
||||
if '/releases/tags/' in url:
|
||||
raise
|
||||
raise ValueError('Releases are not publicly available, or private-release access is missing') from error
|
||||
raise
|
||||
with response:
|
||||
@@ -170,6 +173,17 @@ def release_candidates(assets, rolling):
|
||||
return [(f'release-{n}.json', f'skate3rust-windows-x64-build-{n}.zip') for n in builds]
|
||||
|
||||
|
||||
def merge_branch_names(names, preferred=''):
|
||||
values = [name for name in (names or DEFAULT_BRANCH_CHOICES) if isinstance(name, str) and name]
|
||||
preferred = (preferred or '').strip()
|
||||
if preferred and preferred not in values:
|
||||
values.insert(0, preferred)
|
||||
for fallback in DEFAULT_BRANCH_CHOICES:
|
||||
if fallback not in values:
|
||||
values.append(fallback)
|
||||
return values
|
||||
|
||||
|
||||
def list_branches(cancel, limit=200):
|
||||
branches = []
|
||||
for page in range(1, 6):
|
||||
@@ -182,6 +196,13 @@ def list_branches(cancel, limit=200):
|
||||
return branches[:limit]
|
||||
|
||||
|
||||
def branch_choices(cancel, preferred=''):
|
||||
try:
|
||||
return merge_branch_names(list_branches(cancel), preferred)
|
||||
except (urllib.error.URLError, urllib.error.HTTPError, TimeoutError, ValueError, json.JSONDecodeError, OSError):
|
||||
return merge_branch_names([], preferred)
|
||||
|
||||
|
||||
def branch_workflow_runs(branch, cancel):
|
||||
from urllib.parse import quote
|
||||
query = (
|
||||
@@ -215,7 +236,7 @@ def package_from_artifact(artifact_zip, package):
|
||||
return payload, checksum
|
||||
|
||||
|
||||
def discover_branch_release(current, branch, cancel, repair=False):
|
||||
def branch_release_meta(branch, cancel):
|
||||
from urllib.parse import quote
|
||||
branch = (branch or '').strip()
|
||||
try:
|
||||
@@ -230,18 +251,40 @@ def discover_branch_release(current, branch, cancel, repair=False):
|
||||
return None
|
||||
try:
|
||||
meta = json.loads(fetch(asset_url(assets['release.json']), cancel, 65536))
|
||||
build = identity(meta)
|
||||
identity(meta)
|
||||
program_metadata(meta)
|
||||
except (ValueError, KeyError):
|
||||
except (ValueError, KeyError, urllib.error.HTTPError):
|
||||
return None
|
||||
return release, assets, meta
|
||||
|
||||
|
||||
def discover_branch_release(current, branch, cancel, repair=False):
|
||||
loaded = branch_release_meta(branch, cancel)
|
||||
if loaded is None:
|
||||
return None
|
||||
release, assets, meta = loaded
|
||||
if meta['revision'] == current.get('revision') and not repair:
|
||||
return None
|
||||
notes = dict(
|
||||
tag_name=branch,
|
||||
body=(f'Branch `{branch}`\n'
|
||||
f'Build {build} · revision {meta.get("revision", "")[:12]}'),
|
||||
f'Build {meta["build"]} · revision {meta.get("revision", "")[:12]}'),
|
||||
)
|
||||
return (build, release['id'], notes, assets, meta)
|
||||
return (meta['build'], release['id'], notes, assets, meta)
|
||||
|
||||
|
||||
def branch_release_status(current, branch, cancel):
|
||||
loaded = branch_release_meta(branch, cancel)
|
||||
if loaded is None:
|
||||
branch = (branch or '').strip()
|
||||
if branch == 'main':
|
||||
return ('Branch `main` has no published installer. Use `skyline-driving-update`.', None)
|
||||
return (f'No published release found for branch `{branch}`.', None)
|
||||
_, _, meta = loaded
|
||||
revision = meta.get('revision', '')
|
||||
if revision and revision == current.get('revision'):
|
||||
return (f'Branch `{branch}` build {meta["build"]} is already installed ({revision[:12]}).', revision)
|
||||
return (None, revision)
|
||||
|
||||
|
||||
def discover_branch(current, branch, cancel, repair=False):
|
||||
@@ -251,32 +294,6 @@ def discover_branch(current, branch, cancel, repair=False):
|
||||
candidate = discover_branch_release(current, branch, cancel, repair=repair)
|
||||
if candidate is not None:
|
||||
return candidate
|
||||
current_revision = current.get('revision')
|
||||
for run in branch_workflow_runs(branch, cancel):
|
||||
if run.get('conclusion') != 'success':
|
||||
continue
|
||||
try:
|
||||
artifact_zip = download_branch_artifact(run['id'], cancel)
|
||||
import io
|
||||
with zipfile.ZipFile(io.BytesIO(artifact_zip)) as archive:
|
||||
if 'release.json' not in {info.filename for info in archive.infolist()}:
|
||||
continue
|
||||
meta = json.loads(archive.read('release.json'))
|
||||
build = identity(meta)
|
||||
program_metadata(meta)
|
||||
if meta['revision'] == current_revision and not repair:
|
||||
continue
|
||||
package = package_name(meta)
|
||||
package_from_artifact(artifact_zip, package)
|
||||
notes = dict(
|
||||
tag_name=meta.get('tag', branch),
|
||||
body=(f'Branch `{branch}`\n'
|
||||
f'Workflow run {run.get("id")} @ {run.get("head_sha", "")[:12]}\n'
|
||||
f'Build {build} · revision {meta.get("revision", "")[:12]}'),
|
||||
)
|
||||
return (build, run['id'], notes, None, meta, artifact_zip)
|
||||
except (ValueError, KeyError, zipfile.BadZipFile):
|
||||
continue
|
||||
return None
|
||||
|
||||
|
||||
@@ -463,8 +480,23 @@ def main(request=None):
|
||||
def branch_ui_enabled():
|
||||
return channel.get() == 'Branch'
|
||||
|
||||
def apply_branch_choices(names):
|
||||
branch_choice.configure(values=merge_branch_names(names, branch.get().strip()))
|
||||
|
||||
def refresh_branch_ui():
|
||||
branch_choice.configure(state='normal' if branch_ui_enabled() else 'disabled')
|
||||
enabled = branch_ui_enabled()
|
||||
branch_choice.configure(state='normal' if enabled else 'disabled')
|
||||
if enabled:
|
||||
apply_branch_choices(branch_choice.cget('values') or DEFAULT_BRANCH_CHOICES)
|
||||
|
||||
def load_branch_list():
|
||||
token = generation
|
||||
def run():
|
||||
try:
|
||||
events.put((token, 'branches', branch_choices(cancel, branch.get().strip())))
|
||||
except Exception:
|
||||
events.put((token, 'branches', merge_branch_names([], branch.get().strip())))
|
||||
threading.Thread(target=run, daemon=True).start()
|
||||
|
||||
def work(kind, operation):
|
||||
nonlocal busy
|
||||
@@ -512,12 +544,12 @@ def main(request=None):
|
||||
repair_after_update = (bool(repair_files) and isinstance(previous.get('build'), int)
|
||||
and previous['build'] < current['build'])
|
||||
if selected == 'Branch':
|
||||
names = list_branches(cancel)
|
||||
selected_branch = branch.get().strip()
|
||||
if selected_branch and selected_branch not in names:
|
||||
names.insert(0, selected_branch)
|
||||
return names, discover_branch(current, selected_branch, cancel, repair=bool(repair_files))
|
||||
return None, discover(current, selected, cancel, repair=bool(repair_files))
|
||||
selected_branch = branch.get().strip() or DEFAULT_BRANCH_CHOICES[0]
|
||||
names = branch_choices(cancel, selected_branch)
|
||||
candidate = discover_branch(current, selected_branch, cancel, repair=bool(repair_files))
|
||||
branch_status = branch_release_status(current, selected_branch, cancel) if candidate is None else None
|
||||
return names, candidate, branch_status
|
||||
return None, discover(current, selected, cancel, repair=bool(repair_files)), None
|
||||
work('checked', perform_check)
|
||||
|
||||
def do_update():
|
||||
@@ -552,10 +584,14 @@ def main(request=None):
|
||||
win.protocol('WM_DELETE_WINDOW', close)
|
||||
def channel_changed(_=None):
|
||||
refresh_branch_ui()
|
||||
if branch_ui_enabled():
|
||||
load_branch_list()
|
||||
check_now()
|
||||
|
||||
choice.bind('<<ComboboxSelected>>', channel_changed)
|
||||
refresh_branch_ui()
|
||||
if branch_ui_enabled():
|
||||
load_branch_list()
|
||||
|
||||
def poll():
|
||||
nonlocal candidate, busy, automatic
|
||||
@@ -567,6 +603,9 @@ def main(request=None):
|
||||
if kind == 'progress':
|
||||
status.set(value)
|
||||
continue
|
||||
if kind == 'branches':
|
||||
apply_branch_choices(value)
|
||||
continue
|
||||
busy = False
|
||||
if closing:
|
||||
win.destroy()
|
||||
@@ -583,12 +622,9 @@ def main(request=None):
|
||||
win.destroy()
|
||||
return
|
||||
elif kind == 'checked':
|
||||
branch_names, candidate = value
|
||||
branch_names, candidate, branch_status = value
|
||||
if branch_names:
|
||||
values = [name for name in branch_names if isinstance(name, str) and name]
|
||||
if branch.get().strip() and branch.get().strip() not in values:
|
||||
values.insert(0, branch.get().strip())
|
||||
branch_choice.configure(values=values)
|
||||
apply_branch_choices(branch_names)
|
||||
if candidate:
|
||||
automatic = False
|
||||
win.deiconify()
|
||||
@@ -611,8 +647,10 @@ def main(request=None):
|
||||
win.destroy()
|
||||
return
|
||||
else:
|
||||
if channel.get() == 'Branch':
|
||||
status.set('No successful CI build found for this branch yet, or you already have that revision.')
|
||||
if channel.get() == 'Branch' and branch_status and branch_status[0]:
|
||||
status.set(branch_status[0])
|
||||
elif channel.get() == 'Branch':
|
||||
status.set('No published branch release found, or you already have that revision.')
|
||||
elif repair_files:
|
||||
status.set('Repair needed, but this build is not available in the selected channel. Select its channel or a newer release.')
|
||||
else:
|
||||
|
||||
Reference in new issue
Block a user